pax_global_header00006660000000000000000000000064130116261400014504gustar00rootroot0000000000000052 comment=458f31838a7db1744b4ad0dfcd8433c92eee71f9 glyr-1.0.10/000077500000000000000000000000001301162614000125405ustar00rootroot00000000000000glyr-1.0.10/.clang_complete000066400000000000000000000002011301162614000155060ustar00rootroot00000000000000-c -pthread -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -pthread -lsqlite3 -lcurl -lcheck -lgthread-2.0 -lrt -lglib-2.0 glyr-1.0.10/.gitignore000066400000000000000000000006231301162614000145310ustar00rootroot00000000000000# Ignore object files *.o *.a *.so __pycache__/ *.pyc *.patch *.cmake core *.gch libglyr.pc doc/doc doc/build spec/provider/results/ # Ignore documentation, local working directory child/ bin/ # Ignore CMake output CMakeCache.txt CMakeFiles/ cmake_install.cmake install_manifest.txt Makefile # testoutput test/out/* # swig *_wrap.c # vim swap files *.swp *.swo *.swn #rmlint rmlint.sh rmlint.log glyr-1.0.10/AUTHORS000066400000000000000000000010471301162614000136120ustar00rootroot00000000000000Christopher Pahl CONTRIBUTORS ------------ Sebastian Pahl - lyricsvip provider - various fixes (in stringlib.c) Christoph Piechula - jamendo provider Matias De lellis - 'make dist' target Etienne Millon - Copyright issues - md5_update from RSA to Glib checksum Qball Cow - Numerous bugreports and partly fixes - Being the first adaptor + People reporting bugs and using libglyr. Please tell me if I forgot somebody. glyr-1.0.10/CHANGELOG000066400000000000000000000071561301162614000137630ustar00rootroot00000000000000Hand written changelog of important changes. Use the github page to view the automated git generated brother. 2016/11/12 ---------- + Changed License to LGPLv3 (from GPLv3) 2011/03/07 ---------- + Fixed lyrix.at parser (ctr was incremented always, not only on item found) + Speed up of cover and lyrics plugins (Up to 200%) + Fixed lyrics:magistrix plugin + Fixed lyrics:lyricswiki plugin + Fix of DynHelp + Fixed overall memory leaks + added finalize() call to cleanup plugins 2011/03/09 ---------- + Help text reacts now on "I DONT WANNA COLOR!" + added ability to stop searchengine by returning GLYRE_STOP_BY_CB in callback + Fixed a bug when using the google plugin and -D + Fixed google (and other) returning 404 Pages + Added 'tracklist' getter12 + Fixed all warnings occuring with highger warn level (Default now) 2011/03/13 ---------- + Added examplce.c + Removed alldcovers plugin (they have good image, but don't allow API usage for library and desktop applications it seems :-( ) + Added format check for image plugins. Almost 100% success (== valid images) with this check now + Added option (C API) to configure levenshtein fuzzyness + went to bed :-) 2011/03/14 ---------- + Fixed a bug that made the itemcounter to be negative. ("Got -21 images!" he happily replied) 2011/03/17 ---------- + Fixed very ugly stringop.c - Many functions should be faster and smoother now. + Fixed -w stdout and tracklist. (fwrite didn't recognize \0, as ->size was the duration here) + Fixed an ugly bug in photos.c that crashed libglyr if ignoring a URL + added provider photos:google + reformatted due to trying anjuta as IDE (instead of pure gVim) 2011/03/18 ---------- + Replaced ugly remove_html_tags with a more generic version that works completely in-place + Removed a few redundant strlen()s + added (non-working) albumlist getter which gets all official albums from a certain artist 2011/03/19 ---------- + made albumlist getter working. Wohoo. + Fixed dumb shortcut bug in glyrc + Made finalize() of ainfo,similiar,review,tracklist,albumlist generic, i.e. only call the same function 2011/03/24 ---------- + Fixed crash when libcurl put an error to stdout (ouch..) + Fixed google names when having spaces (ouch x 2) 2011/03/28 ---------- + Did a lot of fixing regarding the API + Fixed "broken" review getter. Still slow; needs more sources. + Extended src/example.c to use more functions. + Tested API (It works, biaatch!) 2011/03/29 ---------- + Made 'return GLYRE_STOP_BY_CB;' work. + removed Gly_infoat, it was only for devs.. 2011/05/15 ---------- Found some time to work a bit here :-) + Added support for google translator (new 'gtrans' getter), lyrics/text can bew translated directly now. It is a bit restricted atm. as only 100.000 chars are allowed per day per IP address. But I already requested more. (20x times as much), hopefully it will get through. Also language detecting works. + Glyr does now checksumming. This is useful for developers which want to make sure the data is unique. For normal users: Well, the duplicate check is now as fast as rmlint :-P + getopt_long is now getopt_long_only (no --arguments, just -argument or -a) + removed call_direct. It's of no use. Not even for devs. 2011/05/16 ---------- + removed wikipedia.c for ainfo - never worked. Better provide a link and (relation getter) and let the user render the site (too many information would get lost if glyr would need to parse it) 2011/06/14 ---------- + added support for proxy. (only basic libcurl support, should be suffiecient for 99% of all cases) + made API *much* slicker & nicer 2011/06/25 ---------- - CHANGELOG discontinued, use the git commit history instead glyr-1.0.10/CMakeLists.txt000066400000000000000000000166071301162614000153120ustar00rootroot00000000000000CMAKE_MINIMUM_REQUIRED(VERSION 2.6) # Write in C, write in Ceeee... :-) PROJECT(glyr C) # Include pkg-config INCLUDE(FindPkgConfig) # ------------------------------------------------ # You can configure the next few params on the cmd: # -DLIB_SUFFIX="64" for example # ------------------------------------------------ IF(NOT DEFINED INSTALL_BIN_DIR) SET(INSTALL_BIN_DIR bin) ELSE() MESSAGE("-- Install binaries to: \"${INSTALL_BIN_DIR}\"") ENDIF() IF(NOT DEFINED INSTALL_LIB_DIR) SET(INSTALL_LIB_DIR lib${LIB_SUFFIX}) ELSE() MESSAGE("-- Install library to: \"${INSTALL_LIB_DIR}\"") ENDIF() IF(NOT DEFINED INSTALL_INC_DIR) SET(INSTALL_INC_DIR include) ELSE() MESSAGE("-- Install headers to: \"${INSTALL_INC_DIR}\"") ENDIF() IF(DEFINED LIB_SUFFIX) MESSAGE("-- Library suffix is: \"${LIB_SUFFIX}\"") ENDIF() # ------------------------------------------------ # ------------------------------------------------ # Versioning # ------------------------------------------------ SET(GLYR_VERSION_MAJOR "1") SET(GLYR_VERSION_MINOR "0") SET(GLYR_VERSION_MICRO "9") SET(GLYR_VERSION_NAME "Raving Raven") # ------------------------------------------------ exec_program( "git" ${CMAKE_CURRENT_SOURCE_DIR} ARGS "describe" OUTPUT_VARIABLE VERSION ) # string(REGEX MATCH "-g.*$" VERSION_SHA1 ${VERSION}) # string(REGEX REPLACE "[-g]" "" VERSION_SHA1 ${VERSION_SHA1}) # ------------------------------------------------ # CFlags and warnlevel / build config # ------------------------------------------------ # Tell config.h.in IF(CMAKE_BUILD_TYPE STREQUAL "debug") SET(GLYR_DEBUG TRUE) ELSE() SET(CMAKE_BUILD_TYPE "release") SET(GLYR_DEBUG FALSE) ENDIF() MESSAGE("-- Building Target: ${CMAKE_BUILD_TYPE}") IF(CMAKE_COMPILER_IS_GNUCC) SET(GCC_ONLY_FLAGS "-std=c99") SET(GCC_ONLY_OPT "-s") ENDIF() SET(COMMON_FLAGS "${GCC_ONLY_FLAGS} -Wall -Wextra -Wstrict-prototypes -W -Wno-unused-parameter -Wno-strict-prototypes -fvisibility=hidden") SET(CMAKE_C_FLAGS_RELEASE "${COMMON_FLAGS} ${CMAKE_C_FLAGS} -Os ${GCC_ONLY_OPT}") SET(CMAKE_C_FLAGS_DEBUG "${COMMON_FLAGS} ${CMAKE_C_FLAGS} -g3") SET(CMAKE_C_FLAGS "${COMMON_FLAGS} ${CMAKE_C_FLAGS}") # ------------------------------------------------ # ------------------------------------------------ # Configuration & pkg-config # ------------------------------------------------ CONFIGURE_FILE( "libglyr.pc.in" "libglyr.pc" ) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/libglyr.pc DESTINATION ${INSTALL_LIB_DIR}/pkgconfig) # ---------------------- # Find deps # ---------------------- FIND_PACKAGE(CURL REQUIRED) PKG_CHECK_MODULES(GLIBPKG glib-2.0>=2.10 gthread-2.0 REQUIRED) PKG_CHECK_MODULES(SQLITE3 sqlite3 REQUIRED) INCLUDE_DIRECTORIES(${GLIBPKG_INCLUDE_DIRS}) # -------------------------- # set directories # -------------------------- SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin CACHE PATH "Single Directory for all Libraries") SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin CACHE PATH "Single Directory for all Executables.") SET(SUBDIR_LIB lib) SET(SUBDIR_SRC src) SET(SUBDIR_SWIG swig) SET(DIR_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/${SUBDIR_LIB}) SET(DIR_INTERN ${DIR_ROOT}/intern ) SET(DIR_COVER ${DIR_INTERN}/cover ) SET(DIR_LYRICS ${DIR_INTERN}/lyrics ) SET(DIR_PHOTOS ${DIR_INTERN}/photos ) SET(DIR_AINFO ${DIR_INTERN}/ainfo ) SET(DIR_SIMILAR ${DIR_INTERN}/similar_artist ) SET(DIR_SIMILSO ${DIR_INTERN}/similar_song ) SET(DIR_REVIEW ${DIR_INTERN}/review ) SET(DIR_TRACKLIST ${DIR_INTERN}/tracklist ) SET(DIR_ALBUMLIST ${DIR_INTERN}/albumlist ) SET(DIR_TAGS ${DIR_INTERN}/tags ) SET(DIR_RELATIONS ${DIR_INTERN}/relations ) SET(DIR_GUITARTABS ${DIR_INTERN}/guitartabs ) SET(DIR_BACKDROPS ${DIR_INTERN}/backdrops ) SET(DIR_JSMN ${DIR_ROOT}/jsmn ) # ------------------------------------------------ # Set source locations # ------------------------------------------------ SET(LIB_SOURCE_LOCATIONS # core "${DIR_ROOT}/glyr.c" "${DIR_ROOT}/core.c" "${DIR_ROOT}/misc.c" "${DIR_ROOT}/cache_intern.c" "${DIR_ROOT}/cache.c" "${DIR_ROOT}/register_plugins.c" "${DIR_ROOT}/stringlib.c" "${DIR_ROOT}/blacklist.c" "${DIR_ROOT}/testing.c" # "Builtin" special providers "${DIR_INTERN}/cache/db_provider.c" "${DIR_INTERN}/musictree/musictree.c" # jsmn "${DIR_JSMN}/jsmn.c" # fetcher "${DIR_INTERN}/generic.c" "${DIR_INTERN}/ainfo.c" "${DIR_INTERN}/cover.c" "${DIR_INTERN}/similar_artist.c" "${DIR_INTERN}/similar_song.c" "${DIR_INTERN}/lyrics.c" "${DIR_INTERN}/photos.c" "${DIR_INTERN}/review.c" "${DIR_INTERN}/tracklist.c" "${DIR_INTERN}/tags.c" "${DIR_INTERN}/relations.c" "${DIR_INTERN}/albumlist.c" "${DIR_INTERN}/guitartabs.c" "${DIR_INTERN}/backdrops.c" # extensions "${DIR_INTERN}/common/mbid_lookup.c" "${DIR_INTERN}/common/google.c" "${DIR_INTERN}/common/amazon.c" "${DIR_INTERN}/common/picsearch.c" "${DIR_INTERN}/common/musicbrainz.c" "${DIR_AINFO}/lastfm.c" "${DIR_AINFO}/echonest.c" "${DIR_AINFO}/bbcmusic.c" "${DIR_AINFO}/lyricsreg.c" "${DIR_SIMILAR}/lastfm.c" "${DIR_SIMILSO}/lastfm.c" "${DIR_TRACKLIST}/musicbrainz.c" "${DIR_REVIEW}/amazon.c" "${DIR_REVIEW}/echonest.c" "${DIR_REVIEW}/metallum.c" "${DIR_ALBUMLIST}/musicbrainz.c" "${DIR_RELATIONS}/musicbrainz.c" "${DIR_RELATIONS}/generated.c" "${DIR_TAGS}/musicbrainz.c" "${DIR_COVER}/coverartarchive.c" "${DIR_COVER}/lastfm.c" "${DIR_COVER}/jamendo.c" "${DIR_COVER}/google.c" "${DIR_COVER}/coverhunt.c" "${DIR_COVER}/lyricswiki.c" "${DIR_COVER}/albumart.c" "${DIR_COVER}/discogs.c" "${DIR_COVER}/amazon.c" "${DIR_COVER}/rhapsody.c" "${DIR_COVER}/picsearch.c" "${DIR_COVER}/musicbrainz.c" "${DIR_COVER}/slothradio.c" "${DIR_LYRICS}/lyrdb.c" "${DIR_LYRICS}/metallum.c" "${DIR_LYRICS}/magistrix.c" "${DIR_LYRICS}/lyrix_at.c" "${DIR_LYRICS}/lyricsvip.c" "${DIR_LYRICS}/metrolyrics.c" "${DIR_LYRICS}/lyricswiki.c" "${DIR_LYRICS}/lyricstime.c" "${DIR_LYRICS}/lyricsreg.c" "${DIR_LYRICS}/lipwalk.c" "${DIR_LYRICS}/elyrics.c" "${DIR_LYRICS}/chartlyrics.c" "${DIR_LYRICS}/vagalume.c" "${DIR_PHOTOS}/flickr.c" "${DIR_PHOTOS}/lastfm.c" "${DIR_PHOTOS}/google.c" "${DIR_PHOTOS}/discogs.c" "${DIR_PHOTOS}/singerpictures.c" "${DIR_PHOTOS}/rhapsody.c" "${DIR_PHOTOS}/picsearch.c" "${DIR_PHOTOS}/bbcmusic.c" "${DIR_GUITARTABS}/guitaretab.c" "${DIR_GUITARTABS}/chordie_com.c" "${DIR_BACKDROPS}/htbackdrops.c" # Old plugins, removed due to bad quality # "${DIR_LYRICS}/songlyrics.c" # "${DIR_LYRICS}/directlyrics.c" # "${DIR_LYRICS}/darklyrics.c" ) # ------------------------------------------------ # go on with subdirs # ------------------------------------------------ ADD_SUBDIRECTORY( ${SUBDIR_LIB} ) ADD_SUBDIRECTORY( ${SUBDIR_SRC} ) IF(DEFINED TEST) MESSAGE("-- Building with tests: ${TEST}") ADD_SUBDIRECTORY( spec/capi ) ENDIF() # ------------------------------------------------ # uninstall target # ------------------------------------------------ configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY ) ADD_CUSTOM_TARGET(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) set(PROJECT_VERSION ${GLYR_VERSION_MAJOR}.${GLYR_VERSION_MINOR}.${GLYR_VERSION_MICRO}) set(ARCHIVE_NAME ${CMAKE_PROJECT_NAME}-${PROJECT_VERSION}) ADD_CUSTOM_TARGET(dist COMMAND git archive --prefix=${CMAKE_PROJECT_NAME}/ HEAD | bzip2 > ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.bz2 WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) glyr-1.0.10/COPYING000066400000000000000000000167431301162614000136060ustar00rootroot00000000000000 GNU LESSER 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. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser 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 Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. glyr-1.0.10/README.textile000066400000000000000000000203561301162614000151030ustar00rootroot00000000000000h1. Glyr is a search engine for music related metadata The finest search you can buy for no money ^TM^ !https://github.com/sahib/glyr/blob/master/doc/screenshot.png([screenshot])! It comes both in a command-line interface tool and as a C library, both with an easy to use interface. The sort of metadata glyr is searching (and downloading) is usually the data you see in your musicplayer. And indeed, originally it was written to serve as internally library for a musicplayer, but has been extended to work as a standalone program which is able to download:

GetterDescription
coverCoverart (front images supported only)
lyricsSongtext
artistphotosPhotos of a certain band (press or live)
artistbioArtist biography from various sites
reviewalbum reviews from various sites
albumlistA list of albums from a specific artist.
tagsTags, either related to artist, album or title
relationslinks to wikipedia, myspace, musicbrainz ...
similarartistssimilar artists
similarsongssimilar songs
tracklisttracklists of an album
guitartabsguitartabs in textual form
backdrops Large artist photos, suitable for backgrounds

*Terminology*: To prevent disambiguation the following terms are used below: * _glyrc_ is the command-line interface to libglyr. * _libglyr_ is the library behind, which may be used by your program. * a _getter_ is a type of metadata to download, e.g. @cover@, sometimes also called _fetcher_ * a _provider_ is a source libglyr queries in order to find the data. * _glyros_ is the ruby wrapper for this lib. Currently it is not maintained. h2. FEATURES * Many built-in providers (46 at time of writing, ~30 distinct sites), high success-rate (the longer the search, the higher the risk :)) * Portable: Windows and Linux are supported (Developement on Linux) see ~[1]~ for Mac OSX, * Fuzzy matching: Search providers with Levenshtein algorithm to eliminate typos and enhance search results. * Decent Unicode support: All sort of valid UTF8 is taken, and UTf8 output can be forced. * Fast Download: libcurl is used internally, and sources are searched in parallel, unneeded data is not downloaded if possible. * Lightweight dependencies: libcurl, glib and sqlite (for caching) - typical linux systems have those installed. * Download of a user defined amount of items, @glyrc cover -a Foo -b Bar -n 30@ tries to load 30 covers of album 'Bar' by artist 'Foo'. * Grouped download: Query providers by descending Accuray / Speed; controllable by the user over the @qsratio@ * Optional download of images, URL is returned otherwise - so it can act a bit like a search-engine. * Free Software licensed under the terms of the LGPLv3 * libglyr is portable, easy, threadsafe and lowlevel enough for everyone. * ...lots of other options like min/max size for images. * A built-in cache to store the metadata (using SQLite) h2. GETTING STARTED h3. Compiling See the "compile page":https://github.com/sahib/glyr/wiki/Compiling. h3. libglyr: See the "examples in src/examples":https://github.com/sahib/glyr/wiki for a quickstart. There's also a more "gentle (and brief) introduction.":https://github.com/sahib/glyr/wiki/Introduction-to-libglyr Also see the "API Reference":http://sahib.github.com/glyr/doc/html/index.html *Please note:* Since version 1.0.0 the API will be stable, and will only be open for extensions. h3. glyrc: Please refer to @glyrc -h@ which gives you a brief introduction to the arguments you can pass, See the "wiki":https://github.com/sahib/glyr/wiki/Commandline-arguments for more detailed information about the options. h2. FAQ h3. Anyone using it already? * "GMPC":http://gmpc.wikia.com/wiki/Gnome_Music_Player_Client * "Pragha":http://pragha.wikispaces.com/ * "Freya":https://github.com/studentkittens/Freya * "lyvi":http://ok100.github.com/lyvi/ h3. Glyr.. such a silly name! Why? Indeed. Should have named it 'Glyros' (too late, damnit). h3. Is it hard to write something with it? Some knowledge of C might be required, but the code is straight forward most of the time. Bash scripts are pretty straight-forward: @glyrc cover --artist Equilibrium --album Sagas --write '/tmp/:artist:_:album:.:format:' --callback 'sxiv ":path:"'@ Additionally there are bindings to Python and Ruby: * "plyr":https://github.com/sahib/python-glyr by me (complete API). * "ruby-glyr":https://github.com/meh/ruby-glyr by meh (complete API). h3. How..how am I supposed to use it? Well, as normal user you could use @glyrc@ to retrieve some metadata in a batchlike fashion. As developers you have lots of option, you could write glyr-plugins for musicplayers (gmpc!), set up a webserver with this, hack up little scripts showing covers on the desktop.. everything related to musicmetadat is possible here. You could also extend libglyr itself but that's a little harder. h3. Isn't there musicbrainz already? # The kind of metadata downloaded by glyr differs heavily from musicbrainz. i.e. glyr finds metadata for musicplayers while musicbrainz finds data for CD Rippers and taggers. # Musicbrainz runs remotely on some server and uses a large database which is searched through lucene. Glyr is a client program which hops over several sites and does not cache anything, unless you do it yourself. You could of course let glyr run on a (remote) server, and cache all results to do something similiar. # There is "coverarchive.org":www.coverartarchive.org , but it does not seem to fully work yet. But it will be accessible once it does. h2. AUTHOR See the AUTHORS file that comes in glyr's distribution. See also COPYING to know about your rights. h2. I CAN HAZ HELP? h3. BUGS If you found one: _Meh. Sorry for that._ If you file a bugreport: _Hey, thank you!_ Use the "Issue Tracker":https://github.com/sahib/glyr/issues to share your find. Alternatively you may drop me a mail at h3. PATCHES If you hacked one: Excellent! Send it to me via mail or see below. If you want to add new providers you should look at the existing provider plugins to get an idea how to write one. Starting with lib/cover/lastfm.c isn't a bad idea either, because it is one of the very simple ones. __Take the usual Git(Hub) approach:__ # Fork this project # Make your changes # Make a Pull request If you're not familiar with git, or just don't like it, you can also send me the patch via mail: Thanks for any help in advance! h3. WRITE SOFTWARE THAT USES GLYR May sound strange, but you're giving me a reason to maintain it, write new providers etc. Also bugreports (well, there are no bugs, just in case) are appreciated, or just questions. Those help to make the documentation more clear. h3. DONATE You also might consider a small (CS-Students are already motivated by 1 Cent ) donation if you use feel like it: (For now only possible via Flattr or "Paypal":http://sahib.github.com/donate.html , you gonna need an account there - Sorry) h2. DISCLAIMER As usual, no warranty is granted that this software works like expected. Refer to the LGPLv3 copy you got with libglyr. It is "here.":https://github.com/sahib/glyr/blob/master/COPYING Glyr is just a way to find the data, it does not own any rights on the data it found. **All retrieved items are copyrighted by their respective copyright owners.** Refer to the provider's terms of use. Every item you get from libglyr contains the name and a url to the provider, so lookup there terms of use there.

h3. [1] Glyr has not been tested on Mac OSX yet. If you own a Mac, any help with 'porting' glyr is highly appreciated. My guess would be that no to almost no changes need to be done though. glyr-1.0.10/cmake_uninstall.cmake.in000066400000000000000000000020211301162614000173130ustar00rootroot00000000000000cmake_policy(SET CMP0007 OLD) if (NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") message(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) string(REGEX REPLACE "\n" ";" files "${files}") list(REVERSE files) foreach (file ${files}) message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") if (EXISTS "$ENV{DESTDIR}${file}") execute_process( COMMAND @CMAKE_COMMAND@ -E remove "$ENV{DESTDIR}${file}" OUTPUT_VARIABLE rm_out RESULT_VARIABLE rm_retval ) if(NOT ${rm_retval} EQUAL 0) message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") endif (NOT ${rm_retval} EQUAL 0) else (EXISTS "$ENV{DESTDIR}${file}") message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") endif (EXISTS "$ENV{DESTDIR}${file}") endforeach(file) glyr-1.0.10/doc/000077500000000000000000000000001301162614000133055ustar00rootroot00000000000000glyr-1.0.10/doc/README.textile000066400000000000000000000002171301162614000156420ustar00rootroot00000000000000h3. THE DOCUMENTATION h6. Is not here. It can be found online at "www.sahib.github.com/glyr":http://sahib.github.com/glyr/doc/html/index.html glyr-1.0.10/doc/build_doc.rb000066400000000000000000000060271301162614000155630ustar00rootroot00000000000000################################################################# # This file is part of glyr # + a commanndline tool and library to download various sort of musicrelated metadata. # + Copyright (C) [2011-2016] [Christopher Pahl] # + Hosted at: https://github.com/sahib/glyr # # glyr is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # glyr 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with glyr. If not, see . ################################################################# #!/usr/bin/ruby # A simple ruby script wrapping around gtk-doc; # Normally gtk-doc requires autoconf, which I dont want to use. # It's a pain in the ass. MODULE="libglyr" DIR =File.expand_path("../lib") BUILD_HEADERS = [ "#{DIR}/glyr.h", "#{DIR}/types.h", "#{DIR}/cache.h", "#{DIR}/misc.h", "#{DIR}/config.h", "#{DIR}/testing.h", ] DOC_DIR = "#{File.expand_path(Dir.getwd)}/doc" BLD_DIR = "#{File.expand_path(Dir.getwd)}/build" INC_DIR = "#{File.expand_path(Dir.getwd)}/include" if Dir.exists? DOC_DIR puts "-- Cleaning up.." system("rm -rf DOC_DIR") system("rm -rf BLD_DIR") end def copy_doc_files begin Dir.mkdir "build" rescue;end BUILD_HEADERS.each do |file| unless system("cp #{file} build/") then puts "Unable to copy #{file}!" exit -1 end end end def copy_includes if Dir.exists? INC_DIR Dir.open(INC_DIR).each do |filename| inc_filename = File.expand_path("include/%s" % filename) if File.file? inc_filename then system("cp #{inc_filename} doc/html") puts " - copy: #{inc_filename}" end end end end def create_dir_and_cd dirname begin Dir.mkdir dirname rescue;end Dir.chdir dirname end def do_or_die is_cool puts is_cool exit unless system is_cool end ############################ copy_doc_files() puts "-- Processing file #{BLD_DIR}" create_dir_and_cd "doc" puts "-- Scanning directory" do_or_die("gtkdoc-scan --module='#{MODULE}' --source-dir '../build'") puts " - done" puts "-- Setting up DB" do_or_die("gtkdoc-mkdb --module=#{MODULE} --main-sgml-file=#{MODULE}.sgml --source-dir='../build' --xml-mode --output-format=xml") puts " - done" create_dir_and_cd "html" puts "-- Generating HTML" do_or_die("gtkdoc-mkhtml --path='../../build' libglyr ../#{MODULE}.sgml 2> /dev/null"); puts " - done" puts "-- Fixing crosslinks" Dir.chdir("..") do_or_die("gtkdoc-fixxref --module=#{MODULE} --module-dir=html --html-dir=html") puts " - done" Dir.chdir("..") puts "-- Copying include files." copy_includes() if Dir.exists? "../child/glyr/doc" puts "-- Copying to repo" system("cp -r doc/html ../child/glyr/doc") end glyr-1.0.10/doc/include/000077500000000000000000000000001301162614000147305ustar00rootroot00000000000000glyr-1.0.10/doc/include/index.html000066400000000000000000000043171301162614000167320ustar00rootroot00000000000000 libglyr Reference Manual
glyr-1.0.10/doc/screenshot.png000066400000000000000000006105231301162614000161770ustar00rootroot00000000000000PNG  IHDR98sBITO IDATx^`EǷ]N @B:* QEAE){轄!{%!K1(~3߷8IЂ " "N "O`ui jhJmrœd Wm^ʬ&73A|5us "jvhW~ބ"͍6T֒ku2X Еʫй~ }+W߀.v(g30Q'p[[E?_wMSSTsCQ/?4?DgnsKӌ`O.;hġkmsvիf|,ϣ?6!VPo|[)pg_l|;#n{Wg?&kSC\͞a7/W5y>.ٶ<+L4p=F8,:-ؐe+og̥O-#e.^P\Sa7.Y?٬S}~Yl c~bۅV*/~;l5e[W͜Yq>ݱ?Q FԹ-3u@͏aB>9[o}-_Bo0 3G%s/;2f4^5p /}-yƋ=ס^LJ;mf;zǕ3#OҊMqjDZRx@_˭ qӃc$4\[&/$l_oj[}:w"iCW:髣N4lH&ޘy?[Y"wo4]^zf̍QVd0xjOSf._1&vق}͉|\|߸i xkBijea *B*߅'? _[r 7Un'w?B<ܡa~2.x#w݃~G-p'])ؽ3TV[q>QQ^Nm~ l֬8`]Kf>GeDJ!du\d-5Y1Eʅ^oq~3jFbbi,Ei+w~-8cn]\eB@+%.,ȐJlȨ/ݥ~J׮&-:hRZ&d*o l1s6qP>y#֧o NWOy'HVZD*8J[-ccKKHIȋoԨsuhz 6)ecm?w,Bg>\=#O*aĈ trm1@f?u(&xx ϚY,Q;8e`:߃bT`r[ cDyvE(,bx?~zJP}, G^|JλtY >]KsnxXɑA~ӍY|=7f$].rᄾSW6p+l,"BQ;G85|.+/U.0N`kTcRڤ<[;~ !Br"[OXrdcڙ_C-|F7#_Bydfw1uD؎vMV*CQ9Z;ivNY&i'/ iR|W i;_ɶ7wߕ yYر5w?xy}6lnQFU4O}bљ%ǽ\tFΐ qT%DuI+o+y[p[:j%&Ժ ?_76 f_|}A+Ykypd -:ݪn>^Obf [A\BBЕ[%|+gZfw1Y|yra7iNMHtZouo|<19m+sqeZƱsާjfeyw'ȹǞfbحNH))kN1=K| ۸hc˂SqBJҏ%aCuǿC!{}mAH w;s͊\l1 NR<K~x;׎#Z~ː呡u\%bR Kfz d3 9[*:hieZ")$/f[0&G.*tڇmĦV3YEݥ X9kEwVZ]y)(c~kٷG+PUчӽկGYro4P Bl祫Rn=86{t72}'11U%"ׁ8)8ө`I3g"nd5̶3߽Q84DuXȻy&o` )oB\'ϻZɅrkNSdUnjy.; 711\B x)e$.8)IJi0uCҹ/f~t[iݮǥJ2< pKx^J' \Zr6'ٴ4(}"\pn$.mڼY?X 7gNߒoyZ5ٿ]޽5_vMW5. !WrUIۥf[\=WFJhR?o4anW={\;29LP;SK+S);7'.Za]YQJOrmM>ޱx%3CPVVBU\b5ZVl%l1PS uVJ#^;kXv#e!^ ͈B uO'k~$e1Pv\2ŪtJl.g$Pa}\$}ŸlŧG qJ1zwχHDoqR3_n|ǹEʏA;\eߥgn'Ho!hc+C EpWή#\rȬUZI=J.ՅmiƠbk1U|UiE߅S&bY®D-P('&9#Vأ.gkx:ߢ~B0r~ A*jx >;y?O ^En/԰rs3W.tn! уlËK{x(d_B\}b~<: dK첻;iY㖔wDM[v&@wmG{: yMg[|E^ଐbU^Й$1&=(,#pM(EX,៉ G h0.gƷCm"ᜋx-~hWlKҒ1|DFOʄw;׮yrUYM@9iÐޖǐYBq1O^k00":VL8:ơY_*,|*K@q"hGw rӈ2wBPmR=ZZc>}_\oP)n"3/+Ot2xz Q%BU!^_Y81&mιl{>1+>M²<2 +IOxx`(XlD)%LЎ9?aU-/ 7Ňgw 5I??g䭲oKqoT4s<XܿDvݗR 3n?),$Pnn/lZ`xrLH׬ae=Wq u@qj3M:Jg{ la5Vg\ 5-ЮPuK@0)D9Ο(?=r1Z߰`J"~mCsy >O#83ZG߫fsRb\>q[GozʦI:I|7n^"jխ6չ{|6!č[l,AV^]?;tq? @h̆|Sť=;ms+b[-c?mB۽ q&7?pIp Ǖu׍lA-7ZtLVblC.\+iBq1o\2'^F]FmhUǃ8Q|9TU?/;ܯ:Ig׳ c.n]Dy?0j=L*܍Y&ͻr7%웮k޴)"ln mJ>^HxsOs-g{SzkƙSgDrS\N^􂸣I|VVYwn@#r~)r3!O8%੩*/U9*7_ B2CXT97qU\h/OiD/וi~q/&x|ָ~'ֻ;M .3mO?#NJc,)4n쿖Gq%ԶPB-&*bb֘ kumQa.:uζgygT4Nj7GDw[C!Cu~fƋw6{i;kE~U#3ڪ4`J'*'s{Ws !GY`Ö[Pm)>w@6j[P8PuDK؄Oe </+_AT o<ͤP" " rsFQ" " %HZS:V{yr{wD@D@^H 223}Y_ΔKb2T+5z;W_RM WDD@Tu[O$U*8wU/c$d!;ͯ}"DD@ t֞n1vУW3Fڌ%}K~UHS ov]_" "!P K<4O֞18|{҆S%G vOqi?lJqG:Xf j5sDb[X!foWgr~A+x:o-ݙ *CNü۬O:կ۰vOLҎ~~oJtaPiJrʶ} +N0h?xl+Iw9,vCD@DF T3zthK`mn&UϥoLy7D~Ʃ"N?5yeszvm1/E`kTcRڤ<[;( !;/lWb\v|&c匩47ݘGQ»|6 =.M%&zG4tmUÈ3)V?d[I/0e=;)W)Ai IDATuR nBD@D T/}趶q Q͝E|eRC)&M4SkZLLl͞L~Bq.M `&?9)^>Oa7{q}P%"ׁi,IЏcMl|nM"ED@D*Lḷn笽0.kOk`cwc;nLk_=kca5{?\;f}k[טu3 PJKކ{(ld/5[9q|>SeeiޞşjGӳR:۬}:Ԗ1k"$Dϸw.;Y%?׮`P!" oc߄0P GČzec*~{wh/^~>,ùycEpn׏ż05yƵNP$h+J1=rw~>d~Q!;ڬ^Ѩud_Y/Cÿ^Loq\(W'iM'ye֘l1P]SjW^0QOڭ:" " 5CD"kH,brҒI|#Kϟi_5£.Xvr3Ɯ4IR)rw_H+L[~3O^k00":VL]cʯ[_(G jsVpϗqmi MԴeg q}Ð`oKiT؞t.qFAod^| k.VT " D5[plKp3oHXpFڇ{ j>}GOy>hĐuOJ_Jطnb{K;vpȎ?^޿1PYa΃t.ŭav,^stP<`Z愲EZ> ._v-BFç*^^O8^uwa~.4ǧV " +!3WbED@^Ke>nҕ#p24"! c5}V* 9 "TPnV."U\Z|'}oU" kC44k#ȑxeRCc "2O_A "Ngy{20'Y]׫#KOKP[L{j @D@^)OmoN>VOD3m&3G]']D0':"`@3 H*|Igq[l[ .> |JR\È~0[KwaȸIAdИ6K.ś>`HX2eK;~EI6?-q&t^N|mFߐCoQ?Q§OqG:J6TPa#4PGb1άP|\& >Tnmz l!" "; TS>F7X_S챛Z 9xو-)kI,#Ղ$%y?:ikͫuw,ЪI1Z.5>2Ӕp%ޟe{lgoq+:Y="ԟl8#os^>_3eS 8y):j.(1\55f(m#YOf;5ǗMl"P%eB̓uWnݤK{L ©;Jy?kfS6ۏfƅa4~rd1iy!>A"" @DZ(Du7wzy,B*׮isk`aw\}f15bk#[˝zk-֯rfS/|+^;O'[¯wLnRn=86NBfO^~!Yl8Mo X={3k_/ޱ:~?8iJ(¿qQ19rU٤>l$6%S,)wiצ8f37;Yܴv,elͼ3cGeD@D_J=ޞ1DK M벶v8Fx,?f-S,k9g^#|8TZ+%sU"-S[Ƭ2Jd=ޙ#ͭ4J1'-[ۚTء=†6ꕓ`e} eNr\!:7%w (N2߲ev""YiFbG,ة" " ;ֈpؒB2<7V48`ܷ:y/ C5e*:%6 v-3b[!$%fȑv,(3̬|EF rUZy&h6g :ci|s!B3f13T"隈 ''[3{[ؠ͉jjg[Jc!@Q8C^[D!_|o p|$iBv7JD@D PErXDR(xFrїv3?ӾjG])!sť쾔WqIa]zVS3GvNڭ07R> O]-œf:֖swF?/,*ho4FWLo ȥ!5S 3TymܢCgƘC[bώ[Va|#cP%g-m 䏥0(W;cxN-oe\0Ggb[A;bD@DxX֣d ΟMw n]2T!0ីbI8j9v PRVbFyO]?LbS yWmZ[ Ȝ e+<upZ;zÔ]Mws@LCz<[MG)0w"s44l,hۘ@!x<*~K|1wtF(:M Y0UU?h[Gᓷ]HP@D@8Iz@! " $}yli͘ݰk* `N91^h׬Z " "@"gZDD@DBD@D@~HsΨK*>9PS^#;" "Hn1&/1ߡRk(2 " iڣ uOC#" "*d@D@DjX\^U}* J!aɔ.BayI6?-~.¤RP*̺5꛽ڍu[{ȘM0Y2o0CƵO&Nr %ƴYr)Z:yͿm#N-9UaD=Uhל/ @D@(Fa{sўS.»|VsikԜw,}~ûN?Ojf޾M̬O1lRҍu4R3dݝ2y%pYҒOTU݊BD@DaJZ;K)BRu:QH(¿qQ(Ȯ"von#+\KE0쨢o X={3k_/ޱ:~?$`_ȎU@k:60ĸ:iiϟ2?:{lA+THM٩ D@D@NͤuD@DP&PuDI0e9 #" "  jZ " "O@"DD@DHN ̪Zu<}N2M" " @TCÖp\dz[Sמi8V8 " "P!jIK O8Ag/7& h3nZ Q;?N?0Cד&: %[B>~o?2}2qCP(4͒KWVpS07+:i[:n0fT `%]8zv=&b}*:sYuW\aԮ).AwܑD@D@xTDnxEJ9s1ǫc/ = ^{io??`6Ю&fpR6 ur]b\j|fҫŅ6笧}ٝK~g݆];5vi_Ϣ1µM:?u(&Cu̓uWnݤK{"YQ" "~jRb"!k8rl݉vKc&RHٸgPŹ/%0*𶜀s7u3+|㟜ٔK/vɑˮ&-a[o/%,墨T+gumbP ;/rL@.ɾ>Z΀ ܥRLb+g_|e@D@{8'O k/w9ezh?WC"/-T{C{[ i9Ӡɯntf8TZ;+ܔ )&O,*+Kt "Wpu xMN4uP *AD@D"PwkD8ZV\|f^[Oz ,ɣ H(lH\dt~`fo+Jh4Z pT6u-0ԮlQJOrm:Ig 2%;8|fco_["D/ӱ/+(W8VhX@=*O "Z8FJo,/^`eM|͗+oP')>0*iwմ<gGC(rcLu`q8Q6fk`-oLu(/v_/^jw`q &/kPFn 7!|oWmbXd5ܲBfJ,Q4h7~)En9XoK-ʓ#73(qghϒ-"kH,b$Iyߩek=h7+doxWNm|(*PqIRcnb܃f? zo}N\(+8jV_O72u^rz&jڲ3Qk>SaH796KoyBL'.FkDx# p,α$x< bZ%ۂ~3^pPd)á2kM62(d&&x'Lޖoλ˯[ALVbGU R XUB6-2ŖyL["X7[x&@xm[&|2V*h6c>V]˯eoO 0s}k "R'ZJ8M1 9"/4X// Yo6?e~<?|(@L^rh1U2m/lrB,xUQ:Gg/i~m#@9t2sZp sݾ54lcQu>qW\|頨t| ӧNOCf^Vrtl!kڿXbV{g ۮגCEDx &NX &﯁bjL[$dzp\'09$9H7l J.`{P8a>+.rRY,E[q>o6HMÆA@@+ei mg|Xb h:]\nAVB8tHP[NC.3 T 'Y\] Yeh4K M@ޚ )xQ!o jC% Q)o 6%ya 6M Ƃ4 8Ob"7Ɖq1N,βF8H?-5j! 8t- I:hB^ւCI{%[Edcq'|ぷBYЄ@5 L' C2f((0*w=O[qFTms rq߯&jHŅ7n'#" cKi$` T=8.+Y.o\jRǤ5R .PoTsTn]["[,d | W@*;hh B#$P,P0KP LQĂw1Qω8ZU hHB )~ 4Km0x0 g8c%I\jXJfc4:F0NI4I$j0:=dT1`򤤘UL C8=TI7|JLP b4FA2z#~Ds@ G6=zU`b7 8bfh8ѼoJDJtP])WSl.-aGHMT%`[ a`Wkx# | Һ,@&QHI4fd Aj KWU;F0ҋ 6OF`TI0 x@`"( zA\0vH^&4ky>2'pS4̀Qmʈ~A<JTkpl_N̨W33\̺y7{RZ53" " 8$9:]HR':5-0Srw)%5ϘKzu}P]:Mp IDAT8rƦK^sj/ץ8 C5@M=e䵀ȐRФvSH +V~9I0I< 1ܓh e0\H5螁)<r%NҦfgdcbp$z@zEJ05πED` b!Ϗ2u4]s1ipZHA j@w-eMSV@Cr:\DK3defڶ*2# EC9Z3e:PSR/*.;d96kh/sZ^4-)UkcĒ+ҋ@~.aRJp; $(Aqaìd699O?$IJ\0"7H= -Qp PL0ؚρS;rE'UXX$/0œ$yM{\#8 c҂<3bH4KMfR7" " ͤ>l ^)ĔMflD&5iҨI@AanvNիע=ej"78HwЀ\԰oLb속n\ >?d0Rb$-1H1>Jۢ%r Waz ] S2YT4')i/\6fҳg XE2D߹{6N9yYQ=Fo !$'XU.eY2'Lz0=O]%&OIUyݾ/Ax2Y2^b"q#ym }zL_ز$Afx'N8Yǔ %!"SSsg%ek3THaIEwJ_& !md $wϾF`cVNf~QTy?yIlb3@gÞo -=[ے$TF.@ &ЦI[̤"jvBD@7 "EWx%$P\S= ttSJ N814 N<^o= q8:Dj K> NmY}"#2k]=N񣝿HHH1dA p-[uo6:Ϧd]0(ehAzڜp%S =03KK , B|"|lX RZG_xsU֪_a<]ݯ\vƕĴ@qAf̚> hm{7/w``*mN,Tܟ9K/5Ip#;w=vQFNdck4D"YfnwmԱy;B tꌬ׮_;w\xΑUflup?nfS2{&Nkըs۵QZ`#Q@9;P"9شkQVafg 6&@'^qRR)75Kw+6Z,|a1PR4wMct~Աm`Z 3e.l-a`p:K!Ign\$U  F"4&&͜遣ϑ#ooD$QIsw~tJB޼{;ᓷzqsp,Ъ:W7 fuc2eu.␞~٧O#ݖ[op~IbHçӲ3m߾agg7ONL"q#e%` `$`Yf_OD{E!"T sR9,8iʷ:t7lĦ^&@Ig /]jqcρCJ+ZneFP^HJ"0lJZ~᧜skau_v}vmEywi̓iunwu=&d'>,yPNmQ"!2cUk>7IWZ ;Ȳ}?z,3=3;;3&lLޚ>eS=L4g~>IB_,! H.6k蠰辁;89˵H:d3#ܨîɻѶsC=\I$: (u^Q3hؐa7 b q,7?/#3 ϔ.lacq\k'MA(d)op^JO$ډ)*+33;+K,wy}<T. я;~5iiwD Ra)@/v Vg͔N !#~ Ŕww{[{66g "Dv2ё6t-Zҩ$+7; o}NX{[o=aH3N L8 qTu$:bW:^mk/= a`Ν:t$CGU]H'WFxمw/,(Hj6jUkA)V*rԪM~䪺tڥ}ܼ;v=1h+fL#b8M:\rEj Iqm "I } gDߠm ?4tٳ?=?-o6^琮n6ޥbq#F@* h$"+k~"a]W:5`} 5&1Mcu|tjעudOo;l#F}~$?gNdM/VXKztn+c =xD,o {=(qCbGL4e`S RZؑ۵iֲRKH1fqZ z:㯫s"meq dgCچ"?q|ӻwjT˨X0O>zYU9y@%b_oOmX~nwzwѠ\X+?nɘ6\.ɓ'f0/ y2LR0`W?*[gDRy`:}\{1h޶x p6и8`"NgsLV SYƞzu$x&ϧA ~.ۚ~o :Gef- ^B` CcuZAS櫥>|ttIƃ UAFVȔ:U0s$3# '4JzҌ9s>UHg~7#v/?ү y;uԬqbZOKkhG٨quҭuGxC`PRˉc|~X]HnzƔ Y3nb%R::e9rSoSb՘D(E"  kp'vASNڵzO…+'لF*[놬sXo*!BՂL${=n%2>7|v{y(_w;/'7PQ[Zn=z `` m)SLoÇ'eŊ^`%YF~9AܺxM??|a#\e0&u…&N0}2謆:D:❡ nCmԡaX1T 0qM2r3ܼܽzu|~Ge/FH3ƒVH4dW93 5P0 %7h8waQOK|qe_αU`!]؊rm3G,|\&uw0_'_re9}y}ӤqJSp&,.$2eZ7fܻ:?q)8".¥4g I C} A[:CPG|Eyuܕh >X0x@F/р&T[pҌtZs{O(wip  0_>)evŞ p}°MR |\" "F@*ry9 $TQ5EQLYAPP" 9!ar+^5*Өv1Lz]w;xHOP !hm̺R*voaimm(I"h<{1gɗKfZJHA^% Llt9Xk-Zzai_{`i hAy*UtQqJ\)o><VUĀ(v!dw&5@5tJx:gk.oyT^I?yZCNȊ(iMbo"-)۪>i:v܈EBүV^񂉷 onhߢEK_~v2LQfD+iG\Ukɵ*СE)/Oݭ'P-ヌV[I316,Z3gO!U.SR5lz!3,bdN (*-PyNlq=!}$H_O卆~`ː(Sd:NCΙX^]ݩ">[$CH0 je9"X b"JnlʤJ"Q~?$Uf8 ` yD:Sr1~ܵS|.f9vB,CHKq HEQ`nTfw]rKI(r- тM֝R}tOh4ei)q܀UV%GKtԯ^1VvhVuI\:B%p@@_`:Upo|}[n픋]E!W:C{k0Ls۷l3ZQn(ы S_yyuC^ymjqelF23* zsHn ڑ(WE 6I2vAC# U^< (E5 &;}BIȣ^^C8*k<ɦy!5ѐ""`x{ Og ZT@JJKЮ=Gk7}wvßܮ.sΝQjY~Q$X|ן傱' |~ ]>J?~Is\PYАOcXҧkwNj_~tI.+Z4#  (P$]%jG ʋ/uo9DOR?ʪkOTiZiYY͛ؓ}Xga/I#LYWSѥ˖=Ec$ .c($k7U1֖b0 ޝ/Dۢ\.xߙ)t,hDfّ?$-4!J [N:γRo ?]n𜰵0?1}tPPMb;zrB }&S@r'd80h`se#A̡yg+S39PM# pv':Y 332eQ޹cO$3kdl#^!QTQq߬m4(&{G-\Fɟ}*PAψV/}ȑD@E\8hӼU˂|4chk볏713{ttSӡ2R fU؁Ρ-:bgĽC }# )rU D,t@yXkvtdBw.9+3P(fTL V0'||Q>j'<EC$%tvDGĦGbPz6M;m=[e@rBRzu^gO#qiz N3U{ )M5ٺFm WvJSTV߬  aWKc⢢siiNM.#P93N۶-ᣉ2|tiA$,0 e*Z]P.؉ڶj.GG9;v<ۚL|!Cc؆7{fc{=3=ɕΑ;#❆Ryt < h3x.MKff4Jtm{BV r $A+-xnƓ;JHH."(2'AKKMarUm~}Ҙ 0+,͒!9u|[%NhR|TbOm|Aĉ4@EWVlٰD#\Im%^W(DζVmRyst_Hpd֦1 tCqdA"YYviAb2 r5)Kfr"Ă V ^ dE:b"X?':$9 ƈg'|eua|QT5_)z?):h⸉F|\DHES,B/.c&0qK*@: `lX;l0( >⛵-(1b8v{T/Pf+9}w>9&W,"VFK7  $ʚݧNAD/ "UjfffӉ06sr.Qf [*Dce5؈F!g8Fq) $4) gg~Z\jAIỳg<:f| KA4VǩDJjaa!h"*~M>43VƔ;ӛe)ȩX4$枘_3q/]{7=8m[6oKϾ邅.Esi%c:}TJZ$/z Ͻ9H{g&|_?' ?Zqd%f <W$r&b"X:a 1T^inK)I[pǽ]3+l1e zK]2jEH?!'^M%&cG㸪>:ej3?Pd<*RlުbS Xe0ZS*ⳘֱiG GUPM71='h ϻO,\/OSqI>lIH`')ԧa7.6d2j$#!u&Y Rs^vݚG?l{G= ;|qI"$v}:(d7k=U@.N_bϝw!FA.hd8~硓@sFڮ֓ZiӶSaFPQSS]8euEF&:n:c%,@?N(?O҃dVd?q<[fJZX`5 MTf׾}kȵFm}EouRaY䃛}w:->#H`TN81Q'P,x`_qr_Tob6~vԏjYcu¤g\Jf͸v? o+ D,@M 1x1?ϴWMy# L4~¸sgN}E^PXX-NR>QQ tp 24Sz]<̻Ͷ:ў5k֌}NON{mST:j8tv>eI+ p \ƨۑ`%NәjB0dBB7S)Yt渋TpmJ.*XLeK7Yxw 2/1ܱslfoT*[Ik=w2۳?gӮSQr|_^ `ڀ^<,X1ta7۸jͪ[F,c FcO=vxFG !lƃ{ɉnQ"Lҩ3Z5V+iڱbJ~d{6 H;-@أsF_<}qUxITKrQ0WqP8PJ*Zˠ*QO }!jИE||. nhr5-k##?IxdW,^֫g>ȴjN_t,H6L~:8Sqq{- 6==>NjZAI;3<~EZZRS/4De!: SC+կ@ D,PvژEϚ1k/sh hSٟLzXQ.O{QtW;(Ap8,VIu tռ|0C*W5!}9jZ}>dԬmh] *-_P8HMHoܰCDbfɊe~׀8nc~~S.uu ""@zrZ@#ɹ.݉W $l K`Aj|*8n9+-Zp|^9w4l`#*c}"yj瞘%߭]C[xC}*TTP%TRJE-Ç,]EӪ5:%ȯ*)G9A+8U ǡIOqA::>Г+ a!q ) fg ~6+ot\>{àA^~%sE '.~t|D䑶 o;1gϜ-).ӌ-0ᣢ+`4d`UًT5O9Nh$1q/GSiIq%-9%=#{VJUj_c(@~I36 &ܳoޜ'Z  H0Aڈ^h)ҽ3X :[hRZ O~OB}8sk[6Sp¡I3~Erj+RkJ\ D,.E퐽fO, oQij(dH[LfN0i\1YK._Y{pjF wDmZ | Y`zu;ě))N)+,Fj\=3n:M6B#'>lqD=ģb=x%-$>!ii<~T*\n g\g"AaN.*" V ڬ6ͮ5BMWOէg#>{wG˗T%CxL11æ*Ŵ2U޷E ]E Io7Mv}cf2z=Ap%cas g,Qi1hPNgt |1hoJ,,Yj=H۴yxwjvUuPVFXETZKVnOQ/}qQa'|nPQh8uYmLOe,.ƖX T1,:g5ŽĶ84ZGoGmvciE%u"p? Ŧ# j34tGUoi?4_z+JN~~K^P}?.д?ֿl|{ki3j)&r*b;m0pQQ~ч> t|r 2.ыV/umơ4f;Sx=p'K]_$FxVDаf~y'sH59cc,Mn,+nZ07ޞ-fϫS$zbk}r3TEE9`OVӸ%2CD[믌NP-!%⨟ T{5gf8>7qYT7EۤAҘɺQY7kWf@ ѬKㆍ=SjC(08zA bwMgo^[4勯2ͨw<)(yuD?al#o. dtۣ|#y0p2##F`̊#n عMU|fW_ݵ]W,%(,h)}Ѓery.'hdM[45O6*1L䥙+iVRˋW4:ۏ7yd)uQWx3oz?.D.X[ gw'1̀+$cm5I%y9nXɧG-ageS *ydϣ449!I?Ztq%tՙdeIU={La,1IO>:; z-kֻ@q oբ}m9zĈ1ni_u\nAi9tn\DZNoJsM9˯u\B{=O #G o=i@cz?S?ٹw˝]ʻ#ukÕ_[{2~kn)X`3va֞a_+j/*vg&yޡ ΄]aտa"#?`ِ̹Jf{PA<<!db.۶jtZ  Qp D-]r/MCw/:V/okkCkoرGsŧ9Uo\sѥ{ #CgjW8;ZU]L-6 O} 2 ̫Y?r.b:kz|r؅&zxZ#I W@\E|en7%{EɭDL{Xe%q>dks(;lFxŸ9yl9)#QA/)X3oL{s͆]rsKr*NRR&Z*)_(tXf} HRm2Sn$>* 6͛3g*n_QwdH6 C(ڒ9|pNN7n8}g'X64&>}a Fk'$SJ.c&v F}J䦒R@`4ʋ:(G%12zKWwmxm)V (ԐQFZl"Tޝx6hƛCX-V ͓Zh+kNhjFsj>])Kݍz_/XՏp=Ǿ]P|~}^!&{˼WƘ I<+vjg7+9 뗢WͶĦQ m6͌W_dI1l޽})AKUn79wC3-<$n$ͯO8{;_kOrҝROwa#l۾ܑPWi?kOioCF~wQN-BR˕/+?\{vzTc pu;\iO*p _2LtZ\Ҹ;Sk~<\O_ύ =;\iș Ã%BPl(*+, Bdsqy],aID9t~IOԙ(c^|dJJN#!a#XWi#vV(vֳmsMod¾bAa 1-cLHF=O&eƒ93B{zfN׈5$`fZ?L;Om{++k,He6n R^hHC?BP |gl.}>ͶcϞ&<}"yzȲO`&&Ξbt{]UUQ8ŅŅt}cT/"gsV|ҕ~i[x-wJC va@2VmZI xdMMyW ~o_ZhN^~i/Y ]fum8lFEsZ96d +^8sl!{.83t@@ݚ1_AxP2,4aԝ ]2h9:-M, x o0KEl;:\p(b * HRcK_( k Ql}ytkqR}m,qI Q17Z,rOAZC,FA X,ixzPs(/AaT. PŅ〶"Dx?pu[R[ UFڤg:j4rVÞ[3tI]oO+).2K5j׎vWTT83YM=+Yn_*q&Op} {Nɕ%t\T+v{Q;=ee>kfrKMW\Raۯ\?ȈՔ^Na= F>R?|>+*vs!LiΔhnj*e+$r&b!HO1T.ŃZ O/Z"V?I&lֆ+ 蛦NW\x r~0֬O81gFg6*Sl?S(rP& TImEo^oڙ8oC䥭qfOq?ؙXL)<[%{k;O=⩢g Nп(?l”=YJ׾3],o1fzp W+돖0+՟ep=XϷ (W j[!N?tc:mm<%INNNnkk q3IF1?u޽{ dE"1b^oM=畏7FwVl}"7ly74YzE^ZY.~Xbks?k"}(% ٳ[O=so%ȥNt\X+:``H(5|{E}p_Ey3:++̰I tuUd iyZ@5Iv?z>7?CNPA7+g5 ^1:777`: %ˇ\7iT^C۶} :Yu2S˪wCk[%%@tC7AA-KtC55XKCnulwXdY܋R-3eV*{kzaBҬ@@jְ=*\q7 6U+c i\^0P1ؤavotr\.tl _+̴`TRrͣnݾ7A4h؞5ߪ)Ԅ@ToŠX%`SBhNEȱ)6sKߟ1='Ou׆-kTăyEZT#|ڻ]-؟uvEą}qN],%#x^[c[NB^}d 9m@p`-׬[;wۇgl-X!/ Ԭ^}QuGKxط&5q 8*Q<< agj#Ћ35_-U 䲭5ChUXٺgk6yP{jPyW ];2nqw; 6Igs1eP#jw(U񓗪Zu u5IȈ|鸲BZGz?2B)F.1+Z>uu/+_@0 wrjG$ (r@].j*U!fq1JK3'UM `$GGQ`lRMKWƂoߥ^ sl"9lf(fs

bg~8򢳿1y%\{rV\m݂\vmw869E/;eN*`u [6_k+pHe1Zu=u]tH?wq½6}Za3Ko6g朔Ģsi^1ۄx] 7p4{a.o#Im7…+> sop׋of/_d@"DAݵW/V?͙/ʦN!];Y0WGPXDn6W>3;wld5kңe&bb:}ϖ>;3}5wD1_e_2FŊT0aSolksFg^\W=j7aկIbŌ~w>7&HakʹS'X ?Ř~67r|<Acj8ZFجnd{w8v%g#=ÝKggo^?╇c_;깹mP~~PkӬI̷i-O Zמpk)W.妁їځj4o*CkWZmňL HR1j~cۇFϝ_\#*ɈY~Z=Bᜂ7<~S&`~ycg1co5ɱS'M䀧^ZAw*OYΉ|{z^=V,4Oo)T5q,yM4`蜷gל$'n6lq[FxC>RR4:??^<_659À`-~q{0=!YY~9;z,6Ue ޺R3ygvo@#p -;ӯ3_YU{1QN{vSUXy/b_۱0@U( OY8l1>$Ỉ3UHt0+0 f933Jv}d?F~7@-'FCwϟib@]µ+W=kvbu,{4SPGxA =tp][w}FM7l%E]@o(bGNXn}Å]tƌxٛ g( r-}^**+z:7|Պ6b^Og6|۴n ]֙_|ԅU 4E.||y%B& ?|`Ξ>9~ˆ:v㖓aԩ42`'@!=EvIBb5ح۶9RVBE@\!R9Bf5K[;QXuJT +ʶ@ č{/B9"IӨa %kSC$E+7Rqzb)B iiۨ~ץImǶ&Pا 2p[8= 䩓kGǘx{?(}<ԽkC$j%DqD׭ߤii{}WA&f9: ,VYK}yì =Znioզu dԤ ݥP/< pA_ǚJolQQ4l J n' #z pZm_lȡ*XR鮙ܤ;-[T 2"%)<'n,+22!4sh`uH~Ō/;[<䴴f~Ԧ~ӊ@uy9lu8bJEE%7mZpɜj1`4@|WJߒ#/5eH*)K*w-|>pgϜy|9/\q,c ٽ{'z&+هޟW_,;qDWC61H:?VH yrKo$Ruopn޿/2#X b8 ! UwKP})e5Ff)Q ~YVZ]0o'HЊβIT0:;s81[^]ztؾN- IDAT}b|l5z’B䩨Q0W-Ya㖶D4Ʉ pbJ%3NU^0@:AuPNѼggSn\>z[^;vJHH7oAQbiɤ v(IDi4xDv:\WhBTm 030 W4'x}'ҾS/.keLTT'Tچ~ft S%E0C3CIYf@ڋEZ[Cn;iƚĄ3)9eu f-*;Q8iML$'&gffb <\Ut6cLtrZnXaB ErPT qeU V+ظX ON^ >|>냏oybaD@MyiS^"ݰ)h{濈ˌ5)N}/q1\!N-t:ءg}nڸ©XW6qJʩ:or g*70 Ia:>\YUbWcJ[T o說~3m{ZulݩYfIXm޺^:}Ob22d8$d*z@ D,AiI%X46ڍ760fE2J ih+s3HN' i%JQi!+ LlqQ5{ULḦrWY V)x\E8k`PLPІQBXTW\CdȽ. ij af| o:Y&t̞;g,Z2ɣLt)WƋ m$m R@pp" ?0K. ݵS -E bš-[ H%rT~_RHfgg޼~Fڴ5yaU eCDKH|L؞ NZgN=:iErt-HPG8\od N䱦ljrVY`F'-BJπ)lZ! Zݖ-$uVw!dqKm1>lwzNaʀJ Xpw>aL+}XQ)g yd͐7OKEA[^zF XwҨLj['a*5-XYZEb1 `xC4AH8X 108Z6p+ydG21`Ёb@cB8gte@K( }GR]^~sȝQf5wL\Zb9IT[I(nńA`7̎z_A3U,F#"9Jʠ!g.qlB2K*I|ńn i]Р8]=% TZÊMqy,b@M0#7Ux9++˿[NMCE[G8J߽|TeT@ z,dr@!ZlBYPcC%Azl:~q!)asxιbGZp]^B{?(?'nڄ ST@A ",x pBqR B _ #'0jMōܸ? 1VT`B> 匲(9*F=pF8 mٵUqC ?]ΎZe~=)tY9sߨj%ނ޸ Ap2Xr6s9 oM:7Tp36A?$ Ľ`s^yȐe…310&8(@9M 4g <)`GDbr: H9muiYatY*EM9?D-zbTtRi}6Vcy,ĸrY@Xsy3gά7uDj5`Hc8jG/>ϕ =Blиv+5L09xZ / 9X5YV~B4B+F|a/no <‚! N$ųXAvC%݇?(}:18@8Ai=mXmpN,bĭBJAxp ^97{y}pB`f|z^~6N!s#VhuZb\W:f8 9ђq\ܔԍmr x=0$DH-,Xfai#p-٬ЃA|]K1?rhpn5>{-Ff04 ˤg)ME2~Ρ9`QN (D8=@hi`,0ˆhe숾W֜a9@<-ͅCp#]ρyT(ȮU`6ih-0{sƊۗ.f'[ʾO%"Źc[ˑ`r'^az-b܋@?:iկtΝmW30)Gr}Ͷ¤TKI?0|#[}BWNߣW^T-v3=9!r0F-rH14D`~ aE ht l&J3Ρn98 *B1J AA{=T&(sZlgi*pB¡iМm AzK]jD:GsOqSn8]C˖ rMLPl}Ӡ],Q,t2wLN&t[lZF[ʎʂ6ſr _G6 *rq WTQKn߼hC#2d9+ iUڵ 8FU\A[jY;wT"%_ ;O/kόeab'DDAN[ 3C ފgz'}.rxEŐx}ʋz Vї3WS-m,/SҜpo>Z^|scpO5_ F0.]O^oŌ80JTK|S!fv0MܮN'vU _+/)#t8^RWRi,sNoť;\^uipM 4 w;]VRm 1KvSbұ` JfʹpKմ_z7c^\ݻ?~_Y h\oTfzXoszcDq?E^Pzk΍'Mv3F.\ZSLo1'%0Ѹ4kʑ-Ͽ_f|GjbV7m27ҊY}#8=H~ÖwQ!W;K}>6Է<5][vf!"Z¯G/ji!'?F,r=?C% %gy.O 0 %;.ZQ=i!rjOKץ,:pS#rTڢ>jl=?YS i-YͶp~,cQeh]>.4dВ WϨJ @s.F*4Dю~?j?[Vp}Ha!mQ_,V|0w۔џW;DW.-Y-׆~]܍ۧ@/˗(Hu$oIv6s+vvIpt~gw4JqǬ:2ꙟM{:{Μޓm# bh3vN{~冠iOMlfr䭅XE;nWf=~şQ )`esks}Ϲ.rI ooȝ.-p/P -wZgĉ_4&_-KRyYz&xL9ot.Ojgv<^ڣhU83;Wv(OY3Eup2>5b˓.5^WܛptG{Y>ԧq )m$~ j!RJ }K^fkyovXf/_-A3kgi7~7k޼뿤Ķ+LE̫NeʥmKo!Jg4/ -_QW{y@7T^щQʳ8?: j͜"6q=0Įrr2gF=O=@qrKjLx~~[NA9>wp0Ax57˙yft'v^2b]C(0e: $rH'':<$rq(Kl9uY.e_ 5Vl^]) YKfŞܯ65F}Gz\ep%I_Zz]QunR$,[HsA r|p7'A\c9ItX͝VshP4q)dCFqK4 NZ8Cn\p/-(tn'*~@6]K(Tq=nѽJgzG>;  ׫%~p~KG/P3ϟA|g>v O`;/s|@]5Ëpe c7 vPb!NDH}uz&\-0,W"WMfZ1^Ob%1޲zmEo0&&<ɰgJsDt"\̏Dk,Z-L(E)IdERD 䕵z>%DnTW >U}DzKS;~0:;AbfBٶ+.}PG{{0Q][tl״VMlp4]*!c~ϋ|k"'X|ٓfm#HG*8n2BYdx;|>^Rf[e RQ/Mn }oA')o仰u&]DyE?ow9ATw RWv>]Un[nFҩ^:ZU`5ys}!ᝫ/e ;h4 j=RVφ AO;p^ot]?IxJ0!,N r hiU1f1=Jwg,5/Y48Y2"Coub.V]?' Ro4M *V[4l($0X"S֨='O:Yjmjch@s~胗BV=;/ ߾v%#%u6J!*bxQ̋byAN-* vZrK'fcoswsq y'ɨzR%_}k6«U$gI)):U$HRl3}ᔓ ;zTF ?O}84<ʐWg~=3#EZ"^^dYPq P7ƸsÕ.v[2u cyQJV 5QՂklk$|喹^_зV #]DQi,VƸfp\= a zsi}p]Lskn¥9q.\'w9uɜ\-:=EGI?șW?|"M HNGTp]xo7HֺV8"#<9$ۍ3y[[Lj<{NXCr 8Z0 \鹬}w| j™Aqq_SӉW:8٧ 7+g|.Np>7UXyR[@;!ի]Tq>4!ߍ{y5e"V5mT*0a=smߵSRw|pcĬ upOtIBjUvFpU rŊdm7nJAR)3'N8iĀ:7WǬncFAGo`F>pP ܴ kЧA-ƻFTZJA3:ۇK?5i_->K~6|@N咯vޡVf({fis 'gR۾eKBZ+~t%u-%-W[޸b 7[vnq٫;cHnxZ /=޽kh9%)MH`ңL&+!] :b:%J S9,V۲SiYEcLW f]#'ntO5&Ҳ:dɻD[&julݽC:b)--{ hdw*mc:pcg/_:08aZS4j- hp.S|MĚ}4l|@,}zKCZxunѤLsnn}6ɽ{gҫI&f@_8r˷/xM=sS[g }*6. ".;Ȧs>uJ-{ivN]3u~ΟY~cOuHE*a!!ƍ?+F) NY,䞍p8$,^&Fϻ<{?!$= x}:{g4k'~2+SS˓矔cPq>=dMxijVC.O1ViË*-j_W|vS5 `Q}Ը*zdGe~˶<=VH+5P ##(Tu=;v`[%cE;){4c6Mf 4ĆvV52Tԯ]{}-CQ&<3, quscPE^Dw0QSY-pyng) N?eE06㒯_^0gM<ܻG $H'T¶6 nǦ7-&?"pm^Ӹdw2fho Ly' sLN5*N>8toSD}k'} s!`GjO nbu5]{=F/J TjRG"Y"Q(_JHHR %3gAw77v]jsO@ 'FuYK4cFhDp84CvRv?awYbx,Ax' *CYMefQWnڰE 6]4e?s,(TbDO{UYg2Qhъe$Z 'yu7NNI`eTG[df eݠAZmt&$EP&"ʷo}y' (t ;*$57` k72errPX baB/fEb&tKlZvaef I$IZ] /'~10EC8kg֗) W/TyY̬3O'UG$nDXQ6?} Uc `f4z.WX8mSUUINx`|U!·b6(JH RhȢ3(Sǎ?1fF\>y=eD|xc^N*)JȦv"qr7Mލ.lڄ6sva.,“Qdi o7v֗}4b1XhZj6%#'Co #|:,3m~ZhW'I ɘFB;筚}U@O$,g/^Qs>`iψ`KLN)l싗AjxVJ 0Y{W c*̉t06bCh#.clr A, IٻC?fJMtܕKjY[slgNLH,ܹ fNt2aj2LHJ3ZDIVaNgDHFL=e /M1&Ï 0S4sYs7˵ y'N1@ʒed,e=΍z<|.1^BXd[E_AҎ: FЄ M3eٳ>[fezf9'HB@լZniѢI^4$u<ώl B"Bf೿&||I}:|裧%gx*c mc@SLF31)pͳ~^]3Vpسm@Li`U{L#q3v#~;ѣH!4 Dq1 EyG"m4oP 1f>Pp3? |HT07P=R959+P2@*5P T<ߔos^6PTYӬ763KthӨVbCd6/Ht&usZtxOb%Y/jH(Cd?m=Bm6O>m{с]ӓTNJv(U+1+ Q/eKwZeC2=aF3/0<8񍄶0W{]e˴oݚC3ԬR_2֒iQv߸i#Pb2i~qldM0ԑɱ16m3}ej^IX|/13u/vQ&&<^{\$lbN a'p=e$XwyrYnk'92ŀe.ֳg齏Xw:/N~ͽ9d]pX>);+@d 6Ae6l E?Z<.O^tw˗?Iʪh>0U|NuqǒR«U =6g߉;|tGcvd`dgT|}/_0WAH#AgۘW_8jusc;0>ǚ9 G箟Yz%SH)̄l({q̃7+/h*YܻOo\c_бc{tvQLnM s}ʌt׍ UFV?Zȁa>4`osvnȲA8)9:szA_Xⱬ!AS}ޭ Z>Z[6\cN6z_xVc75ӯ4>&񟌜9鞹y4>+e׿2}K%k+w~<>_M%U޺ugSfn?^N '?wP*$0~e;x ]X,SF֑s7P-طĴ_.JHKdQV-5x#ВE Ӡ7z՛I(V$gz%9ą UBR֞>[JՕVEEu{K6H 4'y2UoK}RyZbfn~8GN7#lp or*th/8cTսAfLquw6PFŸJQfHY[%1e7¦@i|4m+ ~uX<~e n?jqĪVZ >\T {ױ5=F!!8f{;nLؤDpfGMcvc&~vɚɓ'i|r"(: rP)L:k^-/g2`P0ZpѺVQ-w^ޮn$Lg Â(1۶?&S5MDK|7LZ{1m 0V xb-޼fCbrtŨZ.-o05cܺEf}=aYmL͵%?Yiں&s젞؍=439Ӄ/5-<\گs?3knBipTq=;dS%lr]5Ƈ&4o4ᒫ%d֔7BZ,f[R8TXvo;cpxiMY2uGSij۟ӣ'#B?{T2꺈àыuo:yȉ|݉a8ᛌi͵4{-8בʐ>6?Qb'ڷ0^I~7 i٢[/Օ)Ҧ>֓W6}[w&uwu,o`e_y{ew e}%'h\GhDwͿSLJ-IAUU+(,p0FLTe\I4i:OwRRI5XJ$dl(m cmZoٺըc<$s]i3m jzpwcطkwQ~ۧ ]{p2< |B;j`dgl?Uރ5G84,1{^#O^ ͓0v#wkf?uy/(]AQ!bgY!Z|B9 8Y?H 2B_k+qqy(.Z&C0Tf 5J$!T5b˓.5^DQ ~ pt~g]hyUgm4^;ڽuK?[qǬ:2ꙟMʴcrԿ -wZgĉ_4q3E;nWf'Ѹz*nmc9E.iYåEU_ѳi)8EMxɣ n6]q*wWwӎƥ3_3Gg_U/vN{jb3sr3U6u;C;w0uwG,g-a?,xzY&ruEDl"Erqqm6 :1'Goϧ,^C52"036Ser9x^{Gsi_$uo,| ^cĘ0po:Mb3kmd4~};@#q r #=&d@(ޔ];Z:$(H3~ǃdU \+p$pQȹ;XB|^䌍2gZq < r@xg@OW@КJqf*V(kb*   $$EcՖ~Q̞Y݂BwlцNͭZ'OA.8< 4)Y ]\LCJ0| dF^H0;;oRhx yY'ϟg;i (YauvHǀ H+n,l3;<6q̦0cdS,OU<:BԹ| ,!-#R1/bﴩOTޢm]IIE_F ҧw\$=Ws~$ i_[! oysm~iuoMo/%J(wH f/+[9ÂŵSvG9K4Mq~뱅uNׅվToHp?/=#pDlWsFS']D€GQ{G:C)B_ЗK^zd~l¸m|ŷ'&qҋ?]MaE^̛7Ӎƃ:[>/ex9h-Gb ]s'g/c:Ut4!<%fO'Zk gbRaM=!3CqzM!fȆQ Z?ȣAo„|аN>Q-mfPFgkmX:ۦ]Ox2)r<B߼L_Wq&?}ؐG2w-F eȳ>NjԨa:>@uHjZ)"woO`=n>..lLoY3jÿa`:$"sy#Ū]IJhN"ۘèL mD5|/$oQk$<8ncckxV n]5nڥ\f4pItb^TN IRh'sMNX|$ K`B ɦ `P,{|sm"׻Fx|6"##MgSF&"9wsusћOzz.)=*Ԋif.n:3i8m33XxxUx8J๤.gn^oG*JbMyҴquF&Y#722I[W =Dd^3bhh\*t@b^8i0Jѳi&ŠH_ߠ}sgd p<RUlW\BR ˀ0ЪA^VWefka|r`R{ͱTKcD *[e%B)4k8#,ֽ5L(z{/L^o_KR:5bA!ĞRIgl}N?Cm *5-y0]}錂-߽nňeyy`H`{,%rvǨu)Xha|Z7:ԃ|b̏1^9{e~!?C&vox4˱N V2CWsdҬԁ}HŮ6ѲQS&/4Ǘv'rT5FB&\.KowΎuQxG 1fWr:\Wک/Հ`7<$PV ujCsTY^CZEJ1ɽ %y_ߕ]S=:Փ]3~y maCZTlY(_zi46*%I#BRO|k#;D=zۇx@eM^=lN>j;(M:V$SNwǃn]js>.,Һn%8gF>!.|NJ_?g̨W۪=j>nŋbaN5^w%_) vw'۴SJ/fG&6 }mxrX(yqYO3W߅s[ͯ7tt_׮?fG+aK:ĆZj7)h u"/OU\Ԝ9[W[៶v~q`')7=+U;HAJ2]Yy;o;QТ*)l|h?䗼YWTCNX*AwЎt/wCǛf' my\O#m`_@ ^OmWN#ܣ8})cNݽO>aRwG?ֻF.['$_ysO?nC »<DS\!j4þ+t*oxM.zʉ"D;o7^[@0̻nKg7~k]V~jhN$Y=aoI~>3'^ٸGVɇv=z" N[1ܳ;= ¯jY i΍]ol\LrČCQ+LQ[s~zZӧODc]-Mꚫu?]~/{COt'{%y30o}U^}nkx(V?ft˛yZ5ژQL$UG~]uL_oxm'L^pS?8:o?~7rc~ck94vp缋̢Q s)Zb_>hG_>$vÓz^~>r=6j/ޥص:`f;K V`''o.;}^JFfu',26_wxAɿWhon%uC7kYsʈ1qGR+mdu[)>ZCXXkFǭٯw̮2l';aqTk 揝\=GD_".CRNlq_\N^y\?u}O7GN-ᶵO'{q/ϫ-8Q zޙS {R|h-}_|@ym6>O?~}@oGRAT#gJr;+;yڥa=N?|}q9Y3xiuQ{1빗ݔ֎U?HhJP i]kS>J_  IDATZ܅d^^u̪|/S_w7>niKvD؎:3%es{dq/M_>ws{wnEKP+ԭZ5eҔ!3dY-Yz/^ջL 'PQ)Nw' ~ǐo3V._C@=?w3?>‹.LU*saPㅅ~=a\fMm媥kn 95L$5rd%4f3; _}sNKkKTt(ҙ:R| j.Tܴʫ~cu*i9RPksO=ꛥbCfr o5n]b3#w=uҪ'^}Y iĜw?ar](`0 [L*5g᛫VU+ZWKMj ѢFEM:h\P:tsh1aNZ\FۙOVxj̦t{zTuMK[|]2 Gsl]]ݾ']<^;GڛtD֑,>UZ{+׌}1=hjHgO$9kgrKl;=iybEXGKq*6>?!h6͟/_X5ʎ%?mOŕ7LG*>+\1b5~wt̍k$6]}yC_,{6+?;%տDĴ~T^1v=1?"o몋/Yp%_K>.N}c?k/|bߟ]\q8+h?}cg]0H+m'w8ʝ/9+G݄ N WZv. >8oI+&=2_=Lm^{{M19K;՛s]y.̲qQ12^%' vٵ\ym-m Ֆ2[r?:k{ʼn/|{{~V\#}1OOw]kY_VyM3ҿZxP̛(L&t8LBPʏvNjSXJ۝7V96 ")Hԛy]An=*\VQ/ڡ}~'7_Txi.bH !Q#f;_w(>Vc/o{cA$CD$~% :[NٷuBCy)_ d+U;uEǻ:;[F&ijiï.m_岤]THHa##IƊ߾mK\y5^JIj>j"$g1i!"%'E|]j;pۺK톆j;B$Z/RgZcǦkj3Uޟq~du ʭ|)OzdfPj5P! \*Fnv{u|>S\ W~Kz5 3~,@$ U'v{9-z5>H!$R#;_PilzK-߾. ZqQ6Rffd&7Vu-ﶢجC.WxB%b=&Rauc PѽnU}p8KEצ/ZA{xme;Z(*+͠ZWrRtu;}Ge^,bT=^'siI'pRЮ?4B%„?|Fɋ$~(ӘA4O;\̸3 ߒjk9*ee>O7문'܇Oy[>>Y![ҹ;>jkN p:PaGx-'z#-kyv\K`Ya04]G/K1lY:ԡecRAFl R,X!ܤ`Ìrylk-@b:)W :-q!*[c`90Rll]#ZsE" 846a$ s[)Rwx PD&Ϸ09Y37_[zM:%a܂Dy@Y~BN)u)æ3+HDR[G^ǘS|8Soh]@4@(RV92P6vKޱ5^c[yW ='[FqOP g[Xۍx鏗eZQwҏ.a*ks鼼6^y 3cic{o/gFخRӟ.=̼yڎPwXCf`B;#l_v}+Sv(Zy#**m8 {b#w7㎌z_ʬ>p VO%V̊1r4o3\M1ꇖQAd 2$m-…6MübX\UͼM;#jVlcQJv3^Pp=l{EVw5zUt٥Ƣ<ͼؽYeEk7v%N]ny(%uX-'{whnzޏYte8z;Zfԁ3Q?]ݪ>|}xG [%c~z#########I?||||||||v8| !sGGGGGGGG`C';ܐI?|||||||0|f/?q1]|`x U#_݈n~GOI՗{ׄis^Pv`H="1IW.b֫qh?%-KU@{####@7r.,U(ϲ@I}Q\ɗ}U-*eWXwjWZ"r_E};~|||||||>,>tͰL$Xds,s.9\I˴UT—5UnFv8`H1C>>>>>>>_ hdf+Oc/8bQ%e )lDyU'JrR\3J<'s 2)]38TBo4MYORwa;######D[h)(ҽ oE-Jiօd ݊,2)!A I-nSa j1gqdHF:+XhSzSgw';ޘ=6|&AրgGM:Q(MECi!Ez*]1krTR BBG]qmY#%%FW >dsVRTKsˢ:ODh#6I9buDϮCʨ~0rJ)PyEb40KFS7* [9KEE PC*)dJ.kZDAt\*j6H;^OR|Sjbp)u"],YDmk"5s4*{Ѣ2M,W_]:zlq+JŝhQդRJlYuhRrv';ܐF%Ď|k?CuTʒ)M\ UٕHo``NIL`/##### 7_FX~f'1%Ywc^*U@ImD3Qoa2cSenf@$Y.5.4TFztmuXn2PHBF\)W2\!5"dYi .X؄|[E֕%"Lrp9M\Ng)5IK9Gh$C`9ύrppԒ(I5|Us)X-n1M$MUkR"Ua TDVLJE8ݴ5^rU7.ߩ,щUr3.oin:`9H*8D7p2]-%+bhG[/~&s';#}||||[-ֿ:[$.Tbqdܗ+~(og~d <'D >|sMd Bw,MDpHJj$Ӟ^"̀ZNRp'DĪT(d˲^[l/X IVתHJhV$'5Y7qrj\MNf ep}SԋZ(Xy.&>ILt}||||Gz Q2sޔXP*V8_B ƋD(^Ev4gl1H<(|I(Ѩe{*q|鶘rL/\PWl7JbUq1cuE>)b^b)BST\A) UU]^l\J5(`I-x0uL+Z K`W:rp8)w<\\}:l X5u2U2_%$7!uɨ*X;cۿۏGCJTT6X5)qbjv6Wo yG׭LdˑI#*L7qm4H )VR90ZdGEUBS#e=V^yA jخ#Ey:*eSI8Q\qܬ82>'© sTC[ڮVR,fX Տ=>--t,ePF%aڠ; Y,RףȰ!ccj+bCdj˲jm?mi( -Ԯz봞]WۥX%HGI%Izjs5XK&9ç*A4"݈*x%mbp@Lۮ^lHȚNjZI$EAri. O4!D.J8-*x1h%H.qƂ3zq&Z0s}:m|(s#e>)_ȗC+E(+6cyG]F'qRibTxgԁQMؼ+m~- $>K2FwoU !ǎt6nhwh$@VpfPJt7 TӉ8&j$2>)`JnՉ@ %SU"iAQ7uf;|>Wx[iR.Rh+*l@W ;)%AAs14W}rΰwOR?|>>>>>[CR'VLUc'V!dHƷVlkC{.v!BHɤ3T# ʴdj%nj\,3ݕ-0{AL*׺yWɶXm_7o߳0lH&}Ԕ¢`-EI)H  KԅBh< QՄ*26EXE)*eDkc-KweMQ1#FD ^KrR|yB+KŞ`@͕LQ6lCS6* zHꨲʊ$["Ⱥ[׫LT-'}8-.~$D>>>>>;d_8)#!4-Ws\6"-⊼k$%բ˲ Z޲x.. f*-sPb]I n1!3=Z N0/#K_,0X@.*'ْ&p%yŭJq}1wi򯯫ue&>qm!5u/e">PF58WIOfdy;kQ.գ>~p020"\~0[bS C0+ePƛjD,@G5{fuCøǞv@Cf&߭N+feLr5 ,B 䦻 /j, *$HdY؈ SVn$3`*آ%Ag339|.D AXa5D)q26W,Fׁ>vĄx; 5Tgz3iU`qhXA:?Tk֙psuWn%(p5'?l+uN{- ӭQ^W# \لҷuor9ms76V;9h2>~ JؓC,n9+LZfMVe/f?}7yMsia 3ox~ƭ ddADܡ/rt{EoòqU?X&S%d7OHa䂺K-FBy8i"Ep +Y:6#.x^3etav.BsO;>l\[) JɱaqO urZR\Tn]AFTJ/mi\N{Q[ [A/=[v:)rEETW"dQ,#HQwU7*9"SL1̨fbޠr b +IL֎$q!<\'Ŋʎsdl @Q+Ųdd&ItP63:čmǞ{)fmQ:z{ Gpspt=yf.a<1hFncL_R4>zc%}NjM6n?6<)q;w'-d,cm{q~]kq` 'Y `)bN2B?'{*q/~⃚Q#bY/d~̑?>#*C*CFBv&ud2Mqُw盶e{T33en(V5 {&4Z"vxHRÊ@AɔqJ$(TzʡuXq$C$K8eP(B"8/Xu~$(T˰2iP?vap!@bVduJP%CTJT̹4k 6av.@g#*hɍvLgL:/vC}uM\w?07CssVzmէOǠyGK \Y06.PR?f钄1 q͐\Ȁ`_-cO! 24i7lӱk:9꒟^4$ x:DP%1SOa)H\kLce 4DZH["2˔8XNb`CDc7WVp,z,idvlŖ)@sq Oc XŪCfɈd;ʁ8"r)W)gj,*(3|8YE8)j%ě*$ \.'K*◵Eہ=QID,6Ȳ@[ȩrLA$֍sGQ_פ]A4BP/(:pu@?BJdۚ^!]RAlq$ IR %$di#:Evн`6ڽm =L]~KͶ\tJNR\{+J*;qAy@[;hn7;rq:`Xu͂AJ;rV]r9vs )YTQ (kpVjCߌ t#(ނ 2AFxH"JIN>ktnc\Fh͒^/|ʸ}/qLfa&V#J|v^g0kl at.Oa eHiUEe6z(pj@Qd,B́[uC4ة>ƋuOCLE6*ϩ1iꞖQޢyL# ;(s)*`?@n-Mb?u-l fnQ@R֚T :ѩHU͐co‡m1!w C [nߌ|}jq@|Zl )Zՙ9! 9d9ئHh\tݦgfC*a0̈́ʤPx HfC|AࡅBowzHUpU=V"s>)+p_ {Mk~HH^..x/;,E 0YBQUEfj:6"v؄(o-CER}VьB`n, ):Mb +(]HLJX|+H?,FO1l00D{u;=BUPM(ºn@LAؙt:)UR"VDKa5zٓ6O5lCk=|'l(_ )C!3&KZDVK1$DJ!= S + DnVPB,c0%pOnnn~Oq.Q_!xNsJ[U#Z9Ed%wP r%9 0,fۣƜ!ce9Ȱ8O#=/r)dƜDDe uE]6X: # w鲔Ta]jj`?,λ1@ K +@3_S EI+%+}8tcI9:~xvg{|RLznOj?w܊}g&<DJ9d+(^뼒G *.9z;4ظ~=?,< 1=BH(G;]^%]glC1꜃X0w4qMA#eg7-TfFt嬱omtdΑ;o܍?lk;####0PxLF97mSĂN2a# A؍UrB uę@V H;"#r@d؂/}}+ z1T%fS3 $D,37th?8WwwdBpB`n1P8x b+m̒ rm酋2FqR7@ŴPNneK!dXxTRP;eBr(v(蠤⏡ mQk fd1n'0ZyM[{۲W6u4tC%ZVZ5&N<(qVEIEnIp:q$kXܔ=N ZlKݔK~aH<ԩm$?kng6ncGGG # # 7[KHw\.,ScIWq ?8uT:Af Z)R ?1anX`[эU\ ^`*6ېdk M˶z~/=L'pP07z㲘%oIs ٬%np%H$SbJjsU:Qk\u(8%ȫ:]pi׎ c% )Q96{PCXoM3[D["hʎњ%S&Lik,z^_[ڨZD(xl!}AMSQϲZ-d HE@-4d.(e!$!+h6Yk3p)X6Q>>>>>_0A$HHk9!G8+,U, ≄i RJYx42+9>vQ"" BHb̖ FT/L>Qhyd(&"QڳXĖ1_kSrIVÄ eV r-sp&)f+|(jz!gj ;-8d\]f&[0jHC]\A Bd܇,YrW?|hz}ޔL+$ RPm-\a^>R6#ʘe9 6BO8#R#YjbUxX5%I-R[)2w#%gCo_, ]*LlR>Ɓ8 &X_%Bw>/!'d=;oJCzRk@_qY7ym6=c:ٖ#f.uRqKN|Q\ޑ<:pt-#,/E>bUO NU15vt.^הgzGyo>#,ܑӂ5|B,rsl RpLfJeQ<x]vR3Y'Cj8PDu)x;G]'&pH ]wY4ۨ>yہ&ZpzA@#Ƕ[35n~'ޚs?g mSF1IPǍ9cIk5_o[Zx;:Yhd$U3+y/V^=[B~LċPs)kѹmuI B{Ϭd2~B4^ 1)۰LMTSReub6ڞ+w>qd|_x4-%xZRV+oΛCۗY8/B5uC!_*ᦙ^{+q˜YI,~x3~kLA>nO[ fpQ>d/6߿SvGrSv;bg]JWN"Ջ{ν/5$>E10Iw#qXxwL;մ.z+WqW{Dh6^oz3ȓ=Q4m&{wyș[mhUy~iv+; 0jmn֩5eUY'7~YuP3s0Wa2"+;8u7ܼw_[g+o,L#:X1^q}?]####9nH&\.)J$dE7M}iKX ] 1XE}9B^*tuos"5}'"2Y IbbrwΠIlem]wg?#-i_{ai% *<5SZ[[Fۂ2G0!/dƳ>cn 0VEzAg@{Ob)@t]#6e1woSDJBP=6QD"lN x:A"YIDÍV. OEEz۴dM62,Q5TVUh8èg"B3=N>U qTi።MHrʕiGxPQJjg g:{Qbî,`%b&4nbQ]aA@z[ve{{M}Hd'Dyw=~p+,-tWj:[(%F A)#BEMnJ?DBp"KY WZ]Z7+RM4NaRm.T]EߖG~?3|ή};A&}v!ϸ~ pÿ? 3L݅u:5>ܩnh-e #}hg}}ьq_kfcʵCo~jQ/^>[N$to?sm܇'xp;u.bu{rg|Fnƛ]~C_2+bvĦN+g}|VyW}s޺n\4+bw&ڳwwv}g_e_;.Y!ƺ+Ϻ}A[Wby~ޟ{-k^ -@#Ӥx3*ˀ! vKiI&ԆAN  Xm~(BGGʃ J[C,Zp"d-O<y)}q';[>Nzɟ}ed́E# }ȔM}ꂺQ>~yIoȔJ]@W;vUmzQq|ѪSeCx JVg]{?<ܱmeA9K=r,&SNL;Uk f}{ IŹT Bq9j*d)bE% ss$0i:pBzFC5U)+V dSFZS5o#(BQLN%Nua) x~ym2-r864ƪXdFJʰf)!%Y, aeQP$- v:*b,YW f 3e >Wb+zhM9ucFe07V1m`sY/ooOˊQJ\A]*JEcSmS(G8>f ƞW<ܧ8\m 9f:k Xi9Iik{wOhupc󾫏I}҄;gkwoK{QVOv^ Z'ʗ`4Iq |< V1I`!rT b~ڇ{#fT(m(I-)S'ZxI[ֳ s%ޟŜܤ,r}K|C_xWKāpTz“3r|2.hjeT-$-tC:!Ğ?x1'[}fg$yA#E6%BM?Sbc񌕁`(E{5yta5Uoھ]9fJHqL} HI0s3"ҚB6"M6!'$YM5CVH{0. 4;$ǾcWUrFMzXk *YC8 }͔wߞJ+ڈ Bj/;SF 5jmM{Kuߓ糒)S "-SHٓ,Db)(?PR*dmD_w:0=br@'ϣ1k*(h&asa4yWq;Wjvi_sGl~GŲbK 7W͸ <@a!_avʧL<7kw YS|a毎kVge۞~\z-kw (%:ގGI$%J n ( Ha0i*zg(To2T:o_q_1G*nrjZ|'6QiCy5f8 uW:O1&0mhë<| +ГTS`RԊ3n3k֐?O9W2]&5;;߰B2 $FƔMZ&yi0 eXS #rXHBV+hc͑nU $:u5%ҀK ⪢hZ4;+x=f1ǿU^yMfR*\~8G(7zo?)\z2F"}RAlRVh@ֳ)Zmղܡ8yhF0(e/92{SmSA mfQ!oRz?/Q$߻lN-t>gϧ:+]+[~y3|hi>*O:|b&gG.tɭOSYSY,,~Kv®~zk!M~  N@;x䙷3/}⦵3>Т#\q[ŮG;sJ}=ڧ3|#TJre{/=k^ Zdue |1NHKc-p(%Jx74}QTs?j%._tY^XeQdҝP76-3vc3h`EBQ#o<8ݦiH\D88I .AƎn x=ps=OU(G.2'9RqsW~J'M[5hP4joD -@+]#E4eEA Y7*YΖ>n"fȨa@8;/]J["7QeH8 9/J52%Q3տԧ`uRAA5æHp͒a1JKG87T_Uf64A)Q ga+-F]sho봞%}=}ѫ^fc>k;+:V`?1ƿ.Y喦Pf;kAk^ Z`Z{a$hAB|JipܦEh xO +PKK 7޾l NGÖ6$[߈Ak2rlPLjA1JGo}'$D:`Ȁo--+t=[voRde%#%9( -[|.;n8.ǚԖʻ\(@9 v" QqiI9'5Rg9(KY$icHeY+?BsB p 3gb_46FfRv;U 9k (d()%;ӵ*L!ХnaѨNv 0y[>ɱPP45DSa(wƃTieNK]{_*SprL8*OBVDWbS#v1^zG6iS\ :sMo}ѯr1\[n1/OjCiQO PȢʓ::"R8oa@Eխ&2r@%m=.OǾ9wl?Z73sA?#y#7~sU7ڿGho{-bʘaQ鄲 o}d,_I0Qje E<flvۚ5 +V*tآ>PLP1vόɔ L@,(R BǣfPNN8Π{)5%#":mv 'j֖bEIN4FsAu%͕ezw"huA]I霂{vi( AFND9  IN!3H# EcZO l %h1Ê+_n|cah l*yeE@n%G1i2&B'ȖBDL+@yU"e  INd|T2-%]O-~:^롙~MGflҋwe|z-k_^YK7@HBBʋK6V:p`Ie\ScN 'ɯ Vmi2x+ei'b`AV`uhBJCVi$rԠ,6k |,@Q*̃btɑl 75QQ xQ5ek7gP0J"/3TB1hRf;)%S9P%`C'(6RKJC NjoeJR bQ54u?7sf7PrgpwwTýK{A(A6!C.4ETrM7aB։VbB#T T/9p0jX[oY_ Uȋ;dA3b|SKh -,'yChoْ,,"YVJ!  4t-{Q }ӊc<bcj'0+t\%dXlYjF0 M(=7QBo2Fs? Rl^k^ ZI KCFN)d;)Upt%~;jT,֑Nm6?0 'vT IDAT uѬnB?WE귆R"\ StB`T$J4՗)6PsKg*`45ܴ%l.BYEC@,ӬdB/PXU.J#AhCp(j9J{Yم]áʇe!zo $HniP%Tzzq)NOP#%=}~&Q `jJJ`)(f'{b7LML1 Cτ4Θ6VvVmDNK7#)tppt PNtl/ΠԅRwߟ+ &= 0"9A]Ban@S+\ P֨q,"7o]!#tg^z4z>69^q@+MN`:j9M?$}V d%8zP(9F@,֤PB)& ~6G/ݩ[lLt;4OhtF\)r8)1 @/dBlߟ}ךL}|2`5IEݻv{ w9狸~'7\:e tDŖ2qژAo{y"nwn~6C?yH΋>=k,z#qE!]jo]{zga۪2vr~is'wҩ^s; mzh \dwsoھvg# ~SʳߩߪҜ)}cO8[??G|v~bJ9],#hCLGgfN[U/:{}=HܗH^*&gRj"8D.)r4*#Ԕd5 !YC@&v" XmELO>E{KnazxǾmGy6+Q?yaյ%v+ݝf7#K/?n+\}X'A5Wse+-}ݶ=N6;tÈϽ7lڶ/tӜo3n=]?Zكf8>jE6rbT w=\L.U_>ˤ2j5K^ ZEk*c<%emc&bST +'zchyޤ>%]Sd$.:h!,nٰlj6,kZVUdY;j*)vJ5"r[;6ɢczvK??R)- pؒ6 :0#Lv6ڎl.|5=;nftθ"2$^ksc4s8϶Zh|FOtFYw)Ccǎ`]HlmHc٘ͼf|ϙ3y ۻozꆊ\=d}(uw^qynje'<~;nHk.<ڿmF(GI$Z!\^I%#I=:H(TIMuh'Pu(8ow(vF%+ְml gFY\Hey͗w#;5Ex\y6qpa`hEnAjox8wS pdҦrn8Ku)Y Rl+jzRNrRQ6/s|~o~NN/'bAEr%%rV^c`[c4 x$TD4OTgN*:cXN@ΌG{Y0J Rl:FYM@)SH㢎G˲^:V٬VJ[:1e}UiUzg}|^nqv!gٮׯu>,wXZ~?|{t 2Y{4[;Ƌ; so˥w3l_6o0>ʴuW=x|k ǿ="U7 Xbko~Rso9[D*_ڶc,Į=/8*]e<.&i|V|!u:)Fb$Dbo]nDӵԦVkcLj~O/?3$vP[8zQs*əԿ|Ё??h*!eJG>9pJ`H.˾$ΐ&! 4PϘaB?|D2̥*IE& B 6d<- մ~_~Hr!em9y*"lEUN'T5&(5oh "xƖJlhftmkvP%F)6F=剘}BjA #cϬ/I-hCԩK"ﵓ|^ݷD!-qzNSd+$'lX)ma5Rcxs8KDՍ_|XAdqbVh ~DxflJ2³ 1dUȎIiS cPK fTB*0(H29A6TD5I,#:xTr!drҖzbDvvBf?r. nӸzɄzp ʔօZV3BdSG| 'M t2R.sM?=x]䙗~ÞLJ#?0܁^8mzAQdފP5ulA@V!Sf<7 w4spn奲ue$mqX1E|͇/u}#G:調۶ٍ) 9/v1O|fpzOrrga ,#!P#gThtg]{?<|鱍ݝ,v@CdVG`7D 9}ʼɎv*uyҟsyٵ?dl<~$c4k1r($ܓYcqSگ9cש3,s3-6w ~qG8kP7M\r=^>lcgz>[;Yk?yU.]ǹu/3S?ᄒγ>*AM8hU㊭?rg(Ν8~)NN ^]U7wS6cY[:9 0:a䈡H;P$|NrFm KMS=r键C.1%7q b(DEANp_[4t7r$X喦TTM%PK+v[7m\[~Kcc8ךJ;DAh<O"VSI`PUlcy8A+iUtD'M=K Jb%y@Q#ӡe.!3nBn-9$?M2!Q *.2mVo?_&rZ,J<^4G\3LEe{7W}>@=Oլ8|Ȑ!{ECFM Yxa.%ᩏA\?—Ii}xoIL-\^BLM$ "egh^3#HvBJiTG”lܘ11C-|BY;Xk86b+Rm ݳc^'n~67'wiupc󾫏p_zO_,~sߝsO:{X@ >VIOUA; ̓ר[ z;& k[jjF/5lȠIGL,*/(+;Q/M-)SX/\_YKVxuDhțE~ܰڭg,^ݹ+cS=bע$Uya}J ml45]Lu'O-}H=TA:G$eSyN6eXD#B (SQu#|Sdi% #+Ec H&z@=v\[dAQ_<;__N f}@:BbxmM #<:x-lhD.r2UPD0DC'LXI[4ij~*0OsĂBܪ=Ϣ.&6knM"oHN)6%niX:N!6$CQ7zrꛬ3??mcYn| U2 IDATiumslƉ 5ӊbS;:Yx~AZ1 ѧxO{>ׇ㋇K\&qt)aƴ%U-F2FM.-VL:#H1J266& [^É|y v{Ȍӯ9z@i]v~ۅZk]tc?o?]wonJOi+m1 ݙ^~n;yowӥn=o=;S;LzG0hvðB^o#Vkd-e Fp|֚9T_G{.Ͼ# Gv&Ek(1{C>=7Ywu2Vm뎗4yzϏ=8,* cu2u@î~G#f|͵׼+N?󔹯+|Fo#7[o1;""&Դ@\/,wze-u_/dcT؉ jt=3oB%("@VeH20؞K`u)xstVݠ,c8jMYc]<7.ܽemUYhlxaw ,Ym6!ӦXˈ53_tò.{cf2C]+3ܼK>Yi)ܙ7ofm}u˿voWQ7̟SL'mmg;Vry[zso=Sx9GNy7/(Ѻh(F=<VTr(Mmi13//[j֢n-{ ,?r?4:jFבDOVӂow}|bU3{iўLQl|sAc(æ(gk?26TnˮbIEqs[?箷˜y?7WAoO.{[Uppij~]0nsW<}jSFû}1ܷ~ӳ&=2q~j4 pgyԝ}o3YoCS6wiTl|c \ϓ{lg; 7iWb7]VM]2Ӆ<A9>EX>#իMPߛ{N/z{gHOUhMrMsk.O#UӂB!TCA,5B[jC B{RNom͵*ryiEŰTSIRpDuOQa&sQaG+ԩ6Rq]Q "^m $ z[Yo~fʁ :@Ί!'l(92RB>_ QvZ-ќ9IHz]NH3ij 5#aF$Tc6!YhDd m3)͍- MmrP*%{ d9t^>8SZ^]ƐSvŪk6wB9*] L%а,H\ TxxC&ng%EHѢAD횾9С-F8'e:KhD (pWRkAZejenBLP,7;ÊxN|rc( 䛞<~C9[;Yj: ur)3)ž,nͻFN>uɤaǟ7ˇfs)yh&?yǽR8׾=`;c?TyusI7u[x=]EfD=gI@P>xϜKkx37i>fYqգ@ƴ!m3퀷j=c<۶{7xֲiёsT.;}^jez{33ritcف4u޳w{lK{fwcTݓxfmqLW72_>{׮ãZwz>o{>g?so^[;=##yg'[?G(*ٜ9[;7'QsSl-g[3|#TJreT$e!c}y"+gE؊ްA2+K,3 8{I!Eə)_~Q!-U5h !wIYg~6ԸK~͊`gWq#W+ (OL*Hrkܹ 4\>MM$^S]"n&#I5R4eڜw?m Z1։DMG CGpy7s4/UmbCX_M$KJ4G<`TB #Q@@A n}s|J%:Ͽ,3RD<e+9T&*?aZ0mft{gAhpe,L`f7/0QˠKI!@v&\`֪*jOB )%CoyZʚ5(,q:\&-yD,-Z8i= _))!N"%Q~,/ (*e)Zv_ jE;/t% 0kiIqkU5"mc ȈXJTZ0c9TmjILXfo]ddkΊzÃ@e^5CxYvv>[;;,K_Zv)7ԇYΨk?9/'<=z{i/?ު'6:|9zZO2%IFd*4zD+9ˬ:dkUcRls M:q~үx[ fb=ˌ fPҲud:҈"Ea$;@˦57>0tiY5O TCpuK'5СbEFO`.ϒ⎙pljyaMwWb+ ٪Џ93PM`x`s ]G#^ps6[ Ci@iVӕN8L0ӲL@8zq'Z$+OBh "8.XHJ< /xӉ1MY=C q'Q1v)('Y|ʨdeL O^QAd"t)UTc'%PHK<_3Tj$u?AA%q4RLcD F2}L+;tm΄?b$bxf*԰Lr@Hf؟|4C(HSG}ME/dca碈Y%ii9-. '$PTT`5 $XAD / <~SLϞSyǂZl*;Ӥ4oZ5<ӦU"7-(S^oT٨@H YR v+L$H}~\_?/ٳlw~iKzv{ҋw]zY '||*$2HАD6?䶙W~7lC;>OօXPZ4CP>7^S˝t0߈w84H; RŅ˖xNp^%ʔeV;8pĔCG 뗊-Vw֭[<`H-Qp#ZL ^@=<$ݴQx2BS6)Bt`e~M"(j2WhdmVĦ<M#H"' 5F}$[ɠaeU;Z6F:MxoYuˊK QJJ*ԁ0Qfs{ZS,4E)CTC⦰ND3!)L@T#Lb. I U]@(FP%Sm[ƟxG%mlJ;t,~v9CTܮxKS"TX2l_S[/˱hc4)ZTzK%tQS$*J'v ؠ1HEV2Nd@nI[~k^ Z)MiTm:b6 "HE>QoDb3AȄfÝgx( Sbt15dY e0b 7oKmukGFY`E῅?꧃҈x)*3!|,ACI#njmiFgAz H׫,w/oo^H!iR,(b:uF@ǮJql !^vozIB u{?'{u{-kbKWɄlS rYNe\Fѓ r{Bܯ~pW;4,m >60LЇ'Uݱg0`$<&Ao$ivc]$6Mp62$CF&[[Z:R *V+:pv@K&B ~HUۍAH]ˁ" ;}$ nqTN c}~xk|$hMzA"EuLF@*KS0냤34}/O~^?،5~鏌~wN?s{fa7kjoUVZjW_M Ӆ^(p%k@n<:P]b GO4Ft8 DSTp90%LQ7Q1n1DPfA*CrdeP Vwlrn<=6 IDATx|pjj挹A!33Z@VJD㜹ZFÀw0s$)^2/ye GyH?ǂnb]X/"%He'u][+ U\i* >+B⦇XiĔ>yV2ҍٷ7}ɬ^=#ͽ91Pzz *P@UDyBEZڤKLƘ#pT0!)BWzBQр|?JJ6=@R!à^pB^TRωTw\tEfXkL7K:y11 }bQKk7AK"U ܇{cY#`Ჭ&k0O%:_1ֹ$S`a"|< AUB)1v1a=x_r>"<ԀE_'a)k⟜#쑊p~A2-9nZtcvգ`4~*Z;M>y9*`A`)σͤ, 7T@kxR(|bbqAY*e\9ؐϿi^)o|3~©d g+ ͛HTJ%bR.ˁHk%^,xQQ.Zm GSz &"p#p%M$8@шV ˬ ڥFsY#[AoUs[btPdRt.HurBoPѮ?sq}O&g! Ͼ}?}%klxW_}Y[zѝyz/}=Og77xoϽj?Sm߲z_[SO;ڻjUVZO,Qĉ}BoVM0)h6 ~Gpϓlw(ȹ|HIJ8d42yO2P*zd)x.Wp71_9Trw˯-Z*SFRUKϬӯ$CY? H%ҍlO p"?G`}8K:_>=LMoX:ck-]Vؾk둇S>>9D<_]'ـH HQ!hmaC*@E,tu `"U]+NP"G\ΨTe@>tIvC:PgrnS+W,szXcQ}@ly˜uh r]9ੱC)Db0 c FI ƶBtK'!3O7v-j w3ىk_v/|t&nXt4z_,O 3gɑkyFՆGCAH.j]%B+b!M3[S݈W0PYaCt3\xs။v@FL VȤF e&DI8J`Va4'RvϏH<^0`:ܷ͌uv/nC>Ӗ`GK?;\|ǟ6ibڻjUVZVOb" N']ab(4"`T&3 |&u/GCS},2E E&5 ?CP4"T#AZ~}(gXa?\ .TH `D%ĝ9Pէ&'L&;2,> H xZf1_EnZ]qsXJBSKX]iPkO ^J@Ur>?0+_'*8Cځu$AZ\EY> P9j`V`Z͉(,%) "&GƃQc&fH.=EC1c H4 kI0aػGNs*ziA=DLܿP,<<Ī.gcaSI+ F&DXGفivRD@ʻ:}248Z6s2W*_↖MǛ:c',^:nBn&-g{Fw&d01w4!DWD!bCw lۉ@6Y2 ><o P6a_Mxi*-ߣ"!l ӟؿ. k]˯# Cۋ j*eMY9 `7#-Į'G'^}/K9yʄƓH\lv,?]VtMjJ=vs3/$9ȯ눏B 8/  u!\*P@ ϯq/m5tγ. (Os0DxK8m'ebP誈 +)*ޅ&V. A. $4p43_oz5T eݵca 9K6v &VtbmN ˙ں}C+zcWM<=j~F ǹwBzI)+U9:k mBJ&:"^gy𐠉ժH \nakƇH4+ePw6#= c K@ir@EVhעD|8:Dhzę<; ng[փ&+Jr0PHdf/:Q0ҞCYލhR6lZ0x$`WpD@8ݽM:(a _h]f4WsxcKR29,B1T+GeA׫`A< 5ThG$BqӚk@fE3qA(P}PMa!y۰ |cYاv#=+fNKWvxANꦟVdgs-c_3v雄7BȲ"x}f)~fw` B|Cwܰ1+n=՛Rn݆W5z:՞UVZT( 4& fBbcƄЇD{|y&ǎU*#F< 2+}۷ׇ f1Җ-[ ~-\,?53,B??- < 姞xƉKgu7u­7ЙǔMlhK;ʷqXpχ|qX4ȲsAT6k˝f 2DUDF8J9At2J( -G>` !yXмd#!$ +j4#*cBx @.f,BbS4] &p"pN6GM7ZZ$= l +JŐ_[cVH#M 5` bfQ8O%,- ٺh1xC ,Q٬])I[ E$ Poe=N=`ϯk($8[w~䬝?7<y~I>+,<y`}Vs`h1+QMmbícʶ_d͟xoplLo|ӻB+?udjNH)oyiǏ7,zO.ng|v_{{+>| V@ *P1*Ό"b` ts:x2P&%N괩0GrP *8WS(k7&I}kPhs!$AҮJX:Ϳ#l͜Y ռeiQ0trlXS œpp^&&le(JqP D:8Lf?t]?j.{ۈa@ޔBOs_V;SȤ% 0IQ+5N}-"L2a\.[[HmǰWZ1aDzDyʈ +kott,!S\eLec [ܠ'Z|mAcf`o 7& zՏ=lc%(sּ=#|*V ۤ\rցe)Z)) w^uEVzzh2HH.zH$Ǟ_K~%+8'K3eŴ| ^۶=vqkE =C t0Xr’lXs/)NńXeG# Zk'3xCxs¤$#s{<4R];ͅ‹8'8+q-EMڱW~xk,-H8QdL*ϓV=>\5RZ%0O dHpdl̡n{EY. 7 Ɨ%>=.4E=#Ղ`y12 SLFEˣ-eRjUVZjYr>ܞI8HdhJ]B{W=FX&ػD35.e30`^y%,lJ"e |G)ϧ˘6-4VELaL] @$kR2[&|J]4ga*j%Y-WwL%ǵ`A" i~G{cm9%/(]?}k9OD}.(LN۶4L'x,U=[nj(kB ox}kVcIZړ֙|eac4ӹ#C)N46 ƿnx偧<őLp1nC51>D9^h,+ntzd{I͡;'!sEb7Y`zeĀD `P"si8b,]N l۱}ddl-;nϗTabFΖTR)hK))ѣ KL? .0b=Y\Ȏ~&'ߗUD/d~)h ȲQE" M|Dp EoUdH ._;g_P0)? ot%\ b\l IDATQ<^Y=Uq[d"BQ d \wrrojK hʵ4:z:K]XL}1ѸbY {,v`cGCnuٹ(ĩy-&ZgRHbA*Acw(NID*23<f$j葈TX/!C?z%A{]XȘAPZ%HLEټz}r4OIR'7.U 21wog nK0!<%D B JS(Ov{Pоl8 r$ٓc n5fi| "?ލܮqbYy;Bo82 O@P0 /mJƂR9MBE[s@R{VZjURAc~zW:2)h\~tB謗1 (NpR$.۠OAAsh3%Mk^LiʔU2ijJάDM(u$Nųrz5U0ѽ-)W0W79WC;I9PɃ"/Fמ9n_+/76Ƃ*p"_U, rh)=fWW9-^3RR]jykax϶"u:&]XuQ6>yxrs?ECrVf5ad1=OfϻsgW'`2ѳQ*DK[O![¹`%pWroI0Ū!0 ~c+8 IӅ@s*٩I-Ňi#u/-{@Sc0uKQ¹۪n**!wHRPRA}qaV 4 ÒC$( #aS}{3 . ]^vTta?pRWc *P@-vຈDρI["Akd#jj-,!"-XqrxV-SIMsExK6(ONN`j&HKU͎5ۢq7 Jv^w4 a\]%vP_T}hQ{97gYϫ߳ӊ{s?=낕xvW1BT Hd불IƩJw1ŀ'?V놷phN2`'jƻHUH-H21Ir¢"fxgi4,`B]ghG_ql{ Z6', D8x3h~osP*#~ᥘ"hDg=\hK՜5IIqʂm$<^kS(-fȰ OXTUyWmi-(M%A `O!+5z)QrPU3G Lԃ~[~ @N[% *P@GmDBT9\+c c<E₏JcztBH MX"p6z+©ĪX*l,EL($W~L\%Oj8u91ֳ֔V+3pH1[)N'bu[^y7 32ҫ:TkQskE)Zzlr]U.>.@ikġ Vc\؆a2}3weiLZTȜ r922ݽul8cf)?g'^+~e۶o޵ A=2ѠYH05|٣S_= n\O>ߞ{*.]u>wSK+ޡiel^+06HG61GU)>a< ó1K1E)6PEGy0Yތ uh+l eT IDJ2oB}w+qIȏnaA2|&\aP2$=@+R%t`AիjQ+CqǍMڅ.6Q(,Ai|)\W!HP:gaD7:<֠~V(URk0ptV= sSiõy+ - Y]{6]f#Ss׿ҲKeW4[wLۼ}2<8?=T_ 'y/mNXGÿ}$xow/{KϹ_SPuQ^q×\.<έ{>Y#.x}/=/}w3O|Ɂ\$9>sxdĿm:f^9aLgkUV &Y"x 8 ЫVVWkAGzD7PMZL)DwBGh'/AS 0B/n VOm ;7A5v*S֮B]9eK77}ӷ>_y&]M{oUEƣaQƦ&*9dÎjir1RpcOUHP.7Kd.-'&Z']ؒ@*,v٧-ꉿm.^u Jp9O6vP.'E+~O/5a |!pERM/yԅ']#{"f*| r7;'vsJB#0#n 3y&}1l"S`\ IPhDry[fD|E;-K =#@5R)=: m"x(R}+d@XQcs . XN> qS,gw612S|Qc`KLR -sZg 0d0&Β;-}`K90T"SKę x%^ !\\i-гb=37 ºѽ6 |2>ung΁ۿ,zhX{«hz*&v-`^|8هn:T/youw\o/Xu^i?+nz j.,DP2j=r_-H#'?#|>:WTXǪd7}riG]vg䑯])ӏo-= qkϡp^{VZjU࿱`L-`/MGDk3H-Vs v$VpDܢ钦qx\ ekLS+ _!b z>L2dp(p x8yfZfv>@kLd.]JlZ0)jt"<}I[{EWl{mϯ<7mz9v,MS!Gx/5~tc gqS'eLE1u" " Aӌ*d/+&[ڡML],GGp.LCCjͺ 7t#7'>#` \ 8f:$m#IjÁd]e(1[4%d6SR}B,tPbe%T0y? .xŲ`Bi';#*+su'/k _>1Ƨz3ĪJG'3{3^yݍdW(_x^LS'C__8QZ0,|%y'3޶_7wwz ,[ȉS.pՏoې;?Ï/_}_^Sx\G>ke}9A$2Oթ"9̩{^{op,/yy_k*P@ ZH4q}Ddn#TIߛjy, wf*6jJ3ӑޒK#9?@/KvQ} !5iZf4`{Lni?$zDOs``D6uYNͨVՑ=cNXf\:G}(-:w5plU˰HbC`*H,2^!0@'98²XvjYFS^W^(0d r9V T3QaG6팳cE8I*]IM'זJ lѰBZF%ޱxY[^ Fŝ+ocNsR;B@\/,ce6J1[f!E-l#1K3uQgFvSqR &gx1x3TTf VvOj0*Hg B`+C]1n` ~ab0[ כxe$)Tvb Lq>=xmuYVPkC?$y I.Լ`QP̏}L:/ œTf.+fUP)@js/8.^"'X_!=4뻃_R#[|,?|Ӳ+~ƾ1Krrϼk?vț˧=_aK@<|g6=}/k4rP!?Bg?*P@ o{ b-=3MH''jQ5pW[p1&`hlUlԄv6kn7tVyE_ID )ySgaU94P`2lUSK)RJ W];8VW {bVT@ӯCa1)oظc͋~o}l"{Bbo삇PE7X8u9b@cc$C$/ dRUhyiՙgmh$s Z6'ɺ`(*CQp܃Ma !6|` F'b!2.Ǫ2c;OfpURRmBqL& K U pMjp<4XGU=smA#^z7pI#&LELGa d҆RJRQlԅ*Iw r29FK;<6bڈP329liH$!&E)AVt4 DQPͥb"=I}ͳ#f۞Lo`mQ瀞Z'F N^wi?T5bqYf@ǟyicrs4LP`Uw2kU @T訤D^h'L|k?o>g>sz[c->ē>*|u^\Xx}3QPa?z^O7/ElÊ2pw>_-Dg-/6OG/=5c3֖~be[NIw]}eWOV囮x{ǿ٭Z\oj!3^f?Wn={q"o%-+[)[ωϜ?*P@ _ŧ/T&A6sτ8\hM;-*QʅbަEJ]a*()[}& ,45 $cW hPnL)ɀp`lEM(DGqR BRAif1ʐzK&/ |=MͧbHSԐr>+JUiԃjӞdG_ gfHz 2nuK!'Uir}[Θ0xVNcɺVM5d($hCVaj#`Tqt)K) `22m`=('^zJ`r1 z< :ժ$]}%ē Y}2u4+ ?[ǹȅ{GDh/X ++SZU SՒ\ @I8YwRT̨iX'`66v|ږl TЉqhb4 g|.W.Tё.8hZT<М9Ǜ(F+NL(7:4['rR$bbMjbt j]PإVL8b`\8Q@*erUUJ#b̤=eQXaEo\25@Y"K3='d']~m p9G߫K=̛[|?۰m+0vDV~a\8s4 wz37s:g_G>Ez_z~nKuMuM_?ۋixį4~hO<0ϼ[&w_>͟U_x;nͮ[zթU]sEjW}V{t ^я|C}ģ=Q@ *p/v)SD,bVM'vP±.9q2tvh8%P489@ I%~H CD#فɆF4))"FD& bI7-Pٖ4`8O{ˮF^ݰ{ډ1R45~NGu r8P /mܲq7v͇bq}VAA6L hT=~tr^YW9x%Vօ43sb8G #IuT`A!&+3k#iBk(-d傭6,X|[,GS `A?IheLmv"^0/DIkp"Js;}۳ "/9b!pvWX3S$/ TxҶ,ld{x܊W?ű!Ah>S c.X+O1{mYITԓ A9|1߷y]BEժgsfPP=x}7&;u w6[şa"TyLF$DJR2pw-5qsab 6 jq$XTi$ PA{8m0XI{d9н!:Ә$˩^k36 :Gt\ri5X|JucoS&!xå{#H^-Vϴ7=fr*v:0<0ct,~1B@6< %R0C[Soz0V3v$ ?bD%2k,8qIRw'o8#~0;%N^;6 0C]r)7H y!qZ|{K:޷+ӈFZ.͋D0`,ИJGhn̜e8-ў̥ @@C}* P]g Te5[&FdHgw9hA9wmIh0 ͏c7Q*BumSX#&pd@;JSoLqh{_؂?cz!R#ys!ynD Ҩ ǘVG^~q qyUkU 9~{ic?ޚ;N/bbf%NquOzVC10%rzY[78Uz7n^r >v+_޾ Gucu:f-{p'qoj\|J {߮aNl{dO٧&KftG&5vŭTu8Q^pޕ}gFӱ *&R\yh޹as<EmUVZjVVԢ3H vVry=uޢUdlt<;׵V:dz.EyM_}zmt־ojڭ,(*CבhJ8mzGGlNAp7? +[&%0l"S7)-s[n=Q 'CK޲ 1zGy3Al2c>Ώ\ԢH8ݸe3Mͺi|,;DT6X983/5$Gchόeߵ{G`Z,%CM3g/zy ag&@xAQy}J n^ nn^vwj"_|۾& []puX@ *p eA,19. 6M%(Hzhn47|ޱR*;;[{;&%إGTb `GM'_°4,ňu!)18 0ҼP5 4u=Jឦ^kKnwB*| fȑu13^9q) ؤ̡dcC}k!'m< ./73b0*fDa AKz:}c LNuFoϢ'6a >ÈuR@=# (4*١a "H"xb3!_u@g;UiǃfsŨ&chHH# @&SYH.Ϯ[=x_:}BxMwHRiW'%0!D Q=O1K:Eĉb%,2,Nݕ|՛؝~ٮުz;jaיSbif0:3@z۬y}u9G@"צt25/VqIuwrvR)\/̂A;8MXhvtƳ Lx6=1q,owp 3G'(eräB)#΍Q9Mwvn-m. YԖE}c֊WU1-)}]zo~[K#ZZhUUV{T̛_zOЋw_L(.S2:\L%"&Koϕp46#s5=d$ m 8. PJ٪+h7Buv3=ĝGH&@@@($,BBp-!Jqlh4$}Ns K{{haZ6}1!o ;ٔϓW(lG.U7>UYh1 D_tsymr'0UtRd8<kۤgf"cXLsOd8syϨ9H昦ANΉ Fyp׶:֎z"r w1(ݠ]Y <t|~M=@J]@4-k 9yqoqBp;@ M*8͜fm$*6aE6< lRNu޾7t$cV~h;M0TIFv$J~tL fz:z#059 {N\ur].:DK$^ܴe;/`Gp? ȮQ(bSw3D5'pOHF+5G(B02:*%3NUB4dw*xc, Ôܗ;G?5RNkZ? o4kP_e[kUUV_TC :=a:/)ve܆JyPc#"JƜK'fo594lNTlJՁ8v;i)'2ӂ C ؞LX 0Xж lTP+5PX*p#i#9rTwՠq`"* %.\:Qo{:NA)&Hz-$.y۩AMCOG;®>dIr' WԤ ^rq6Qr5 3Ιl.`= Bb|Nq)*$3O_RlUFo֞h/8O:I P?(b!YH׏J>SU,]9R y;$*Onknܕ%y+/NMJ =VIOwbΥ]=4ū80p"O<.JuȖ&>9,;+4>ڱa[v͞5F\opu0\~csleLc6;?FB0RY1vÇ0*p\FKb& wRUUNg_jdZZhUUVW*htLߡXNzM6X6'S$ˉX*J4#6FlNz1;q9 dHVe:X Fs byvh!$ [`>JEmrj̣Ϣڄyt byc{‡!(TY. 14޵.>O"P6Ɗh:z-ǠKo40s<)pZq)9EyFn1;9Yxл4Lj;#߂(|LA̱ǡItpݒL.Rhx괇.Ftm[=Rl"( LP;8*M 4:`s5\{tiѰxط& GOry V<,$],ta5,7Sn\NN޳E?dY˛v0eHqe815`Y\--H׍t[R2m6}I%>IApLWe[(zf7lۄ[ʄ~ @Z}E Cl9S?rzɁifw6Q#>l'&m|0m)>:p_eBd%=Дk!xA@ytF>@,d+h@MʢFr,KBLyUTV $A @&S/U Tg+e;E:' c >0 /G )ZHiMOwЈeZ2K[p^ 2+vȁ5\լ^ @hy;D=gV|XoƤZ< /Xpiww'>p{yѿ``'mōnno4E?~2+{=w׭Ǟ ɌKhz}Lv[1?t ʇ l4w%`wwC={Giڏo?M7wƞ@Оz7x;eW~}g[θG|pV/L7\mOWt~'b-XL?rƾٳ5ȭ?B{tsjaώ̃mmUU״!bEUPEҩDM$٩b8Y,5 $ξIO@[ ׯqFC4eWvUƶ؅n18.پy(%*l3Zbexiꎕ:31Ky=̡+ Oy q4.݃SQGUB1隍Kށ_4aIi lܪi7$ -dGԷ޶{}u|dzX9}]̓fh" TLfO+Ho{(8Um7rTOj..LHU1 5&m BИ/Hfol8Pa[lCtς7$U`8 x4Wy\mKR*ԋy!CC6j>R$Ch- Zp-%1ķVMغ]}*uJ`6:baWa+`Cp)XOKTr[cj3DZRX)ەs_w®. A&{߹.SW &gJmC;~n/#gګn M.Gkι߼؛?3Cz3+)#\[}wycOC:_2[PG=_Oodw߱85ܹWv_|\XցJl<FИ%XsH$d`ѶH2U ~$~}EZՀooe'UuzENu\4}ж/|7)F1S|05 k}] b'3V=Um7%11<*a<c LIdӴkud,`aE$ FpBk=O!QwG+(NUHi@9T8b2vpD]aq !aCiiE+'P.q" 9<=(|. d zDž 05(qrtbbX)Bе,ԅ ǡ N(HMBmr6YDF V@t{*3BNf-jDH᪷Yn 8:aP' vΙ/KP3tB^H{M^mV[b IDATv[=?\|μM#5ej9'kΓx);hMz?t&o[,.^u.V_uA3.巾7NzhM8{S? aZ(Ȝ˿7ߗ1zTi<:yHîwumߺk'V}MdTm]~Ngr ~סG_"|sN#gxSͷ3m=u[7[H#z%nُ~h%MhZp%ߌ>-~;VMVq>f6?_Ia`Ni.9L|se^]OtDmgZ*Ъ f$$B̡Μ`Sڱuv!SÇ#`;^a3lN+(_e@9HL2 Ou0hgiWɉ9U P! 5d/nfj #l$PtSr!TɃj x~ Ccle%YH\$;E{6vB=hÔYZL/bIS1 iejr糿[J$;N:]';>2<ȅ"O Φ\i811KΜ$ȴ*ڹ@0JH#ZJ4\pijid 겟g!qԄ a;,lv]4]AocN%Չ5E{xf)z >Vu;2JQˠ+tBs/Ra%_su=fSK_sWU9߸F>efۉ^exe"6mIoŷlvlMW#?|fVi_'dցb_~`A}|[=NQMo3l8fuhRoV&ݻ:3EmԦ0oЭW>ua?} 5>zEsov^M=m?zC[.eҥݓz7qFCfm~lUU xk'*#rꚝ O2;`"&1[2E]h S{&D `AR_XCT`F(bFȬ"NTeh!ИsLQ<vٓ gd`"lE#/d{sB}qB<ݧˍx/o58rj&,Hyx>. W8s]@$bt@$N:?|dK}.ؐk da wwKg=w5NMluܫ/SHyI}fFPDaLjw{x[V0?von2uv6~ͳ~XT~L?P{X{{?6?zٳh[;Թ/Y|Gizzyԏʿ~l%u.}Mf^s{y?ϭ *UVπ)>Wұ 2 %#(X}!)/1E<Tf m3T>m=2bnMnvØ"c߆Q=G[ Y4"B){)VSm [=)1(gbJӸWX94 {,18#mVh+:r}E :fAq@ ~`At16L: 4sO)SlH%(C^A4=b }uiqֺjTQ wL9Ai*8V'8&0W-ADQdKE7p vocPA/ Qmݴ=ީS^BdT AJu): * jJI`vzH:iBnhר2|m*)OtTpk !wܰSbBxg2EYx3X}ʵ3DX۾-u}tCeZ 3 ^ ],{9h=p~[=j? {:#?E5i;XMX]9|Q|ӐC{z-⎓woʯ $[ۖf)`hl~wOr؍o35]h77|}򚻆ICs+Λ,6(U6gVK %/OSQ 1 vɅbJ)_[a663 f\ R#їZ U m{{FS߇hqTC:)9z$d+zO/J#({p * =s?oۺq j +ğg~W߃w-=f6=|Rr\i_Ác撝aqD4dG `j&c9>60CPz/_wN J|- 7rE`$|Efdw!feG"]FÍ,2/$]RᖋN[6 SYYF{.Mz RuLCZ= ߪ:Q+S97;gӐbG驘K-a,Ց(rT uƨTRr5,+C-#P(i 6`EFAOD_/jba_w?_qpp~d>h:/LV5 !KP T Z9@Ee $+J&=JI\THOF(- Tq0Q@ MW >1W8P !#,db7 Q "o4gɬ힆q 78/ ׯvn1SD7f[4\N.S&IPcJTmtUQ۵ O;A(1j$P!@D+)XȜ%`5*g5o&=a\'bN TFv @?J'\6TzX5ΦRb &&/QsAe{mkܩ~ܳ}'6=Uf(ySm7bU>G O[ڦۉ6l.Ã/g0ʶc|ժE!jtz~F0H4)J;co}z~^!fͦv޾|G ̉z[ԝh&X@ؽ*|nf!ڇySzfJ#/[bWּ?}s@l;H|ϼ4y'uB24~4gA5[7[]v2/4 o[V2g>ޤR%o8__uwKvl"5;_~}Ö[Ivg^T>CO?wDִЭ *W4>>999UݩSeIOfKҜ^GuF3Xm3I / ĩL<(YéJ6ۻ%cbȣ4tC dy}sWK2@{E;C9buIj`ÖY5{6 ,C8;aLȓߺx+y@jSz?q:ΪVBOGԴR"uɧxs>vUe鱏65;3|UA]*-|U3Wt4(XVƟ$kHx {4]C&^!nTGQ=XQ ("g27ҕÜJcZ81>Iqz8VYDb9rqD|W݃"+Rʭ׺4P*)>UU}Km|*0k1ImniCU4=D60L0 Tlf̯Wag9Qp:Lc,d`&5chm؄V^yr,KBtl:2 <- +BVgPẇp:;i@?'t1웨 A Šgy.uウ/qwo^7'$~Gᄌ[Y4v%Iח}Yd.t7,ȅ>"k$ޛ}_+1= Ϲ8/v^/yhwF[<{[xGru+r^ m}>Ϝv?=+5oqë/g'{/Bw!NW:dO6kg!:D=hfi6fcjVoR3=s{_ ,G\Ur|}#!+ghk߼}1&rs_ ŏX~z.K?xkHmVyVZOCK1C`Ԉ2AP.ºCĦ\4*L_:>`XhS\&:Q!FC5C$hQf[׌\2.œH E;eXB(_`*EWMl8 Zb8mIuԬc~0:Dh5Il&)ˊ:%f4؄#hvD(uzYݳ#:`@ˆ"Vdg!1A;-YrvqZw?::> ,'TBHB>>5lV$ѵJYQiM7fEtP)z,&–WzH; ! ~~4$t8zNM@8A `Đ>#яxp ڇ̖F(ۛ`4&'%lH|rIrӞ%ve.`Df+3c6+=[(J SQDB0N_3S[ t`9,R|1lW@I*.(t?G)~4[RIGsqJ.OdDL.:{h1#[d![rT<լd殟0Gu_C CSIF,Nh ;ʏ,$gԊWQ+X~'M:ɶ&_lY- wdeHsx'i` КT| 0`pjQ2; B*]CR]QR8J ~BdbVCGCzpx+A & \L|<υ" )G3]n)Gas1ZQ*QlleN~S^fCIB=*.+ `L QT: >&xTuaȠl3H"#դV)ήgvAʲt\⼚;{x;g*ZC1\<٤a9jOLQN apE;! rx@$p^Sfk)PjFSmj-1gq2jƫi }poyK=_՟'[hUUVZ,Lc=#-FsCY,@ ڨio%BzKTDnGV.,kV*nib` :M@3P8k̴!`B0 %YqaV4PVc- ߣ {ڞXܾ{|d#3 9do/SKTK4Scv^:0U~d@tI3J $4C 1&nx.EVZ1^k2KU1AAVU;W"щMR<Y`EJx]Ac{hy1 Dc;yu)0g"8cq90yo$)`y 92&zn H`4 =bÞ!A(ZK *Ъ@K+@{(tARa N_hn8WhSLtMz#~#ܤ FgU2m1َ67Bc,ɤň=G.šl$|A9jFlE жþӵImf|HqIgw/q p~dNS ҖSpC撈l*N.SX!gQb"h 'Rg(NkU *Ъ @;OlREVOP7,d)</4IS/\8".rLі22(VSBD VT \]:V΄gCcLF^9 grsf)Ct#(D(YѦH?W9]iB0F摉<ط=yǤSztBN4,KՊYQTUQ]OWwx¦_"Ia6uOt&Xj'm1U0înm6MA$ qH&YL7S%*lBEuT l(9,8x@*=%pg !qڔ^/Zj}IWk^iUUVZ_TЭ">Nߋ0|Ha@̣1~2iI6kFXYSOӡӖ8q~P87]X5UD;i* I*S*  0ߍLL0A > F_ EO!n ? M #y5`Z6FK]X!N&FB7XKi 1x%FgHnq85)Nϙ_UǑK];vy ]()=8ZsK( *Uy\`˦IBbښP^. ; k&`%!Oj 0v>l8@104ecq=>cs) .e1) th39擃UE#ҜpxFy3K # !HÝ\26 dlžxr5өNge)LJ}&LDZ01&& x)$n+ADhQp;`./8lKRL{{:z%| 6<uDͅ $en]݀SNJt6F2VSF]NwћN6ekiUUVZhUe @T$=9'`Rn)YZ'v"k@aT#4!/ds⨘"yj`@DwыuNXZWJɕ10Pk, 䂨&QXSOu~nBѬ+媦!Έl hQ Գ^Xn{ QB㘚e0 ;uOف9NT,!oSUnnܴibrvg9dlx /'l7mE&6"2ڄԔ^5=ȢV2Rl?3I|LSJtFƿϫt\X?8|]8i~N>u;۷ڣe *Po(U<| }TH *!Mύa|f}@iUmYű&a  V(L2!XLc&g`6W-l)FlI QQCte&y 7&sF~bPꄋ u kc#M2ho#dƑ)$zJB>Fv5`*v% 578iە W|/g'I똡Ξ7.;6O%=,8'Ӯ㏧+^oy]Ƕ/$Iyί.eUۼ5 *Ъ@΅nYsd:p؝!]v ]Q*rLF DDE&ww)I#/QO LS=>H*@ AeňrLaN{f Ď\ q*|c4TѰVqNe13o4zqIl^sR*7Xu7Ⴄd6 W"(YfLf V!ŮWGwR!+3 Ț毞+`o,k"^<lsl*3l%K0zo>Fb䊫>'^v7ܝ}{>qۣ;Ol&Z*Ъ@ /ppQp.:fiy8O}&9A>q u *l (&SG-JmCwhoc20iul{*Ϋ&I:#ny-+yr*C$T+!˵IpWŤʶR賂 P26&f.&GZs16+lވOJ@:}=t4P+ e)*TF"vt8QZ~3/:2+?6YT\\L>z2oB zuj^m#00q,! sr5Ě_e x3ugvsE$F|>ˍH%jbcRN`b^谌rH-NӰwYCcpT2S4+ˇ5E TzoVhK(bQS -g!եgӶ8 Kϟwwd\̏ HpCWۼļcyy+3 >quӛ6/4Ǧb"uū o6T=r3/yg.vtoj#8X}|xŭ'7? [y©KE*;޹O_~ï_v>zQ/1+{O`B]Ay *Ъ@#*$IAtQW@6]JbŷSg`-D@jCBr1$ʢ^P2}R̓<{; "RJl2@Y]pȱ(( #MJ5Of6[pQhmg!O`` w2"}pV:ӕRu=t;2eԷ{-\DB/;Fvd2`:*n/,'3$y乞( B6 r}^{RVbV gxJPl:E*p(Ѽ;v=.ub/vi!C_pxDzl4NKiW@Ka[z}L6ң'-7asһWG#!kSzU~`_i0h ; >HCDﮯ}nm?}!x.I~fsw;NERa\Y\wytg|N\nΞAU:83Zժ@ *yЦ+RD"kPg4LBy yxkL}H T2N&ׁQqjOUnX`NRS|Le"-R2!ABD>@vl% (zX6D^ %ٮf0"?ލƂC LF^@*@y# DT=ޢrgpD3Vadӎ>F}H`sD!i4QI/xwr];Ӄ;i'qVkS@XUm|!:'+u(A KRPKJ_Y,/&q,{ R%Ǎ5OZG:&sD:DN{N)o P9iDs}0d,:常m ѽK2wd1a#6(c.0#ֹ Z87"kcƿڲy<㴵!"C擦|F \@, ?(\V' D(qDU4}9)PJ%kj2#I:|cq4* ۔+pٚKU:!J1^T=]ɱkvT1`L 6e2 '_7m-57pt blE(iGX\d"ytgF;lߝ-X0sCvu<"BXMMP=ޛٚϾlujv7lӀdf4a"Qc5cyɨ8:̌IK7~]kSg}}޿M3c^x h:u9u~兇sGCDi&Ǟ˥<}.m풁.lPpl9$RKV-kp[}bl H*u.hfq\H}f`nH FiFR9&utCyb+5oNXk}+'"hY{Do㒌*9HEA¶W7=gCF`u|yyw꫃/J[["T/A]285/8s6xmX5j'M|B,Ӓ̱vG٨z 7,iQ[q֬J j<KLOkeQΞ&X?H}…8 $ @*L??[q+ 7кkO |CKN4/P<9i/l웿~m;ۿۺ~voe6c_ߺ[|r1XL*)J)5S,Vה˻2ȧTq\Zxrd]glPpz޳+7Z{^5юU8Jle;&~QD&%l]y oe*lAҰqG ;hR4 YnzeΕkxgBTd}U?g_ݫ}ߕ1⮍/{ _+ܙqLqK;zYtw/̖;W~/ɝ?i_Z IDATwO|OU\k-] ,&b M<^en{euGlH17Q@ڡ#-DP#)n?%i_WM% 5'hN(oWR\:;nq1()^=Yu=aepN`?6mZ}o(l6Ϯh 5-* IJ< L`jtI%u:#v'oOqR'[>hTM\קL,zRw 17jwutW=y7"SE>qdͥZRhA{1:x#?4:(L9t +3 \ʒUx`KS]*ylCv9`)ׇRfJM$m%R1L;hʼnVZU:퍍Z/c'͛W R] Y:J=KQWR1PT5"ʆf>Oh4ggD=}[گ{dI+u~_|Ͻ>ƻ:mo}7_׾7)<__Jέ?~_yQ7|PO.&b |M@:u$E%5'Yx1ߖrۮ| Ns1D C.OAL}iUpuYqgJ_nY֪зUh7O#4~D^!塣\'^Ӓ(1ZmB"`E*]xK;:t<s\#j8  {ULl^eBRdz誩tz=~lV(q,xNHB\h#xѤrڃvkq 2$i獱'3#媳/D\RkײbΉDżE@]t]UjFY[y" pEqL٩nHe97/'sRL'^X[QG-E^$}K{t΋Uh`9 \f8 nZ:Q8<#"tlEb\;+yDbdT{HbʩHW:SAꀘ||H5{8/ݺ+%ogIS?{3}3ܦ8'?e{~o^я>c,_kW^-&b uBÃ43ԘMA5́@8BB TrH)GӮ[{Z?{Gǵʫ[n;qӉ&?P`lCo3I^&ER%YQ]Qc3qd7QdAEؗfz4, ,ڦ. bdžkѬʊsf<>D @#`S)",>()->E( V`T^9|)d%@_'|򖬥HuXf;k :n宭vvGnF׏[8B nR(\/ՙ$,'Hp>)L]IEFbǴ\<9Ne5ʲ&Qp\LW:32knVFj'69&5rqsȵx'նms27lc:)dH͖fVrqRĂ)HWJXJ;UCyF?9dC<(Y*R2u0깔TwيVJWOFgRO5JB(.=8l7+sN[aU0XI:iMybq0 ǧ=BDLv88O5n1Qu&3_]a1XL`1?O=*c`C۾IU@`M%ߋ3o*sEtcY/؄YEvWuR޽nfAƙvړ>Wq$a3inKW:gVONψHRGr!jN wb&Ph85AC"懄BhN>B C^%iSه|֏ fvDB幪6=ul `ۿ>AZHq^ jCCtAM:tpn)p4 I k˸ vJp'FRPu/YEuk[W݆G1P ‚y?g05Q> fRR)_ť@;êx:dxJX`u&Wf)U!LoOr̮9^}r"(b[ &O2΢8ZVY(;z۸.2ZI,-[SmAA/E~LJkK\c~cRm2XL`1'鑍f kIic38.''JR#T[b-i/|a}O lYܾb ,&?'"7MäzYs[U45dT`Zps%biZn'1et=; j<_f=ٶiPǶjxxi4hL]/]CR.`( ClP+OWFw cpoJE_+=XvQd# pA r9.r\sUsWk2(*SlAb/(EJwWL7]9R%+PK6f KnvܶevգѴqjݥFn@hpN(*,||nAl L= ,o]spl RӖ^]cxʨ O]ۚjM1 NV۰|X?kW(kO:c`ګVrН~lEVJ #W暵ֽzEr>} j y/nI.'UV5+2RPc$"]L/5*WFwe=˔Vڵbo&a{cͫt{Mkn#w{2%+mRu)(5a ,@p}I7DZ?\J>S{cE*4e>{n*)&oŔ9b(kcZn*ZOZ싯 *\f/o% 3LF-7S ,&b_e)6J@xN//w|Sh4ή,.ewdq?(B5EI)Yu7y4`Ǿ(7rZK7(T RaI}!ǓuΓ$j 4?v_a!X>Ku%ZYn0Xy2d;Y_6- p&;[fv\Ċ:v &]_yb^6X\%JdҌ˚YMBE) abM%J"U?'afij@\{Wfx+pC..#I-צS-ϝyeI)oQBQ,O! 4iMTAf*@Ǎq:-deylxod}5 }̀I;+b4J,}"ˑ&wu۟J==BCd$@ljI gPf>N/e ,TlA lUr;zS\zIYwDK0荈ůҒhR{$A\fe!hhNOIU\ϒhQΩW'[hl[E#Ay :Qi B0#q'ZNk%.RhYh_]jǺǬ 4-wW3'磍[zZY ]=Da/7Zw7:(9xBYn}۷<-5;ѵaM]U>lde91qZ-)VtL@96ӲFlV?mIx*,HD\kM&O@U_7k|T|F.#THM7?C?ۿ贐xOeOkw߸XoZoxڿGv᝷}{o~˯zB]yGs^,d ,&b @!X~l}\[>?e8ZVkQiFv9UD}tL&~V:(<yxnYe%WTxDW-Jo.S6 RT-cu`tڤcO^I2v@*DPTFөn4=92i0BRLA 7tkf %,%zu?%[;v"~cRel;pZ+KRuk Ҕ *d*V ߓڡ!^5TZahTd88DY_VqsC+^#|Q5Ƴ}brl5ɷx囿뎷7xu߽rTQ +~_,W޿ۗ'.=÷}sԧ]O7;ڷ|w}⇷b ,&b ͜ %Nj{9|:Vn?ߜGvT஦zQ9]¨xYI 2+;}di*aLQroXG C«CrT䩑/qHH9U\xqf 鱸{pZLn߬24cTz6Z7m&Zx g5n.徻zrbC10*9Gim]vZ_$6rdw bUM&1oeCy. ?;E'aދCwPgN_]UVH?CT$q\HҊ&Vv{xdy|1N!] g}^5^ɮ#j&KyouI7!RAp\m/T1e B/Y8tq,ƸL.Ⱦ] IDATFG <_E˟>'7v>u>ͥ^St|^9i陾 nS:Gw>_z__c{?׸Gtԗ,&b ,&@* o5_qٱj/@QwEƛ/坺wkuFSF+y>1΃|>DliP^(ZͦX:j|`S8$}O`;ijYsO2L,LZ. mG~wGyyʣB&UCF}J^s7 82_ =rHҨA OJoU_5Th&eb9Q M9Г&jȂ]^ 'M){dWUf4ϓ?#$*PGal|#G BMJO\miY&b,)H+Ad6) $M敂GGa-YROQrUFL۠ hWs?s{M)(DA7oO[G%ÍdwRn[.>:\\+&(BrW,Ysxkuz,Rjs{O\K-;wikSWP$+Lj4g1]% qcuwz+=/r̀6N̚l*@jņvBہ q]UhNRDq:OɏI,k 2UR㯨ݒ;! 'X kFîw-i5aHLo݃mqz:l?Y^*1ԉWN m*+슙3̖ چg4!faJ'M6Pq."IFI =Y]G6n!cI&yt(6 ^D2TJ+}[҇w?[jv?<*⫝~S vjU`?>st{ K Z۟^7~ÇW4p{#Y+ooXI l#\ܾb ,&%&Fuv_[ X]/MԁHϪA/zs=‹f DTRTvę2uN TM6K[)Wkye)O (8ҹOӐU$ImBzilY)`MÒ`VV!}J|%K[#y~A3|Ͼ쿼ꏮF7}?o>MO݅q7daq!ĢsBU~'q~rU±$VB ce}R*vg_ydvmtXZZ=* L#:~7 m-R'i@@O R6sDRg]r21`3,f 9YEѵē/W1ݼ0' ucS۩D+%uJ[3 20M™r~0%O<&-|3be6P'3leZ't/,o_XSMp:)Sp^S t]By0ؚ&{miԕ"kg-2 x븺Q>yy~^40 / s=YNt+ױRٙGUi]$W_ ]D:JPV%(dD`޲O{-"jb^}KB8"vՔV=*6[eJ$УNGRIZeJ-U^$P*RJQ]KZ-#WI^}ۮ%蚭%̺]ӳkpi"ly-7YQV"*VMكAeIưrH}R5 U\ Bx$!:ё]5`ɿ$Z}k艏7vMѿ:w_s}o>s~ ~ɽ fv{;_Y*W$ Uj op~WUO n\L`1XL'L)s>KvM ͟ m_9L<~ g=_0 :)Ur!Vl5'h4 *,$JL:c*Ǟ{& q-OlsEc;^:U"em.-&'G.`ɇ:bяS (Bhd%f f:Q=9XvƓ4C`;`D@§de/hP]lX XCDmtt6b늌"VE<˕F2}" =Mn9 Y@bȭ>Z/UA4K"AÁ^O|>kDY½Q#@f,v}RDh"eΝ[n]:+99$Hym +i{Z#~3o.4[a 4 ɯEv탓]c y>v,y p}cxQ;p'IuαVˀg+`7)h)Q[olҜܘrg]깎ʊ_ޞWxٽj|sO~:=U<=]+LW ,shTX[gXh2\kvH3QPaT XE<hy%KUF~7wwXL`1&p/\xa6,-\I'vp{?ua6fIzz$i C&xb&BYTV ;AmwҢ퍳Q3fI۴mcm]YBva#T\gu8) ,9˴U&l p Bu$R`4#+0+٧ݹ r =D-AWUN]P>=PSB(gYa"\mœ dc ZC xgB"t.MWdHD(Jq5e,-/"%TAs_#+>Dyx0I>M/韝Ee\:F%W4ob˓DrGﺀ):tRoHB)Ϣf(LduCRAI\%t$,OqH0^_./-~TiQE 5H,Ap('.ǠU9yޱRu?હh4M M ciq@Vo S[<9P|{P-!q9kA䷔φ>Z^Zm{y0ˇ{ϴZZOG/':yJ:g+|2i"ߗRcUI@W&?4,M$Va)^OZC45b Ұ"Ofarwq= ,&b Ѐ6A4:;{p)) 85Ll,6$LQaXAqeh9qμ3|?ml:?lnJG;%9vq$~ ]>'ΌwF`yy;xvJاk]ɓ/lN$SP  v{Uh"'KnZv.R1%R `ڔk|shG7@y2!VjbSȩBFg׶ٍ˜Xhz]6F.)APL@m LxP݆0jk'L=z: :0Ƥ՜D0Pӱ*GC2NtB|ܑuO vSqO<#UY@t>cDh~u-04!A&zO FB2? JiGD)-/rdӏd.]\Bu{^^wNӢyI+}Ғ<goQ4':?>g$Qt߶ujz}ݒp?ymҲf~Gss-ؗҵYN]痮m~vZqpg%ag9'UWr"n̖Ix'qz X/$k?荦#i@g0]و2!*ea&s,Aؑ":ǠkUE1〒Rde/$ UҫkV`+䢜D $p%@T 0DT'>W A=lLC ӼU*m[zE-&yz|(wqzpv7[e j[M4Ѹ*]'jR.vw?9L+ '5!z<],H N$J-ajwut:o^Jϱnk[D6htZ"\g *r8  yC5TՊDyE\oH~f#NF`9Dj1PM[npsM}ɝ|_[=Cy8^k*K~cmsY;E>sxB ;F<)Q-Ņ|0OF ׆(=U=2v.|As|ڠFse"?j᭍f4O6wgS&JحqEXBiLQ܊phxzʜg-f)n$POomƀA;mu,& -|/\)agվW 4d\$: 8~SjP6!vwhO>lbt 5٣`J_sRr)X-5*T(=B d4 7Ҥ.@_Wb ,&dN@o~2X]:kMy|ŋ~?03ۤI,JPSh/]Rud|W\jtq 2oZh4Y]C|)Hg{W־L(c0b#w!k *GP'gOV Γit\Dۼ2rˀIRQɮv:o`8M# ?rx\ IDATsC*}h2#H&~)σ>WbxلruThykМ[6G.dNp8\FQ&i6y7?D'lm}S {Vb$sPYvǩnVvB/yUj[Bzq>-1+Hz P$@RĚ&=&B hx [+6g7 %+5+jf8)9ϵ=U[RD<mZW3D"VhtgW.=*/1ZiYZ,gqN֜j0 1-O_ctB^Cf ntt΢VRL-O#l.''?n땒*LZ^.;:nC:`RIa wZ QbEJ*:pu‚ oN~ޟ Y|j1XL}Ќ%ffxX̋qT2N}76n=ջ{ßZ=,QdC m$OTu2ȅVi>0:zav|UmQsZ$)l=2fNE&ou>J@W}dR Oy"F@CPj߭D)@Z9䎦\> RQf2UGd;hJdI#´(/`Ƅ>VtTэ6jC+"Σ' uf 1@LA8d'j1"Xʃʋ__x~є@wwHWܾp>piRZLz:I5%Ew5._ڞzj|n(KfT򊸇Zx͸," 1&1L1mT Og -9FG "`4B!(u;ؙ6蛆"% aXOq+& w  muVzD J%`V]%?gXHĠ)I]8TޮٔEzΒ’O#Tӣ4lC/j]X(>YmZ'wA28dcAù\mDd)*R5*B  7/íQ!~DciH,!@j~?;~GCξѯ|iz)wp3gexN\.[~ϡ~TOswȏU_._b ,&d!kj]ZT 3: zP12:~n}wKGG[cj&7keu@6rxGS4ьMa׾`:}2;.vHDcw&KkFIԈw"JӘ`=$A=>N eR!>K%@rOA%t^B%ˆTOiJXxw{<}t*^,*0Y]  *͸ZN*Uax1 'WdSI&W%2U#$(L?bL4+-jȚXrS,:}QtLhCdL5p^UtY+gzC/OU(P3 DWmT"aOrLATq=]x*ѥto{A.{Yٹ\ŦLߤ3r%_͞AΘYF> ^D&DPe:hIQ*k|%2B-@ o44 '}ܣ_WCt>Ct;>}i`#~,^uO(bЛptS4B-ί_/o/>O9#U=W;?"ZL`1XLx38YQTMt" \$RB+иS~uL>n=wm U#,:!dL=#R*.#+m ̠ x3[}0x갯l=׽'Ϯ;A6&҉`$WA_d=~ )ņ!. MqNU{5&@RYxL%vi&Z)'NWzlޖL't41M 랧!Aݯ/ۂf>曇%qh]9rւB a s~pјSe,`^$;ݾ/ Ov$( -u[CAsƄda<$e "I߈kLUS4dF[qogGuҸ%KAH*D]_}_Iɻ>g]߹ <ۋ~+ϙ{w{XsXqsvk\)yۋN?=c_~S;56e LW`&MÛ&0 LIPJ1~@Osn`Lko'On2/R0EraR-L/9m3sjF4O\M%J4^b3I!}hp0 6;1s rzY\LH(BjC(tEI$ %ySUÉJƦZ(qZB|) q} f2J{a0Iפ$uof4+$3D[oeUn6$zK./b!'K>k?~fctXK1ƩSp 'n=9դ Ec/ Ic`u*KU2/pYtCfRA64TOأEC@κzah|3i5(TevI^pL\]=16#-P$K߁l X&EF77KmJ{ҟi B\hzvU>Adk^N,,Tv('Q( hšv5d⊿F"bl@ t~{4 |8*%F. KFRw|Q#ГNo6f$&)V TF bhZEja>6)WRī|kx L#;8Rl38:TlMs=;ܸO"jMWo<˻zŊZ/?p[} =fSٻ>z ?xK+0] LW`s@̕3pxs,%T ϑā=7ƅgφh7 0vB!TrV;F IO|,_ݾ="u|219Yu$73IT$)& >:̽=@"Tg@3۲~U C, UE&BJ&6bRD9WINt%7-5Ӄ)<5/l*@ u* d"'` ;H}VNQҵ -DZXd0(XA3Ѣ"Rx*9 t?utl]w?I4ͬt[n!^{`ڥ@*HTVzױ~ ;pYTp3u8>٤D`Y& P ^0%VNC`S`E7"J^eNsqIr`ʄj+fX$PE m/0Vt:383]%؛~]ꯎKۘv櫚 MmipKDԤ!yY'܈)1tNhVlp"Hֲ "J^9,lmI ?U"Ϳ_xXz֯ mχO]>o2??Fŧoxr|PJ4W$YXu븁2+^,섦t+0] |+ƃ"i.ݙ8GT0g8Γ02;\M`cn9!cBY$G;T'p*G"clԆۙks]G2PX@J@9 dj{iKG? kSd^˵@RiDuX%KN'X^(Ļ'5lKfW7#L\`y g^==sw~_z|'&&Kӣ|왯\|_+7oy{><ĽzѿEeN{t+0] LW T d]Ry3L v[t|a j2p8`jܰ+W3Vn}AdCji2u- Tg۽`TdSN>'6G INkvHGO*L֌SӎȴcYN PxD 64K0MPJ#ب=Z " IĚRIKƧ<%A[tdDv*@DQ3a8BCdD=(&Wr1ZQ<[)Ml6 WrJǣCWWFݔC|0wujϋCe9on=C=P\I Ef"`vȕ?5-7yWuiTwQtxCPġ6+ k+}O(HSHd ,YO|#UD y %^Yn4 KK()ZH%X@.Rb# ^IGZhe[5),g˶Q~@v _0ӝ!;sx4)w7cZFKCx8f&vG"`"J)||FhV05  =U/0A~L ƉH#bCs"@EP J>Xx<֪tMp(]֗~=&a;z?Z/W^+ٟ fӧ+0] LWoC($K'r"0B1 6y4!_#WjYq'zUfS|ѤkI,=EUNzӐU aR`̔3EAnGq3Gmf(n7'ED% dd2PMXE/E PS$A I"z eW<&8yEC= TVX&hF@E׼aΔfa<% j7cIAr[Ik)"3&hO%GAnr d? k} c?7v_?ڏ'|w|W~?ǽ7>?}bt+wpw3dN:dL>e̊NsL@Ca𑕻ﺸ{q`o+~d(ڹ JVve^ڳ Rh^S|?gXXjv$ {բh)3Yٴl/7%<@ F8#2Kf_gV "$5H)|ՙBZ9X H3L(U E*Cx2r7fjYz1d{(15ٍ܊d9(q|c8z`'Bx>W i$ +1lWC lZt<ُ?HQ؅yMjHPX?$3V姻vA@o뒃 d-Ak]zydGEvey&TRP>bYtBiMRH9 叠M&8H=ߊ@[mkG dP#R[Bb"AfȰ"Hb.`cqWܕ"*cdκNӧ L A rEjԢae#g1L4+rdA…ErL= #z}5*|+LCr*UgNpL>!"^6 0+K^: Roq~,;e!j>+0] LW`DWr(,cJ\Q2FiNI=删RpPSK2N?4ln4TvzLj%*' J%z@0BV!6t|<]ow=' =I-BOJݔ7K[Q'r^!52:.OH+vb4Be25ƬpݽXCr(e<_pA@Q_ \x#g8:08{zV)/0q}TqD(V4ʨ# eNж@͗,WX GV ̜5"%Y+l+aY@BlLMK$&$q&5!ʓFN*"""q I8Z Țm?9"P)d Ti%B> ێ!Lp5;^6ՊC!1<b`Vc2 $Aau)HmLW`?AjR{ÍO6%%gqN:^rGS:[pОi9z$xtSᅆ=>\Q!%H'6wlEx!QGXRIOC'@G&*$spUR,ym;&9%Th6%ϐ(xь5IMܡITXI1OѰ&Vx!T8Q0R .1rJI;ܒc{[[rrVؔ \u $h3D'1N@LwƘ$ C0Q1te 5(;\,.qP2`'^9-aل*BDX ,h-kovxd?idVwO  I>Ul-d}q`t8@Wn?em0K.7jwm/3M:NK? z1 Ui*QMHv c"sa5YT/IZԣᚎ :D}tF4<`Ч?͂<&ɷRkh4(9p3xi'tN,t&c/JRH^;܃Zt4 F6'!7Md;$"v TEڦs;@ra!%S˚qm\–J:!0q[6ti7NW[M⺈5fG8R LdTrxl.O^fqOMW`?]9sњSY׼2>M̼ǻt{.~l3wB#{V,#suLyb++QFo:27RRMHo&R7R%w:߮! a[[͙ѕg0`!TX88+CI0qk YFJv4wBDjt(Zl4{]`VƪjH[1m]%㔰]O?j]І8̖ ԏ35*?䫏[r Ge4V#㻹߲nQɓ3@؀I.۱"" D5Gx鋃_%8kKNl]i~weIIM AKS奁a01}*\鏖CԸXZi5Y$V3&דI<Z #Ôd Ǫ m(<,E?oBBI_UDנf'dM%IMvJdb#6LD =ŭbhd1>NZ7ّ  Bn`pq̢BnbPx|'s[!Q(jtg. )HIA ^jhIg$ "FJt0˗wOA?7] LW`Y4pL!I$Ԝkzh/)[v !e9c5ۏvhgD(nwCg9ݴDW\΂Z=1oUՈRqau\r1cv_g{a 8_7;cRbq@QAAf߀o&awy6ȝOt#Rf؏[WVzzY=.u >KB#5P3~$0`0x:cM-*A̟L޹]U>9Ҟشwv%iC^̹Vk7t$ڞw>.tžȐ Of ;H}Zѝb/^1|I5J i'W#xp{b4/p'aw|Je 3{2}T BTjJ#dxY "H7'vi}v % % ʜJ83 r[& WMQ* n]8~T"Ҹ,tJt> 7 @)IbR0~[b#VT]Q=F괟͟k5f݅r'D$^ʤt&YiiL#q ӮpvGgTڪF>jC#M%iLG0-(/,*Yk^\+_[~1?][u>eO|{_n{>K21^.?_:?^vt+0] |I?E\gmFC2 C,3cqgIyKݫ-œ=6Z^^:q!(ɯHk[ھó]IDZ6eX Sal! 4W`#2 0A3磢iy]Ux œwc\P%vm;`ME-k:̑!jotv9v7wG?F%ȿdN$Hxm漩0y̢!@i0}ywXv1ĄT;zykC{rW'GMTϪviWwGgC.f▃|'{j.D@m+aID;qE)Vy^b]ጥ/θ,mOUKJxf?T%8$Jt;eIP{4YXX=1֑f2nL.F˚].I1ل:2*j5<'*宴3q>>m싼n?F+ln9m:`w{Y\,"ȒP ".3w,d<.[4`/=pQ,%ǘ$M1 wHopb|a=ƺ0E~bwC5͖ay_+Yr~->R\?w-o|>3܇_w{3 3ȖH>I13QWXfwxmw=?[r o?u?u??{7 LW`ZʂM_o{ ǰ e/FU}x_6T Q#Z#q[KU]o0~nC3'zph4 (AƓ4 E+L=WXslDn,LbE*$ГCfJ;is8酭8aXiISV91SS\4hz M&$Qf ApɸU+CUG[t >tA@x$3߷ձi5Ɍ$yp;VQ+repP &u$LӰ:mӥX3qM:8?I]F%֏lOBf@^.XxC-{rm'.&i#E򽰞H~TȨqJ(Άd,Eۉ,XIoQL!= bvlTTPxh 1i oq+BT4a>;`c]rnC^( w;ց,7w_$?=Dh]j,40YY0U2lnAWK ›S,!3ډvZY!Aճhn:F,|UD+wIr 5߻wSy2gۍŧ}m7x5xZUpeݔb9E]0̻\yu=$KW߱S~ 7w em!Տ?q+E>raQG08qW|Ϩ$7|բ=H7$F]JE[R*DJQ79LO+֖JqB%|}Sn9a2hn Nrv+>1]nڋCJPR֟qEg~ЖLrKL->ʘOHj D|ulWeRe⾲bAoş,_Ob4㈣3j1VcUK+{$+&)KH,q bY8H!1b0G*^|ӒeK=R^'KtCI -hRLTZaтAIVzr""d@VJ=&ړ](:m vg~8p<;HC.#PE/deL+_Jtu$T:"Rv)W'' -K U\HcCdYbLÄ2p q0 .X =6㞁b{0I fI"8!٘:"X~C|)D"άzݞpaތ)6},;DVUeA}{W~3/;C|K'--j4D叼>8~lbp\-n5Y蔺< O^o/ӧ+0] LWjK¯!TMm+f\ȁ\3c݆Ml7ׁbfJC^tqV5}Sn8~ӱGaFmζ!sTÜJ+IBu'ApK;+,:.yr*\&U8V(WJ1b1,  2}KI+jl̼Y&+8Z6D Xl ]U72 Rkw<͚Kb7Tf~#ԻS|-ɫl|]2o~\G?HkIU]TQžNKn0q(7c _kwywˁO0] LW`vzgv"] B1B^ҘL\doA<5A~O/RNZ5#ݸ<1=7 PLGᶶҵZj"qvm稸WH)ћ\&H\@=>rb 6#`QIr#B/.ڃ Ir5"܊K\b'L/;T VTRx@y7.&*4rrؼd5k+ 1*##&xZK*T~bEqFTYq,SP)~MJNT6XF0vsj[_CxɫOOT/]W}kIc?5?n?ҬUh5;~5(^>wh>bmwo=t+M|h↱ŵ.*P] ݁]=lEV5V,J [ L H Gawvf"}FVd6mӭmBOh_`T+Kʵj,*LN9Z2DP!-eM0PNjaZSW{Vi,/Rs%|Ά8?^!@VA5rXЊs `%ZPΤ"H9) T;9}@)+M@۹g#Ip[=[O ` Ta4'N#Fz߅y\D*b`Oj_إFIzxۑFO>Pኚ#FA Mhs6 0)EYS"p(|Ge2|FJpe{",6Kt(UaLIBcb$•$U3Vp.Ti%ApW`HkaSruGEn6zm%,F []˷v8s[8/?n`Wd҄ t)Da19`n5(K-@ ɕ0LXy::>+I< 'C A|.os>M Vv;0a̢v%}#l%jo֢ѧo5|LSRFl;m.Q]z5hDjߊ;4ٟYX'=ZK)/|nоg}_ܧ_t+0]o` 1VFY( @{b2ѽv!0,#mu=_ujR1+*΂ `j/y,h"PkIvqju > c yF}3beL5<aSJxbӤFExkU4N^PrFZJۓO@TEIh(AT2KT8pwTqr@e~TEu҇<RLA_V*CwXe\ mۖh) < ^ T_ a/6?f\ @DxA6$ˎD8]sQ #kXwl9.689SE:ֆDo2m; IP87}@V3mVTڮeVɟyD$:f2[ \or+ R ,¡ t:0K@&+a{9j2LMqjcmҊ|]SsnA}W%:ew[wyTl;zotU4W>M?|왯\|_\ڀ>^%G{[oWZ//oLu~u>;] LW`<+P{\d:͠d8iXvq.OO<( %ג'v'\RGԖ(,4pVly*2*G;?9ҠÕ'۞v~n >='ޢUBj03nB ` I Fшԉi՜ ly 7!=7]\KK.{x>u ]NJኇHPpߛ@11M[+5hHz$EفKWSR$QZ6$.I0Sпq)66ca;,>`d(;/f滞›77sk.Ig{ҾVS~M҈.(NT^Cjι ̵uV]SҢ ZaHKYⰎ™ruLTkSDֳf7RQOy [q%Av )`\spk:zgƭ&Zu9^))rTD4}ga"TBHq)%]ރG\k DI0a[Q"Fe*G!rȸw>Wo_Z{w~Aj+xdat9Qh-w5T(O}g^7eS$bﻐl^|t+0] |FĔtq "LP=l!HUtkHdkT{Ihia|ӟ轧+[` 9=:ƚ Cɔ11}I(6JKX鱌Ջ;?~$UVm7=ϴtN9@4 ^QYW)W]}bV8urToI%DQe ) Ja=ꏳ(ҔHU;]Y$_ \svqk[ONm]TNH6}*YIv p k(iƮZH Wx(+>#`e7g=}t$ի}z>3!=8/= ]5olZm`Ggi=zMrvǹs;ɁE4l1C %&0|z C"5F 'yȒ~6<CaMZ3"tLPŐ$c\AQ@.앆>뙒?Nrz_mLfGC~<|B;=miz0 ]p"70|;Ġ׿c@#h5H@xv6a [17J@je0r+Hm -/NBAa"Ŋ,""Jݞ2q2[.9JG,ZmsbޔO_!VZ?_{GL+0] #Np y:4Y]#T۳E#Ȑ4K=92$v#*L ` 2n>~#^꘯|m|dBDxnR$.qyK&J [Yi탟٭?KqSp5 yA! *r)KG"6pF'CQ%bI,@ALWks0"f"O+%8oJ0;ǭXRDƩ Jb@AKKɯ+ vZu$4A2qP v۶͇#;8=YWd "ܔ!qS['OO'$fzI0L: +x1d&5+BZ J "K \6I5i3uQ&i;n< ,9{`QB `ߟ_>釻c,u, ^x;$`cںF[qtR?&~iHN56;15mo@csMń=ӝ)DMYb4أ墢62"hOe M+p֤W ^?48}LW`tV@>A} nPJ0 BLْ$$Atu($`O e؃u~MύW,7y :zOޡL> *ћ|+Pi+Xӫk-7On^%tQɈRx/>9&*)rrN YZ$R4)W0X ^XZWh]/$hLPkPvPUa5#5GVeAUt[GYBA7w=*([TAaL S:ݨDKlUAV IȐ0"OԊ\Q"f*#x@-L(oDR%]N< b#z)G+`$r)ZIG)9%iXN@]Jd@zN_)fSfy+M~89e He$_|C"DjQ?*Uٺ쑩T{0ӽQ7fC}mtP v˽(COi.=n_ełטLNl8ԕ0S4WDJ,4qnE_SB2|٭6i~"Bk9b##a>;4V ȗ{&'+0] LW`+q9!SC6qT21ʘH9 iWxG{G}^QnwbfJft1Fx QNZ2$8"<l+;^xۻRБ5eJ(Uij6蒊GcM=.U|gF+EӳmbI 5C4/hXNǚ v78ⲣGYDnԘg!}Bv JOT\'eҞb4B`{4Z;f/,p Vnol͆`s~ E%JQ(9H؆U XJqyϟ7|[$[_~B~]%J3W|ƳUy ˑ1?o^ۼ w1] LW`\ϴ#QpSSxtGk^K5YfJPcgCx8;Jm;ꮮ8<'( V2`l3zЂy1h `T9 d IԤFh` J\ٹhYQ {O&N~q()/ҡgP/IGZD,; JW:hLbj <+ ,#~jDigK0k• "_nfFe" $ yJfJ BHm-DtZ}!o/$0P8j޾#랋oY4qu y4:( 53-]q@yޗ@'uQ+f W^ItL>0sy };3Đj; !B0[ Ne GP a$VNE[ky+*[]_.5rZӲhlN4w5/QǾE%/<M1w]gyH8\R۬{}Fg}EQT˯' k>ǿ\"_?p}k_;}$N~G^#}?ct+0] LW@E IdgS/ 0Բ3Eŏ@%`}ZI$K]׃1SV} CM=tMlj^׳]xFڎc{`eQ%|  ݿ9Yh7[qڪP3vkYaLc^1"HWƷށU`(&JJ'Q(EX[M08JjƼm'HŸk;m]tA猒s$G27T0ciwxYKQ.00nGP)p,\O#%6*>8ȶI)k<)\PkYәf?,g;JUr}NCo8jA~#ǼHocYU"=p8]RY ."ͭ ϪΠ=QT6:vƃTwv[\sKnoЛ nv1ȽwVon-¡2$'^Ap1W]?0O?l?v2Z~i?W揇a.KjָRf? y3xUL ; *:JT F5F1QP<<F%PQQdāa`ޫ33:m֭nasQrwΙ<2޼iПwOu?4'Gj8/W]~؎?^|7p"@@^`8]Wln3 aŒsDLrAG&ؙ)DR 'xp 1aywULXEjdf<~`cMy(`cGNH&BX|P`T:ZjNq_c@ ^56zn(4`Wjb9t >L/q혎bS A' Ġ 98l,pNe:?g &;bdʱo88JՎ)X9D9g Ba8v˦53kn-ސ:%t,ά"' Ѳ*Ӧ04"G jw^MP+f0Pv?GvH 1ma1f95cIJuUtw+چZ>z3^,d^idt1 U6DpJ|< m1 Nn z 1R}Za H#4>¨ J@1!#y1W5\ Ba&3ڋHA.WCl,L1E6ی|k2X ^ZQ1hMVnyi  _!oi0Xc~y}pD̾ߞs]qs{kRӯ/|m#E<.>xe.}uĐݑqUKDXܩފu;k3[6ڽ?ҟ5- +{~Ȳ. 牣,0((E"@#gd|@ r }Y"!<_ )H v#DDBIe^lSP؅V&, BsV[\m&Y>q}8-,M)vҡHkBκ`\";eNYn7VsX/@v%8 q09`=~XiaSlJ~B ˔81OV"l . >f,l{ђzخӈ ztlfƝ,;t0é/y_ Z v0nTRM>832d%*U?l,!*D Hir86f P D`$,X'ć3.6Ո!$XBCE{ne2!nVe: LwYA9iJDe 0P"?$ШR qeT&XYx4*wALŀSeK>:sF >O*KkHaAbHt醼-[hU ,x]  6&hXCEljLWƢʑ_|.4"zN6CG-J 974Hz{̆!OXH&H|]ጚR # O3 i> bF*@XD!ݔp`1F>kBk9Q xͿ&KF>_PzpհWNjWmPX{ۍ #BՏ` b" [3o mUT"_oi$bؑ8i5ئt2>]8ve_a;1^-Ҩ>R &s-^n'v6ܔ3ӿNc?!0; Yd"<=1}P5c[ !?bCzV }v,^F|Xa5 ַ$+Sa?pp`wW`# U$-K}*8-lXܰͶa3(fcA4$kű C¹\>,ZJ}d#&3bU*ӫ,"0kZY. ZRl\NV`vv`K*+W \߰͙9p`cAՈ>j,|+lᘔATĜhcs}Y*(ZQ/0Z{|,Fa0h>Qhn[[%Ȁ~ZS驫i*1|-HaJom>  0!Л jk FvRty\bAoB((H`u&Ɋbkc/1ݯſXhÊRQ0琀 _ 9(N{pƒS歺iʘ șkK{8οU%;S(r5Kga ݆W39[ʇZަzd/Z)-Q Dϐ> ѡJ`>(A`*A ] (A (|0"GEz3Ąohɢ5WSǛՊJTjf Ģ/*Ȣ+^I؃!v5VWDGfc0!6T h!FMc:ku.Y|#Eb.3!TPzL0YkDϸB}qaP^Dh|-,\L\!@T ېUG֢pF_7o[QlɲP8.$c@$EW/2D ';P )cbga]IUBv (P%>8'QS˳C")̬%cW-hgdUmSxnlH1Vf9^|&^: ,EInUMPxb?as4E C'獈k̅HljQ'NmsS:JondFRDq$tlsI:+&PR U-=Ь}A,=`z}6lpN$8DQC`k! {Gâ˂ŭ pωMP8]W-Nh@CCc=/'pjԩ'Լv"Zt,ꂃVP\Tmvsֵ]b {E*,~m?7創],euۊ[3oMi.=mܖ?!mŒ[ޘQ%`ļK.=3i [)Ӈ"@@vH1 ;:P:YUc,Zr#Y)WKz& mB+v4vE̠= ɲLƇQ>v\)>ٗ';!fۻLAa`S5?TS$HvAgΠa(509Q65?&uA@D .Nhvuq/ gjǗ~s*3e sA5&xdkg!9Uk JZ Ǟ4= [h`8l˻LD4(rgWp :84uf` .km R;Hl#^6+D* L1x2`<ߦ|8kÞ5 ˇaOɨ00hV,] `b0l2ځl"®NS!>/]Z\\Xbxu@)nЅWB児TMŶ>ک wcM+NkZeo;HjXVSu\lk_ro]xa|0o9YHWxҶ{H} 8V)BUe[we3G>̢6;RF,#]~@MsZ)&\U/ӻ; bĽy[Ӡ??.}~탿?%F)n,է˟S{ڛMsB{$~_Z?}5髍s@Ľ˖w<_ܦ[4oδWz.8;vWϟ3i=88tۯ/ qy^oR\jm ig!w33ʗLLB7Ι|q+{lc#J  qG5$ j2;D/-$TԖ;s{gܙvΏK ):Z9-TN0taFw6rAQE|1JV_?Ux쾉xD[͛}k7\]+2sHaHc^2TVeOaK{?tkձC.?kkdur1_x 79[>ުUΚTmB_2¨x%>.Ciƙe< sbR>a~v;ŨjJ#&?%lLnoxY[: 1)l>pˊ?-쟯N~0B]&DG U~k|0<<ݙ@HEIo5JpuQ *9mO[ѕm>{}^a4=& խ{hISㅿ[fL{BWHIަ&XSW)N3^m`cfQzOCLs(wU`ͳ9kCr%IDATڬ,c]-%y-Lv3cԳ!oϿqh߮g^޸;+l D g d7eFodʧ1,|:5;fYIJ1>xԫTH<`0$~)(2Qi3%ṮjA}T\pwB̾\5/]3#׏_ vm'~Ůw'._tuجY3n>{pw"[_Ʋ:`lQP8H1Ұ%YmG2\)Z0xW㉵l*uw9@1wpe|4ͨ"u[qE+ƜhPj-\r9%m/f!\Uo: "2iԏWNm!5SeDw:Ԏu:*5Om?{.O`Ǡ3x`Z?b4,~7 #'/wL򇷍7Կ=i:Wh9eOSGӧr&-=~7{g+-=;08*Vs_gw 7R&̸G48 '>xw>e)wl=ܒt㱝_z>4>1DÔ4lPRw'{L'͋l'{{S>W7`^!>}Gߞ:m2~7>2>ZU1USLs6F ?a]0uޟ0FQ!GڣOqIss;^Ś"@xDqͳWBeD@\ToرWր!DnHd7 3V_?|i"@ DzA D"gHقt D" T."@ yF,y4]"@ DJ4F"@ Dy4]"@ DJ4F"@ Dy4]"@ DJ4F"@ Dy4]"@ DJ4F"@ Dy4]"@ DJ4F"@ Dy4]"@ DJ4F"@ Dy4]"@ DJ4F"@ Dd*?鼶FAq!o. D"E@pQψ4n"@ D $Tz: ǁ:<{iD"@@8LJu܄JPyGs٧N DDjkmpR5$is "@ 9KHboi y:}FiD"@@8$RbzgC1PyGsÇL D.C"&M=y㫛r"@ yH]b+G j)@q!m2 D"Dj^,L,h y:\~DiD"@@>hF{T(rC>>4g"@ D "5]#Z>HOqT<#9Jc'D"@ks4k"@ DnK`v40"@ D"@"5&K D k"UjnXz{5$bHJQ*oW8;[?w^갨> D"@H77BqЏ?4NaF;H:UWMf3cXB}hkNrm*"@ DG%uj{PEinQ/ALo;?$F8S0⟭;3M]IvKy~꼩> D"@Bw.^y^SGj8~ϦγrdۺֆɖcV ?\d56joз1ɷ:tigbzE8򞑧SH7_H-=A^ ʈA=Ķ7_.c[9P XիhC#FQej㲍ewohT}KN7lh'KȾ?yM$),w$m4e]qेdOD"@BPIޗ>yᅊ_Uzc~=ǜY/F]햂톱`ܽ5Tw{虹@-?|d ۢ|j|n;}Je޸ >|orqB;p Yo[_e8 |kcٲES +g[ }9ES[yL8eali8P틔 |KOZ?5/l D"#l1֠jA#ZԨww"aAcέݼwP1fΠaHɧ5U}n\9hÂ:qЪZrG^KnE+hzu ʬT{p(R3):0o48#=B^^UR<FE]Oֶ4YiU'D"@/H]a$"olRdTk嶉)ԇxR-_zj+GD'F"6?ksA쐫R;L Z'{WX84ĢRצU{1UM(Ƶ3W߳uNSRHDO *!D"@FRlIt2V?|R#ZL^xQ">CG_x)%2fT)P*( =c/}0X0>lK zsh"YKo;\L;rEJpQESs_0X5g;#%/ERF$FZI&4 CED"@@"/Z2g}tB߭_,kq?w{d>F0>Wb f7nTF Dn@@ަlv؊,s?9Be3m[gxoW.srͣCߘP5:ן=dVbJ_3N .+a1M HoS|ɣId7bD^uW^1#_|eՊj}s|WxKSogAW D"h;vB"@ Dճ+R]"@ D"?MwIENDB`glyr-1.0.10/lib/000077500000000000000000000000001301162614000133065ustar00rootroot00000000000000glyr-1.0.10/lib/CMakeLists.txt000066400000000000000000000026101301162614000160450ustar00rootroot00000000000000# Configuration for Version / versionname configure_file ( "config.h.in" "config.h" ) SET(GENERIC_LIB_VERSION ${GLYR_VERSION_MAJOR}.${GLYR_VERSION_MINOR}) SET(GLYR_API_SOVERSION 1) IF(DEFINED ENV{BUILD_DATE}) ADD_DEFINITIONS(-DBUILD_DATE="$ENV{BUILD_DATE}") ENDIF() IF(DEFINED ENV{BUILD_TIME}) ADD_DEFINITIONS(-DBUILD_TIME="$ENV{BUILD_TIME}") ENDIF() # Link libglyr as shared library ADD_LIBRARY(glyr SHARED ${LIB_SOURCE_LOCATIONS}) # Win32 needs that socket library for libcurl IF(WIN32) TARGET_LINK_LIBRARIES(glyr ${CURL_LIBRARY} ${GLIBPKG_LIBRARIES} ${SQLITE3_LIBRARIES} ws2_32) SET_TARGET_PROPERTIES(glyr PROPERTIES OUTPUT_NAME "glyr-${GLYR_API_SOVERSION}" VERSION ${GENERIC_LIB_VERSION} ) ELSE(WIN32) TARGET_LINK_LIBRARIES(glyr ${CURL_LIBRARY} ${GLIBPKG_LIBRARIES} ${SQLITE3_LIBRARIES} ) SET_TARGET_PROPERTIES(glyr PROPERTIES VERSION ${GLYR_API_SOVERSION}.${GENERIC_LIB_VERSION} SOVERSION ${GLYR_API_SOVERSION}) ENDIF(WIN32) # Install Files INSTALL(FILES glyr.h DESTINATION ${INSTALL_INC_DIR}/glyr) INSTALL(FILES types.h DESTINATION ${INSTALL_INC_DIR}/glyr) INSTALL(FILES misc.h DESTINATION ${INSTALL_INC_DIR}/glyr) INSTALL(FILES cache.h DESTINATION ${INSTALL_INC_DIR}/glyr) INSTALL(FILES config.h DESTINATION ${INSTALL_INC_DIR}/glyr) INSTALL(FILES testing.h DESTINATION ${INSTALL_INC_DIR}/glyr) INSTALL(TARGETS glyr LIBRARY DESTINATION ${INSTALL_LIB_DIR}) glyr-1.0.10/lib/README000066400000000000000000000005151301162614000141670ustar00rootroot00000000000000This folder contains the implemantation of libglyr. All subfolders implement a getter: cover,lyrics,ainfo (..) A corrsponding .c file (cover.c, lyrics.c, ainfo.c... ) implement the getter itself. All other files implement the general webscraping abilities of glyr. Start reading src/main.c:main() to get through it.. Good luck! :-) glyr-1.0.10/lib/apikeys.h000066400000000000000000000026371301162614000151340ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a command-line tool and library to download various sort of musicrelated metadata. * + Copyright (C) [2011-2016] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #ifndef APIKEYS_H #define APIKEYS_H /* * Please register your own keys if you fork parts of this code! * */ #define API_KEY_AMAZON "AKIAJ6NEA642OU3FM24Q" #define API_KEY_LASTFM "7199021d9c8fbae507bf77d0a88533d7" #define API_KEY_FLICKR "b5af0c3230fb478d53b20835223d57a4" #define API_KEY_GTRANS "AIzaSyDzfBNb-W5G9pvZY6KGbEoRK4JvOIhUsjI" #define API_KEY_HTBACK "b3085ed18168f083aa69179b3364c9d8" #define API_KEY_ECHONEST "ZSIUEIVVZGJVJVWIS" #endif glyr-1.0.10/lib/blacklist.c000066400000000000000000000040461301162614000154260ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ /* The blacklist consists of two links at the moment :-P */ #include "blacklist.h" ///////////////////////////////// GHashTable * lookup_table = NULL; gchar * blacklist_array[] = { "http://ecx.images-amazon.com/images/I/11J2DMYABHL.jpg", /* blank image */ "http://cdn.recordshopx.com/cover/normal/5/53/53138.jpg%3Fcd" /* blank image */ }; ///////////////////////////////// void blacklist_build (void) { lookup_table = g_hash_table_new (g_str_hash,g_str_equal); gint b_size = sizeof (blacklist_array) / sizeof (gchar *); for (gint it = 0; it < b_size; it++) { if (blacklist_array[it] != NULL) { g_hash_table_insert (lookup_table,blacklist_array[it],blacklist_array[it]); } } } ///////////////////////////////// void blacklist_destroy (void) { g_hash_table_destroy (lookup_table); } ///////////////////////////////// gboolean is_blacklisted (gchar * URL) { if (lookup_table == NULL || URL == NULL) return FALSE; return ! (g_hash_table_lookup (lookup_table,URL) == NULL); } ///////////////////////////////// glyr-1.0.10/lib/blacklist.h000066400000000000000000000022031301162614000154240ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of musicrelated metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #ifndef GLYR_BLACKLIST_H #define GLYR_BLACKLIST_H #include gboolean is_blacklisted (gchar * URL); void blacklist_build (void); void blacklist_destroy (void); #endif glyr-1.0.10/lib/cache.c000066400000000000000000001141511301162614000145200ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "cache_intern.h" #include "cache.h" #include "core.h" #include "glyr.h" #include "register_plugins.h" /////////////////////////////// enum { SQL_TABLE_DEF, SQL_FOREACH, SQL_DELETE_SELECT, SQL_ACTUAL_DELETE, SQL_LOOKUP, SQL_INSERT_CACHE }; static const char * sqlcode[] = { [SQL_TABLE_DEF] = "PRAGMA synchronous = 1; \n" "PRAGMA temp_store = 2; \n" "BEGIN IMMEDIATE; \n" "-- Provider \n" "CREATE TABLE IF NOT EXISTS providers (provider_name VARCHAR(20) UNIQUE); \n" " \n" "-- Artist \n" "CREATE TABLE IF NOT EXISTS artists (artist_name VARCHAR(128) UNIQUE); \n" "CREATE TABLE IF NOT EXISTS albums (album_name VARCHAR(128) UNIQUE); \n" "CREATE TABLE IF NOT EXISTS titles (title_name VARCHAR(128) UNIQUE); \n" " \n" "-- Enum \n" "CREATE TABLE IF NOT EXISTS image_types(image_type_name VARCHAR(16) UNIQUE); \n" "CREATE TABLE IF NOT EXISTS db_version(version INTEGER UNIQUE); \n" " \n" "-- MetaData \n" "CREATE TABLE IF NOT EXISTS metadata( \n" " artist_id INTEGER, \n" " album_id INTEGER, \n" " title_id INTEGER, \n" " provider_id INTEGER, \n" " source_url VARCHAR(512), \n" " image_type_id INTEGER, \n" " track_duration INTEGER, \n" " get_type INTEGER, \n" " data_type INTEGER, \n" " data_size INTEGER, \n" " data_is_image INTEGER, \n" " data_checksum BLOB, \n" " data BLOB, \n" " rating INTEGER, \n" " timestamp FLOAT \n" "); \n" "CREATE INDEX IF NOT EXISTS index_artist_id ON metadata(artist_id); \n" "CREATE INDEX IF NOT EXISTS index_album_id ON metadata(album_id); \n" "CREATE INDEX IF NOT EXISTS index_title_id ON metadata(title_id); \n" "CREATE INDEX IF NOT EXISTS index_provider_id ON metadata(provider_id); \n" "CREATE UNIQUE INDEX IF NOT EXISTS index_unique \n" " ON metadata(get_type,data_type,data_checksum,source_url); \n" "-- Insert imageformats \n" "INSERT OR IGNORE INTO image_types VALUES('jpeg'); \n" "INSERT OR IGNORE INTO image_types VALUES('jpg'); \n" "INSERT OR IGNORE INTO image_types VALUES('png'); \n" "INSERT OR IGNORE INTO image_types VALUES('gif'); \n" "INSERT OR IGNORE INTO image_types VALUES('tiff'); \n" "INSERT OR IGNORE INTO db_version VALUES(2); \n" "COMMIT; \n", [SQL_FOREACH] = "SELECT artist_name, \n" " album_name, \n" " title_name, \n" " provider_name, \n" " source_url, \n" " image_type_name, \n" " track_duration, \n" " get_type, \n" " data_type, \n" " data_size, \n" " data_is_image, \n" " data_checksum, \n" " data, \n" " rating, \n" " timestamp \n" "FROM metadata as m \n" "LEFT JOIN artists AS a ON m.artist_id = a.rowid \n" "LEFT JOIN albums AS b ON m.album_id = b.rowid \n" "LEFT JOIN titles AS t ON m.title_id = t.rowid \n" "LEFT JOIN image_types AS i ON m.image_type_id = i.rowid \n" "JOIN providers AS p on m.provider_id = p.rowid \n", [SQL_DELETE_SELECT] = "SELECT get_type, \n" " artist_id, \n" " album_id, \n" " title_id, \n" " provider_id \n" " FROM metadata AS m \n" "LEFT JOIN artists AS a ON a.rowid = m.artist_id \n" "LEFT JOIN albums AS b ON b.rowid = m.album_id \n" "LEFT JOIN titles AS t ON t.rowid = m.title_id \n" "INNER JOIN providers AS p ON p.rowid = m.provider_id \n" "WHERE \n" " m.get_type = %d \n" " %s -- Title Contraint \n" " %s -- Album Constraint \n" " %s -- Artist Constraint \n" " AND p.provider_name IN(%s) \n" " %s -- 'IsALink' Constraint \n" "LIMIT %d; \n", [SQL_ACTUAL_DELETE] = "DELETE FROM metadata WHERE \n" "get_type %s %s AND \n" "artist_id %s %s AND \n" "album_id %s %s AND \n" "title_id %s %s AND \n" "provider_id %s %s; \n", [SQL_LOOKUP] = "SELECT artist_name, \n" " album_name, \n" " title_name, \n" " provider_name, \n" " source_url, \n" " image_type_name, \n" " track_duration, \n" " get_type, \n" " data_type, \n" " data_size, \n" " data_is_image, \n" " data_checksum, \n" " data, \n" " rating, \n" " timestamp \n" "FROM metadata as m \n" "LEFT JOIN artists AS a ON m.artist_id = a.rowid \n" "LEFT JOIN albums AS b ON m.album_id = b.rowid \n" "LEFT JOIN titles AS t ON m.title_id = t.rowid \n" "JOIN providers as p on m.provider_id = p.rowid \n" "LEFT JOIN image_types as i on m.image_type_id = i.rowid \n" "WHERE m.get_type = %d \n" " %s -- Title constr. \n" " %s -- Album constr. \n" " %s -- Artist constr. \n" " %s \n" " AND provider_name IN(%s) \n" "LIMIT %d; \n", [SQL_INSERT_CACHE] = "INSERT OR IGNORE INTO metadata VALUES( \n" " (SELECT rowid FROM artists WHERE artist_name = LOWER('%q')), \n" " (SELECT rowid FROM albums WHERE album_name = LOWER('%q')), \n" " (SELECT rowid FROM titles WHERE title_name = LOWER('%q')), \n" " (SELECT rowid FROM providers WHERE provider_name = LOWER('%q')), \n" " ?, \n" " (SELECT rowid FROM image_types WHERE image_type_name = LOWER('%q')),\n" " ?,?,?,?,?,?,?,?,? \n" "); \n" }; //////////////////////////////////////////////////////// ////////////////////// Prototypes ////////////////////// //////////////////////////////////////////////////////// static void insert_cache_data (GlyrDatabase * db, GlyrQuery * query, GlyrMemCache * cache); static void execute (GlyrDatabase * db, const gchar * sql_statement); static gchar * convert_from_option_to_sql (GlyrQuery * q); static double get_current_time (void); static void add_to_cache_list (GlyrMemCache ** list, GlyrMemCache * to_add); static int delete_callback (void * result, int argc, char ** argv, char ** azColName); static int select_callback (void * result, int argc, char ** argv, char ** azColName); //////////////////////////////////////////////////////// ////////////////// Useful Defines ////////////////////// //////////////////////////////////////////////////////// #define INSERT_STRING(SQL,ARG) { \ if(SQL && ARG) { \ /* We have to use _ascii_ here, */\ /* since there seems to be some encoding problems */\ /* in SQLite, which are triggered by comparing */\ /* lower and highercase umlauts for example */\ /* Simple encoding-indepent lowercase prevents it */\ gchar * lower_str = g_ascii_strdown(ARG,-1); \ gchar * sql = sqlite3_mprintf(SQL,lower_str); \ execute(db,sql); \ sqlite3_free(sql); \ g_free(lower_str); \ } \ } //////////////////////////////////////////////////////// /* Ensure no invalid data comes in */ #define ABORT_ON_FAILED_REQS(REQS,OPT_ARG,ARG) { \ if((REQS & OPT_ARG) == 0 && ARG == NULL) { \ glyr_message(-1,NULL,"Warning: %s != NULL failed",#ARG); \ goto rollback; \ } \ } //////////////////////////////////////////////////////// #define ADD_CONSTRAINT(TO_CONSTR, FIELDNAME, VARNAME) \ { \ /* We have to use _ascii_ here, */ \ /* since there seems to be some encoding problems */ \ /* in SQLite, which are triggered by comparing */ \ /* lower and highercase umlauts for example */ \ /* Simple encoding-indepent lowercase prevents it */ \ \ gchar * lower = g_ascii_strdown(VARNAME,-1); \ if(lower != NULL) \ { \ TO_CONSTR = sqlite3_mprintf("AND %s = '%q'\n",FIELDNAME,lower); \ g_free(lower); \ } \ } //////////////////////////////////////////////////////// #define CACHE_GET_PROVIDER(cache) (((cache)&&(cache->prov)) ? ((cache)->prov) : "none") //////////////////////////////////////////////////////// #define SQL_BIND_TEXT(stmt,text,pos) { \ int cpPos = pos; \ if(stmt && text) { \ int rc = sqlite3_bind_text(stmt,cpPos,text,strlen(text) + 1, SQLITE_STATIC); \ if(rc != SQLITE_OK) { \ printf("Could not bind value: %d\n",rc); \ } \ } \ } \ //////////////////////////////////////////////////////// /* How long to wait till returning SQLITE_BUSY */ #define DB_BUSY_WAIT 5000 #define DO_PROFILE false #if DO_PROFILE static GTimer * select_callback_timer = NULL; static float select_callback_spent = 0; #endif //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// typedef struct { GlyrDatabase * con; gint deleted; gint max_delete; } delete_callback_data; typedef struct { GlyrMemCache ** result; GlyrQuery * query; gint counter; glyr_foreach_callback cb; void * userptr; } select_callback_data; //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// __attribute__ ( (visibility ("default") ) ) GlyrDatabase * glyr_db_init (const char * root_path) { GlyrDatabase * to_return = NULL; #if DO_PROFILE GTimer * open_db = g_timer_new(); select_callback_timer = g_timer_new(); #endif if (sqlite3_threadsafe() == FALSE) { glyr_message (-1,NULL,"WARNING: Your SQLite version seems not to be threadsafe? \n" " Expect corrupted data and other weird behaviour!\n"); } if (root_path != NULL && g_file_test (root_path,G_FILE_TEST_EXISTS) ) { sqlite3 * db_connection = NULL; if (g_file_test (root_path,G_FILE_TEST_IS_DIR) ) { gchar * db_file_path = g_strdup_printf ("%s%s%s",root_path, (g_str_has_suffix (root_path,G_DIR_SEPARATOR_S) ? "" : G_DIR_SEPARATOR_S), GLYR_DB_FILENAME); gint db_open_err = sqlite3_open_v2 (db_file_path,&db_connection, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL); if (db_open_err == SQLITE_OK) { to_return = g_malloc0 (sizeof (GlyrDatabase) ); to_return->root_path = g_strdup (root_path); to_return->db_handle = db_connection; sqlite3_busy_timeout (db_connection,DB_BUSY_WAIT); /* Now create the Tables via sql */ execute (to_return, (char*) sqlcode[SQL_TABLE_DEF]); } else { glyr_message (-1,NULL,"Connecting to database failed: %s\n",sqlite3_errmsg (db_connection) ); sqlite3_close (db_connection); } g_free (db_file_path); } else { glyr_message (-1,NULL,"Warning: %s is not a directory; Creating DB Structure failed.\n",root_path); } } else { glyr_message (-1,NULL,"Warning: %s does not exist; Creating DB Structure failed.\n",root_path); } #if DO_PROFILE g_message ("Time to open DB: %lf\n",g_timer_elapsed (open_db,NULL) ); g_timer_destroy (open_db); #endif return to_return; } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_db_destroy (GlyrDatabase * db_object) { if (db_object != NULL) { int db_err = sqlite3_close (db_object->db_handle); if (db_err == SQLITE_OK) { g_free ( (gchar*) db_object->root_path); g_free (db_object); } else { glyr_message (-1,NULL,"Disconnecting database failed: %s\n",sqlite3_errmsg (db_object->db_handle) ); } } } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// __attribute__ ( (visibility ("default") ) ) int glyr_db_edit (GlyrDatabase * db, GlyrQuery * query, GlyrMemCache * edited) { int result = 0; if (db && query) { result = glyr_db_delete (db,query); if (result != 0) { for (GlyrMemCache * elem = edited; elem; elem = elem->next) { glyr_db_insert (db,query,edited); } } } return result; } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_db_replace (GlyrDatabase * db, unsigned char * md5sum, GlyrQuery * query, GlyrMemCache * data) { if (db != NULL && md5sum != NULL) { gchar * sql = "DELETE FROM metadata WHERE data_checksum = ? ;\n"; sqlite3_stmt *stmt = NULL; sqlite3_prepare_v2 (db->db_handle, sql, strlen (sql) + 1, &stmt, NULL); sqlite3_bind_blob (stmt, 1, md5sum, 16, SQLITE_STATIC); if (sqlite3_step (stmt) != SQLITE_DONE) { glyr_message (1,query,"Error message: %s\n", sqlite3_errmsg (db->db_handle) ); } sqlite3_finalize (stmt); if (data != NULL) { glyr_db_insert (db,query,data); } } } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// __attribute__ ( (visibility ("default") ) ) gint glyr_db_delete (GlyrDatabase * db, GlyrQuery * query) { gint result = 0; if (db && query) { /* Find out which fields are required for this getter */ GLYR_FIELD_REQUIREMENT reqs = glyr_get_requirements (query->type); /* Spaces in SQL statements just for pretty debug printing */ gchar * artist_constr = ""; if ( (reqs & GLYR_REQUIRES_ARTIST) != 0 && query->artist) { ADD_CONSTRAINT (artist_constr,"a.artist_name",query->artist); } gchar * album_constr = ""; if ( (reqs & GLYR_REQUIRES_ALBUM ) != 0 && query->album) { ADD_CONSTRAINT (album_constr,"b.album_name",query->album); } gchar * title_constr = ""; if ( (reqs & GLYR_REQUIRES_TITLE ) != 0 && query->title) { ADD_CONSTRAINT (title_constr,"t.title_name",query->title); } /* Get a SQL formatted list of enabled providers: IN('lastfm','...') */ gchar * from_argument_list = convert_from_option_to_sql (query); /* Check if links should be deleted */ gchar * img_url_constr = ""; if (TYPE_IS_IMAGE (query->type) ) { if (query->download == FALSE) { img_url_constr = sqlite3_mprintf ("AND data_type = %d ", GLYR_TYPE_IMG_URL); } else { img_url_constr = sqlite3_mprintf ("AND NOT data_type = %d ", GLYR_TYPE_IMG_URL); } } gchar * sql = sqlite3_mprintf (sqlcode[SQL_DELETE_SELECT], query->type, /* Limit to */ title_constr, /* Title Constr, may be empty */ album_constr, /* Album Constr, may be empty */ artist_constr, /* Artist Constr, may be empty */ from_argument_list, /* Provider contraint */ img_url_constr, /* Search for links? */ query->number /* LIMIT to */ ); if (sql != NULL) { delete_callback_data cb_data; cb_data.con = db; cb_data.deleted = 0; cb_data.max_delete = query->number; gchar * err_msg = NULL; sqlite3_exec (db->db_handle,sql,delete_callback,&cb_data,&err_msg); if (err_msg != NULL) { glyr_message (-1,NULL,"SQL Delete error: %s\n",err_msg); sqlite3_free (err_msg); } sqlite3_free (sql); result = cb_data.deleted; } /** * Free ressources with according free calls, */ if (*artist_constr) sqlite3_free (artist_constr); if (*album_constr) sqlite3_free (album_constr); if (*title_constr) sqlite3_free (title_constr); if (*img_url_constr) sqlite3_free (img_url_constr); g_free (from_argument_list); } return result; } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_db_foreach (GlyrDatabase * db, glyr_foreach_callback cb, void * userptr) { if (db != NULL && cb != NULL) { select_callback_data scb_data; scb_data.cb = cb; scb_data.userptr = userptr; GlyrQuery dummy; dummy.number = G_MAXINT; scb_data.query = &dummy; scb_data.counter = 0; scb_data.result = NULL; int rc = SQLITE_OK; char * err_msg = NULL; if ( (rc = sqlite3_exec (db->db_handle,sqlcode[SQL_FOREACH],select_callback,&scb_data,&err_msg) ) != SQLITE_OK) { if (rc != SQLITE_ABORT) { glyr_message (-1,NULL,"SQL Foreach error: %s\n",err_msg); } sqlite3_free (err_msg); } } } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// __attribute__ ( (visibility ("default") ) ) GlyrMemCache * glyr_db_lookup (GlyrDatabase * db, GlyrQuery * query) { GlyrMemCache * result = NULL; if (db != NULL && query != NULL) { GLYR_FIELD_REQUIREMENT reqs = glyr_get_requirements (query->type); gchar * artist_constr = ""; if ( (reqs & GLYR_REQUIRES_ARTIST) != 0) { ADD_CONSTRAINT (artist_constr,"artist_name",query->artist); } gchar * album_constr = ""; if ( (reqs & GLYR_REQUIRES_ALBUM ) != 0) { ADD_CONSTRAINT (album_constr,"album_name",query->album); } gchar * title_constr = ""; if ( (reqs & GLYR_REQUIRES_TITLE ) != 0) { ADD_CONSTRAINT (title_constr,"title_name",query->title); } gchar * from_argument_list = convert_from_option_to_sql (query); gchar * img_url_constr = ""; if (TYPE_IS_IMAGE (query->type) ) { if (query->download == FALSE) { img_url_constr = sqlite3_mprintf ("AND data_type = %d ", GLYR_TYPE_IMG_URL); } else { img_url_constr = sqlite3_mprintf ("AND NOT data_type = %d ", GLYR_TYPE_IMG_URL); } } gchar * sql = sqlite3_mprintf (sqlcode[SQL_LOOKUP], query->type, title_constr, album_constr, artist_constr, img_url_constr, from_argument_list, query->number ); if (sql != NULL) { select_callback_data data; data.result = &result; data.query = query; data.counter = 0; data.cb = NULL; data.userptr = NULL; gchar * err_msg = NULL; sqlite3_exec (db->db_handle,sql,select_callback,&data,&err_msg); if (err_msg != NULL) { glyr_message (-1,NULL,"glyr_db_lookup: %s\n",err_msg); sqlite3_free (err_msg); } sqlite3_free (sql); } #if DO_PROFILE g_message ("Spent %.5f Seconds in Selectcallback.\n",select_callback_spent); select_callback_spent = 0; #endif if (*artist_constr) { sqlite3_free (artist_constr); } if (*album_constr) { sqlite3_free (album_constr); } if (*title_constr) { sqlite3_free (title_constr); } g_free (from_argument_list); if (*img_url_constr != '\0') { sqlite3_free (img_url_constr); } } return result; } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_db_insert (GlyrDatabase * db, GlyrQuery * q, GlyrMemCache * cache) { if (db && q && cache) { GLYR_FIELD_REQUIREMENT reqs = glyr_get_requirements (q->type); execute (db,"BEGIN IMMEDIATE;"); if ( (reqs & GLYR_REQUIRES_ARTIST) || (reqs & GLYR_OPTIONAL_ARTIST) ) { ABORT_ON_FAILED_REQS (reqs,GLYR_OPTIONAL_ARTIST,q->artist); INSERT_STRING ("INSERT OR IGNORE INTO artists VALUES('%q');",q->artist); } if ( (reqs & GLYR_REQUIRES_ALBUM) || (reqs & GLYR_OPTIONAL_ALBUM) ) { ABORT_ON_FAILED_REQS (reqs,GLYR_OPTIONAL_ALBUM,q->album); INSERT_STRING ("INSERT OR IGNORE INTO albums VALUES('%q');",q->album); } if ( (reqs & GLYR_REQUIRES_TITLE) || (reqs & GLYR_OPTIONAL_TITLE) ) { ABORT_ON_FAILED_REQS (reqs,GLYR_OPTIONAL_TITLE,q->title); INSERT_STRING ("INSERT OR IGNORE INTO titles VALUES('%q');",q->title); } gchar * provider = CACHE_GET_PROVIDER (cache); INSERT_STRING ("INSERT OR IGNORE INTO providers VALUES('%q');",provider); insert_cache_data (db,q,cache); rollback: execute (db,"COMMIT;"); } } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// __attribute__ ( (visibility ("default") ) ) GlyrMemCache * glyr_db_make_dummy (void) { GlyrMemCache * c = glyr_cache_new(); glyr_cache_set_data (c,g_strdup ("[dummy]"),-1); c->rating = -1; return c; } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// // --------- INTERNALS ------------ // static void execute (GlyrDatabase * db, const gchar * sql_statement) { if (db && sql_statement) { char * err_msg = NULL; sqlite3_exec (db->db_handle,sql_statement,NULL,NULL,&err_msg); if (err_msg != NULL) { glyr_message (-1,NULL,"glyr_db_execute: SQL error: %s\n", err_msg); sqlite3_free (err_msg); } } } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// /** * Return the current time as double: * . */ static double get_current_time (void) { struct timeval tim; gettimeofday (&tim, NULL); return (double) tim.tv_sec + ( (double) tim.tv_usec/1e6); } //////////////////////////////////// static void insert_cache_data (GlyrDatabase * db, GlyrQuery * query, GlyrMemCache * cache) { if (db && query && cache) { int pos = 1; char * sql = sqlite3_mprintf (sqlcode[SQL_INSERT_CACHE], query->artist, query->album, query->title, CACHE_GET_PROVIDER (cache), cache->img_format ); sqlite3_stmt *stmt = NULL; sqlite3_prepare_v2 (db->db_handle, sql, strlen (sql) + 1, &stmt, NULL); SQL_BIND_TEXT (stmt,cache->dsrc,pos++); sqlite3_bind_int (stmt, pos++, cache->duration); sqlite3_bind_int (stmt, pos++, query->type); sqlite3_bind_int (stmt, pos++, cache->type); sqlite3_bind_int (stmt, pos++, cache->size); sqlite3_bind_int (stmt, pos++, cache->is_image); sqlite3_bind_blob (stmt,pos++, cache->md5sum, sizeof cache->md5sum, SQLITE_STATIC); if (cache->data != NULL) { sqlite3_bind_blob (stmt, pos++, cache->data, cache->size, SQLITE_STATIC); } else { glyr_message (1,query,"glyr: Warning: Attempting to insert cache with missing data!\n"); } sqlite3_bind_int (stmt, pos++, cache->rating); sqlite3_bind_double (stmt,pos++, get_current_time() ); if (sqlite3_step (stmt) != SQLITE_DONE) { glyr_message (1,query,"glyr_db_insert: SQL failure: %s\n", sqlite3_errmsg (db->db_handle) ); } sqlite3_finalize (stmt); sqlite3_free (sql); } } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// #define DEBUG_LIST false static void add_to_cache_list (GlyrMemCache ** list, GlyrMemCache * to_add) { if (to_add && list) { GlyrMemCache * head = *list; if (head == NULL) { /* Initialize list */ *list = to_add; } else { /* Find rating in list * head will store the first non-matchin item, * tail = head->prev * */ GlyrMemCache * tail = head; while (head && head->rating > to_add->rating) { tail = head; head = head->next; } /* Now see what timestamp we have, * and sort it accordingly, younger * caches (== higher timestamp), are sorted * at the start */ if (head != NULL) { int last_rating = head->rating; while (head && head->rating == last_rating && head->timestamp > to_add->timestamp) { GlyrMemCache * p = head->next; if (p && p->rating == last_rating) { tail = head; head = p; } else if (p == NULL) { tail = head; head = p; } else break; } } /* Check if we're at the end of the list, * If so just append it, * else we insert to_add before head */ g_assert (tail); if (head != NULL) { GlyrMemCache * prev = head->prev; if (prev != NULL) prev->next = to_add; to_add->prev = prev; to_add->next = head; head->prev = to_add; if (prev == NULL) *list = to_add; } else /* We're at the end */ { tail->next = to_add; to_add->prev = tail; } } } #if DEBUG_LIST GlyrMemCache * p = *list; while (p != NULL) { char * surr = (to_add == p) ? "*" : "|"; g_printerr ("%s(%d|%2.10f)%s %c ",surr,p->rating,p->timestamp,surr, (p->next) ? '-' : ' '); p = p->next; } puts (""); #endif } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// /* Convert a single result from the DB to an actual Cache */ static int select_callback (void * result, int argc, char ** argv, char ** azColName) { #if DO_PROFILE g_timer_start (select_callback_timer); #endif int rc = 0; select_callback_data * data = result; GlyrMemCache ** list = data->result; if (argc >= 15 && data->counter < data->query->number) { GlyrMemCache * cache = DL_init(); if (cache != NULL) { cache->prov = g_strdup (argv[3]); cache->dsrc = g_strdup (argv[4]); cache->img_format = g_strdup (argv[5]); cache->duration = (argv[6] ? strtol (argv[6],NULL,10) : 0); cache->type = (argv[8] ? strtol (argv[8],NULL,10) : 0); cache->size = (argv[9] ? strtol (argv[9],NULL,10) : 0); cache->is_image = (argv[10] ? strtol (argv[10],NULL,10) : 0); if (argv[11] != NULL) { memcpy (cache->md5sum,argv[11],16); } if (argv[12] != NULL && cache->size > 0) { cache->data = g_malloc0 (cache->size + 1); memcpy (cache->data,argv[12],cache->size); cache->data[cache->size] = 0; } cache->rating = (argv[13] ? strtol (argv[13],NULL,10) : 0); /* Timestamp */ if (argv[14] != NULL) { /* Normal strtod() cuts off part behin the comma.. */ cache->timestamp = (argv[14] ? g_ascii_strtod (argv[14],NULL) : 0); } /* We're in the cache, so this one was cached.. :) */ cache->cached = TRUE; if (list != NULL) { add_to_cache_list (list,cache); } else if (data->cb != NULL && cache) { GlyrQuery q; glyr_query_init (&q); if (argv[7] != NULL) { GLYR_GET_TYPE type = strtol (argv[7],NULL,10); glyr_opt_type (&q,type); } glyr_opt_artist (&q,argv[0]); glyr_opt_album (&q, argv[1]); glyr_opt_title (&q, argv[2]); rc = data->cb (&q,cache,data->userptr); glyr_query_destroy (&q); DL_free (cache); } } } data->counter++; #if DO_PROFILE g_timer_stop (select_callback_timer); select_callback_spent += g_timer_elapsed (select_callback_timer,NULL); #endif return rc; } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// static gchar * convert_from_option_to_sql (GlyrQuery * q) { gchar * result = g_strdup ("'none'"); for (GList * elem = r_getSList(); elem; elem = elem->next) { MetaDataSource * item = elem->data; if (item && (q->type == item->type || item->type == GLYR_GET_ANY) ) { if (provider_is_enabled (q,item) == TRUE) { gchar * old_mem = result; result = g_strdup_printf ("%s%s'%s'",result, (*result) ? "," : "",item->name); g_free (old_mem); } } } return result; } //////////////////////////////////// //////////////////////////////////// //////////////////////////////////// static int delete_callback (void * result, int argc, char ** argv, char ** azColName) { delete_callback_data * data = result; if (argc >= 4 && result && data->max_delete > data->deleted) { /* God, this is so silly.. SQL, why you don't like " = null" * I can't think of any easier way to do this, tell me if you found one */ gchar * sql_delete = sqlite3_mprintf (sqlcode[SQL_ACTUAL_DELETE], argv[0] ? "=" : " IS ", argv[0] ? argv[0] : "NULL", argv[1] ? "=" : " IS ", argv[1] ? argv[1] : "NULL", argv[2] ? "=" : " IS ", argv[2] ? argv[2] : "NULL", argv[3] ? "=" : " IS ", argv[3] ? argv[3] : "NULL", argv[4] ? "=" : " IS ", argv[4] ? argv[4] : "NULL"); if (sql_delete != NULL) { execute (data->con,sql_delete); sqlite3_free (sql_delete); data->deleted++; } } return 0; } glyr-1.0.10/lib/cache.h000066400000000000000000000222021301162614000145200ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of musicrelated metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #ifndef GLYR_CACHE_H #define GLYR_CACHE_H /** * SECTION:cache * @short_description: A fast SQLite cache for glyr's results * @title: Cache * @section_id: * @stability: Stable * @include: glyr/cache.h * * The Cache offers application authors to store the items found * by glyr persistently in a DB-Cache and get them again once needed. * Before usage it is necessary to open the DB via glyr_db_init(), * which expects a folder where the DB will be stored, after this * there are 4 operations you can use on the query: * * * * Lookup by a Query (glyr_db_lookup()) * * * * * Delete by a Query (glyr_db_delete()) * * * * * Insert (glyr_db_insert()) * * * * * Iterate (glyr_db_foreach()) * * * * * * If you want to insert "own" caches, e.g. to indicate that some artist wasn't found you could use * the following piece of code: * * // Create a new "dummy" cache GlyrMemCache * ct = glyr_db_make_dummy(); // Query with filled in artist, album, title, type, // and opened db glyr_db_insert(db,&q,ct); glyr_cache_free(ct); * * */ #include "types.h" #ifdef __cplusplus extern "C" { #endif typedef int (*glyr_foreach_callback) (GlyrQuery * q, GlyrMemCache * item, void * userptr); /* The Name of the SQL File */ #define GLYR_DB_FILENAME "metadata.db" /** * glyr_db_init: * @root_path: Folder to create DB in * * Allocates a new database object, and create a SQLite database * at the given path. The filename is in #GLYR_DB_FILENAME * You can now use insert,delete, edit and lookup on it. * * Returns: A newly allocated GlyrDatabase, free with glyr_db_destroy */ GlyrDatabase * glyr_db_init (const char * root_path); /** * glyr_db_destroy: * @db_object: A database connection * * Close the connection and free all associated memory. * You may not use it anymore. */ void glyr_db_destroy (GlyrDatabase * db_object); /** * glyr_db_lookup: * @db: A database connection * @query: Define what to search for * * The artist,album,title and type field are used to query * the database. * * If you used glyr_opt_lookup_db() to bind the DB to a query, * You may use glyr_get() as an alternative for this method. * If your specify "local" in glyr_opt_from() only the DB is searched. * * Other query-fields honored by glyr_db_lookup(): * * * * glyr_opt_download() - If false URLs to images are searched, true for real images. * * * * * glyr_opt_from() - What provider the item came from. * * * * * glyr_opt_number() - How many items to return at max. * * * * * Returns: A newly allocated #GlyrMemCache or NULL if nothing found */ GlyrMemCache * glyr_db_lookup (GlyrDatabase * db, GlyrQuery * query); /** * glyr_db_insert: * @db: A database connection * @q: The query that was used to retrieve the @cache * @cache: The cache to insert. * * The cache wil be inserted to the db @db, @q is used to determine the artist, album, title and type. */ void glyr_db_insert (GlyrDatabase * db, GlyrQuery * q, GlyrMemCache * cache); /** * glyr_db_delete: * @db: The Database * @query: Define what item shall be deleted. * * The database is searched for the artist, album, title and type specified in @query. * If items in the DB match they will deleted. * * Other query-fields honored by glyr_db_delete(): * * * * glyr_opt_download() - If false URLs to images are searched, true for real images. * * * * * glyr_opt_from() - What provider the item came from. * * * * * glyr_opt_number() - How many items to delete at a max. * * * * Returns: The number of deleted items. */ int glyr_db_delete (GlyrDatabase * db, GlyrQuery * query); /** * glyr_db_edit: * @db: The Database * @query: The query with set artist,album, type etc. * @edited: The edited cache. * * A simple convenience function to delete caches according to the settings specified in @query, * (Same rules as in glyr_db_delete() apply here). * After deleting the cache @edited in inserted. If @edited is a doubly linked list (next pointer is not NULL), * then all items in the list are inserted. * * You could have written it yourself like this: * * * int glyr_db_edit(GlyrDatabase * db, GlyrQuery * query, GlyrMemCache * edited) * { * int result = 0; * if(db && query) * { * result = glyr_db_delete(db,query); * if(result != 0) * { * for(GlyrMemCache * elem = edited; elem; elem = elem->next) * { * glyr_db_insert(db,query,edited); * } * } * } * return result; * } * * * * Returns: The number of replaced caches */ int glyr_db_edit (GlyrDatabase * db, GlyrQuery * query, GlyrMemCache * edited); /** * glyr_db_replace: * @db: The Database * @md5sum: The md5sum of the cache you want to edit. * @query: The query with set artist,album, type etc. * @data: The edited cache. * * Simple convenience function to edit caches in the Database. * Best understood by example: * * * // If you have a cache called 'c', that's already * // In the Database: * // Save the old checksum, edit it, update the database. * unsigned char old_md5sum[16] = {0}; * memcpy(old_md5sum,c->md5sum,16); * glyr_cache_set_data(c,g_strdup("Changed the data - muahahah"),-1); * c->rating = 4200; * glyr_db_replace(s->local_db, old_md5sum, s, c); * * * * Some caveats: * * * * You may insert a cache several times, if the source url (cache->dsrc) is different, * but with the same checksum. If you call glyr_db_replace() once more, the caches * with the double md5sum get deleted and replaced by the new one. * * * */ void glyr_db_replace (GlyrDatabase * db, unsigned char * md5sum, GlyrQuery * query, GlyrMemCache * data); /** * glyr_db_foreach: * @db: A database connection * @cb: The callback to call on each item. * @userptr: A pointer to pass as second argument to the callback. * * Iterate over all items in the database. * Callback may not be null. If callback returns a number != 0, * iteration aborts. * * Callback parameters: * * 1) The artist / album / title / type that was used to get this cache is stored in 'query', other fields are not filled. * * 2) The actual cache completely filled. * * 3) The userpointer you passed as 3rd argument to glyr_db_foreach() * * This is useful if you want to have statistics or such. * */ void glyr_db_foreach (GlyrDatabase * db, glyr_foreach_callback cb, void * userptr); /** * glyr_db_make_dummy: * * The idea behind this function is to create a dummy entry for the cache, e.g. to indicate a certain item * was not found. In this case you create a dummy with a 'rating' of -1 and push it into the DB. * If one does a lookup (via glyr_get() or glyr_db_lookup()) then this cache will be returned, one is able * to check if the item was not found before. * * Returns: A newly allocated cache, use glyr_cache_free() to free it. */ GlyrMemCache * glyr_db_make_dummy (void); #ifdef __cplusplus } #endif #endif glyr-1.0.10/lib/cache_intern.c000066400000000000000000000053041301162614000160760ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "core.h" #include "glyr.h" #include "cache.h" #include "cache_intern.h" #include ///////////////////////////////// ///////////////////////////////// ///////////////////////////////// /* Check if a cache is already in the db, by cheskum or source_url */ gboolean db_contains (GlyrDatabase * db, GlyrMemCache * cache) { gboolean result = FALSE; if (db && cache) { gchar * sql = sqlite3_mprintf ( "SELECT source_url,data_checksum,data_size,data_type FROM metadata AS m " "WHERE (m.data_type = %d AND m.data_size = %d AND m.data_checksum = '?') " "OR (m.source_url LIKE '%q' AND m.source_url IS NOT NULL AND data_type = %d) " "LIMIT 1; ", cache->type, cache->size, cache->dsrc, cache->type); if (sql != NULL) { sqlite3_stmt * stmt = NULL; sqlite3_prepare_v2 (db->db_handle, sql, strlen (sql) + 1, &stmt, NULL); sqlite3_bind_blob (stmt, 1, cache->md5sum, sizeof cache->md5sum, SQLITE_TRANSIENT); int err = sqlite3_step (stmt); if (err == SQLITE_ROW) { result = TRUE; } else if (err != SQLITE_DONE) { glyr_message (-1,NULL,"db_contains: error message: %s\n", sqlite3_errmsg (db->db_handle) ); } sqlite3_finalize (stmt); sqlite3_free (sql); } } return result; } ///////////////////////////////// ///////////////////////////////// ///////////////////////////////// glyr-1.0.10/lib/cache_intern.h000066400000000000000000000022371301162614000161050ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of musicrelated metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . ******************************************************/ #ifndef GLYR_CACHE_INTERN_H #define GLYR_CACHE_INTERN_H #include "types.h" #include "core.h" #include /* Check if a file is contained in the db */ gboolean db_contains (GlyrDatabase * db, GlyrMemCache * cache); #endif glyr-1.0.10/lib/config.h000066400000000000000000000017561301162614000147350ustar00rootroot00000000000000/** * SECTION:config * @short_description: Compiletime Information * @title: Config * @section_id: * @stability: Stable * @include: glyr/config.h * * Misc. version checking macros and other defines * that may vary between builds. */ #define GLYR_VERSION_MAJOR "1" #define GLYR_VERSION_MINOR "0" #define GLYR_VERSION_MICRO "8" #define GLYR_VERSION_MAJOR_INT 1 #define GLYR_VERSION_MINOR_INT 0 #define GLYR_VERSION_MICRO_INT 8 /** * GLYR_CHECK_VERSION: * @X: Macro version. * @Y: Mino version. * @Z: Micro version, * * Version to check glyr's version. * Example: * * * #if GLYR_CHECK_VERSION(0,8,7) * puts("Version is at least 0.87!"); * #endif * * */ #define GLYR_CHECK_VERSION(X,Y,Z) (X <= GLYR_VERSION_MAJOR_INT || Y <= GLYR_VERSION_MINOR_INT || Z <= GLYR_VERSION_MICRO_INT) #define GLYR_VERSION_NAME "Raving Raven" #define GLYR_DEBUG FALSE /* Message output */ #define GLYR_OUTPUT stderr glyr-1.0.10/lib/config.h.in000066400000000000000000000021561301162614000153350ustar00rootroot00000000000000/** * SECTION:config * @short_description: Compiletime Information * @title: Config * @section_id: * @stability: Stable * @include: glyr/config.h * * Misc. version checking macros and other defines * that may vary between builds. */ #define GLYR_VERSION_MAJOR "@GLYR_VERSION_MAJOR@" #define GLYR_VERSION_MINOR "@GLYR_VERSION_MINOR@" #define GLYR_VERSION_MICRO "@GLYR_VERSION_MICRO@" #define GLYR_VERSION_MAJOR_INT @GLYR_VERSION_MAJOR@ #define GLYR_VERSION_MINOR_INT @GLYR_VERSION_MINOR@ #define GLYR_VERSION_MICRO_INT @GLYR_VERSION_MICRO@ /** * GLYR_CHECK_VERSION: * @X: Macro version. * @Y: Mino version. * @Z: Micro version, * * Version to check glyr's version. * Example: * * * #if GLYR_CHECK_VERSION(0,8,7) * puts("Version is at least 0.87!"); * #endif * * */ #define GLYR_CHECK_VERSION(X,Y,Z) (X <= GLYR_VERSION_MAJOR_INT || Y <= GLYR_VERSION_MINOR_INT || Z <= GLYR_VERSION_MICRO_INT) #define GLYR_VERSION_NAME "@GLYR_VERSION_NAME@" #define GLYR_DEBUG @GLYR_DEBUG@ /* Message output */ #define GLYR_OUTPUT stderr glyr-1.0.10/lib/core.c000066400000000000000000001571031301162614000144110ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include #include #include "stringlib.h" #include "core.h" /* Get user agent string */ #include "config.h" /* Get access to the db */ #include "cache_intern.h" /* Mini blacklist */ #include "blacklist.h" /* Somehow needed to prevent some compiler warning.. */ #include ////////////////////////////////////// static int _msg (const char * fmt, va_list params) { gchar * tmp_buf = NULL; /* Silly, but needs a way to get length */ gint written = g_vasprintf (&tmp_buf,fmt,params); if (written != -1 && tmp_buf != NULL) { g_log (G_LOG_DOMAIN,G_LOG_LEVEL_INFO,"%s",tmp_buf); g_free (tmp_buf); tmp_buf = NULL; } return written; } ////////////////////////////////////// int glyr_message (int verbosity, GlyrQuery * s, const char * fmt, ...) { gint written = 0; if (s != NULL || verbosity == -1) { if (verbosity == -1 || verbosity <= s->verbosity) { va_list params; if (fmt != NULL) { va_start (params,fmt); written = _msg (fmt,params); va_end (params); } } } return written; } ////////////////////////////////////// /** * Check if the size of a cover fits the specs */ gboolean size_is_okay (int sZ, int min, int max) { if ( (min == -1 && max == -1) || (min == -1 && max >= sZ) || (min <= sZ && max == -1) || (min <= sZ && max >= sZ) ) return TRUE; return FALSE; } ////////////////////////////////////// /* cache incoming data in a GlyrMemCache * libglyr is spending quite some time here */ static size_t DL_buffer (void *puffer, size_t size, size_t nmemb, void * buff_data) { size_t realsize = size * nmemb; DLBufferContainer * data = (DLBufferContainer *) buff_data; if (data != NULL) { GlyrMemCache * mem = data->cache; mem->data = realloc (mem->data, mem->size + realsize + 1); if (mem->data) { memcpy (& (mem->data[mem->size]), puffer, realsize); mem->size += realsize; mem->data[mem->size] = 0; GlyrQuery * query = data->query; if (query && GET_ATOMIC_SIGNAL_EXIT (query) ) { return 0; } /* Test if a endmarker is in this buffer */ const gchar * endmarker = data->endmarker; if (endmarker && strstr (mem->data,endmarker) ) return 0; } else { glyr_message (-1,NULL,"Caching failed: Out of memory.\n"); glyr_message (-1,NULL,"Did you perhaps try to load a 4,7GB .iso into your RAM?\n"); } } return realsize; } ////////////////////////////////////// void DL_set_data (GlyrMemCache * cache, const gchar * data, gint len) { if (cache != NULL) { g_free (cache->data); cache->data = (gchar*) data; if (data != NULL) { cache->size = (len >= 0) ? (gsize) len : strlen (data); update_md5sum (cache); } else { cache->size = 0; memset (cache->md5sum,0,16); } } } ////////////////////////////////////// GlyrMemCache * DL_copy (GlyrMemCache * cache) { GlyrMemCache * result = NULL; if (cache != NULL) { result = g_malloc0 (sizeof (GlyrMemCache) ); memcpy (result,cache,sizeof (GlyrMemCache) ); if (cache->size > 0) { /* Remember NUL for strings */ result->data = g_malloc (cache->size + 1); result->data[cache->size] = 0; memcpy (result->data,cache->data,cache->size); } result->dsrc = g_strdup (cache->dsrc); result->prov = g_strdup (cache->prov); result->img_format = g_strdup (cache->img_format); memcpy (result->md5sum,cache->md5sum,16); result->next = NULL; result->prev = NULL; } return result; } ////////////////////////////////////// // cleanup internal buffer if no longer used void DL_free (GlyrMemCache *cache) { if (cache) { if (cache->size && cache->data) { g_free (cache->data); cache->data = NULL; } if (cache->dsrc) { g_free (cache->dsrc); cache->dsrc = NULL; } if (cache->prov) { g_free (cache->prov); cache->prov = NULL; } cache->size = 0; cache->type = GLYR_TYPE_UNKNOWN; g_free (cache->img_format); g_free (cache); cache = NULL; } } ////////////////////////////////////// // Use this to init the internal buffer GlyrMemCache* DL_init (void) { GlyrMemCache * cache = g_malloc0 (sizeof (GlyrMemCache) ); memset (cache,0,sizeof (GlyrMemCache) ); cache->type = GLYR_TYPE_UNKNOWN; cache->cached = FALSE; cache->duration = 0; cache->rating = 0; cache->timestamp = 0.0; return cache; } ////////////////////////////////////// // Splits http_proxy to libcurl conform represantation static gboolean proxy_to_curl (gchar * proxystring, char ** userpwd, char ** server) { if (proxystring && userpwd && server) { if (proxystring != NULL) { gchar * ddot = strchr (proxystring,':'); gchar * asgn = strchr (proxystring,'@'); if (ddot == NULL || asgn < ddot) { *server = g_strdup (proxystring); *userpwd = NULL; return TRUE; } else { gsize len = strlen (proxystring); char * protocol = strstr (proxystring,"://"); if (protocol == NULL) { protocol = (gchar*) proxystring; } else { protocol += 3; } *userpwd = g_strndup (protocol,asgn-protocol); *server = g_strndup (asgn+1,protocol+len-asgn); return TRUE; } } } return FALSE; } ////////////////////////////////////// struct header_data { gchar * type; gchar * format; gchar * extra; }; ////////////////////////////////////// /* Parse header file. Get Contenttype from it and save it in the header_data struct */ gsize header_cb (void *ptr, gsize size, gsize nmemb, void *userdata) { gsize bytes = size * nmemb; if (ptr != NULL && userdata != NULL) { /* Transform safely into string */ gchar nulbuf[bytes + 1]; memcpy (nulbuf,ptr,bytes); nulbuf[bytes] = '\0'; /* We're only interested in the content type */ gchar * cttp = "Content-Type: "; gsize ctt_len = strlen (cttp); if (ctt_len < bytes && g_ascii_strncasecmp (cttp,nulbuf,ctt_len) == 0) { gchar ** content_type = g_strsplit_set (nulbuf + ctt_len," /;",0); if (content_type != NULL) { gsize set_at = 0; gchar ** elem = content_type; struct header_data * info = userdata; /* Set fields.. */ while (elem[0] != NULL) { if (elem[0][0] != '\0') { switch (set_at) { case 0: g_free (info->type); info->type = g_strdup (elem[0]); break; case 1: g_free (info->format); /* * Specialcase: * htbackdrops uses application/octet-stream as format * for their images. This is annoying, but needs to be * handled nevertheless. Shame on you, htbackdrops. * */ if (g_ascii_strncasecmp (elem[0],"octet-stream",12) == 0) { g_free (info->type); info->type = g_strdup ("image"); info->format = g_strdup ("jpeg"); } else { info->format = g_strdup (elem[0]); } break; case 2: g_free (info->extra); info->extra = g_strdup (elem[0]); break; } set_at++; } elem++; } g_strfreev (content_type); } } } return bytes; } ////////////////////////////////////// /* empty callback just prevent writing header to stdout */ gsize nearly_empty_callback (void * p, gsize size, gsize numb, void * pp_Query) { GlyrQuery * query = (GlyrQuery *) pp_Query; return (query && GET_ATOMIC_SIGNAL_EXIT (query) ) ? 0 : (size * numb); } ////////////////////////////////////// static void DL_setproxy (CURL *eh, gchar * proxystring) { if (proxystring != NULL) { gchar * userpwd; gchar * server; proxy_to_curl (proxystring,&userpwd,&server); if (server != NULL) { curl_easy_setopt (eh, CURLOPT_PROXY,server); g_free (server); } else { glyr_message (-1,NULL,"Warning: Invalid proxy string.\n"); } if (userpwd != NULL) { curl_easy_setopt (eh,CURLOPT_PROXYUSERPWD,userpwd); g_free (userpwd); } } } ////////////////////////////////////// static struct header_data * retrieve_content_info (gchar * url, gchar * proxystring, gchar * useragent, GlyrQuery * query) { struct header_data * info = NULL; if (url != NULL) { CURL * eh = curl_easy_init(); CURLcode rc = CURLE_OK; info = g_malloc0 (sizeof (struct header_data) ); gchar * link_user_agent = g_strdup_printf ("%s / linkvalidator",useragent); curl_easy_setopt (eh, CURLOPT_TIMEOUT, 10); curl_easy_setopt (eh, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt (eh, CURLOPT_USERAGENT, link_user_agent); curl_easy_setopt (eh, CURLOPT_URL,url); curl_easy_setopt (eh, CURLOPT_FOLLOWLOCATION, TRUE); curl_easy_setopt (eh, CURLOPT_MAXREDIRS, 5L); curl_easy_setopt (eh, CURLOPT_HEADER,TRUE); curl_easy_setopt (eh, CURLOPT_SSL_VERIFYPEER, FALSE); /* Dirty hack here: Amazon bitches at me when setting NOBODY to true * * But otherwise large images won't pass with other providers * * Check domain therefore.. */ if (strstr (url,"amazon") != NULL) { curl_easy_setopt (eh, CURLOPT_NOBODY,FALSE); } else { curl_easy_setopt (eh, CURLOPT_NOBODY,TRUE); } curl_easy_setopt (eh, CURLOPT_HEADERFUNCTION, header_cb); curl_easy_setopt (eh, CURLOPT_WRITEFUNCTION, nearly_empty_callback); curl_easy_setopt (eh, CURLOPT_WRITEDATA, query); curl_easy_setopt (eh, CURLOPT_WRITEHEADER, info); /* Set proxy, if any */ DL_setproxy (eh, proxystring); /* This seemed to prevent some valid urls from passing. Strange. */ //curl_easy_setopt(eh, CURLOPT_FAILONERROR,TRUE); rc = curl_easy_perform (eh); curl_easy_cleanup (eh); if (rc != CURLE_OK) { if (GET_ATOMIC_SIGNAL_EXIT (query) == FALSE) { glyr_message (1,query,"- DLError: %s [%d]\n",curl_easy_strerror (rc),rc); } g_free (info); info = NULL; } else { /* Remove trailing newlines,carriage returns */ chomp_breakline (info->type); chomp_breakline (info->format); chomp_breakline (info->extra); } g_free (link_user_agent); } return info; } ////////////////////////////////////// // Init an easyhandler with all relevant options static DLBufferContainer * DL_setopt (CURL *eh, GlyrMemCache * cache, const char * url, GlyrQuery * s, void * magic_private_ptr, long timeout, gchar * endmarker) { // Set options (see 'man curl_easy_setopt') curl_easy_setopt (eh, CURLOPT_TIMEOUT, timeout); curl_easy_setopt (eh, CURLOPT_NOSIGNAL, 1L); // last.fm and discogs require an useragent (wokrs without too) curl_easy_setopt (eh, CURLOPT_USERAGENT, (s && s->useragent) ? s->useragent : GLYR_DEFAULT_USERAGENT); curl_easy_setopt (eh, CURLOPT_HEADER, 0L); // Pass vars to curl curl_easy_setopt (eh, CURLOPT_URL, url); curl_easy_setopt (eh, CURLOPT_PRIVATE, magic_private_ptr); curl_easy_setopt (eh, CURLOPT_VERBOSE, (s && s->verbosity >= 4) ); curl_easy_setopt (eh, CURLOPT_WRITEFUNCTION, DL_buffer); curl_easy_setopt (eh, CURLOPT_SSL_VERIFYPEER, FALSE); DLBufferContainer * dlbuffer = g_malloc0 (sizeof (DLBufferContainer) ); curl_easy_setopt (eh, CURLOPT_WRITEDATA, (void *) dlbuffer); dlbuffer->cache = cache; dlbuffer->endmarker = endmarker; dlbuffer->query = s; // amazon plugin requires redirects curl_easy_setopt (eh, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt (eh, CURLOPT_MAXREDIRS, (s) ? s->redirects : 2); // Do not download 404 pages curl_easy_setopt (eh, CURLOPT_FAILONERROR, 1L); // Set proxy to use DL_setproxy (eh, (gchar*) (s) ? s->proxy : NULL); // Discogs requires gzip compression curl_easy_setopt (eh, CURLOPT_ENCODING,"gzip"); // Don't save cookies - I had some quite embarassing moments // when amazon's startpage showed me "Hooray for Boobies", // because I searched for the Bloodhoundgang album... // (because I have it already of course! ;-)) curl_easy_setopt (eh, CURLOPT_COOKIEJAR ,""); return dlbuffer; } ////////////////////////////////////// gboolean continue_search (gint current, GlyrQuery * s) { gboolean decision = FALSE; if (s != NULL && GET_ATOMIC_SIGNAL_EXIT (s) == FALSE) { /* Take an educated guess, let the provider get more, because URLs might be wrong, * as we check this later, it's good to have some more ULRs waiting for us, * * alternatively we might hit the maximum for one plugin (off by one!) */ gint buffering = (s->imagejob) ? s->number / 3 : 0; /* If we use caching we should consider getting a lot more items, * since many of the *may* be already in the Database */ gint pre_cache = (s->local_db) ? s->number : 0; decision = (current + s->itemctr) < (s->number + buffering + pre_cache) && (current < s->plugmax || (s->plugmax == -1) ); } return decision; } ////////////////////////////////////// // Bad data checker mehods: ////////////////////////////////////// /* Check for dupes. This does not affect the HEAD of the list, therefore no GList return */ gsize delete_dupes (GList * result, GlyrQuery * s) { if (!result || g_list_length (result) < 1) return 0; /* Build new hashes, the data might have changed */ for (GList * elem = result; elem; elem = elem->next) { update_md5sum (elem->data); } gint double_items = 0; for (GList * inode = result; inode; inode = inode->next) { GlyrMemCache * lval = inode->data; GList * jnode = result; while (jnode != NULL) { bool is_duplicate = false; GlyrMemCache * rval = jnode->data; if (lval && rval && rval != lval && lval->size == rval->size) { /* Compare via checkums */ if (CALC_MD5SUMS == false) { if (!memcmp (lval->data,rval->data,rval->size) ) { is_duplicate = true; } } else { if (!memcmp (lval->md5sum,rval->md5sum,16) ) { is_duplicate = true; } } /* Delete this element.. */ if (is_duplicate == true) { DL_free (rval); /* Delete this ref from the list */ GList * to_free = jnode; jnode = jnode->next; result = g_list_delete_link (result,to_free); /* Remember him.. */ double_items++; continue; } } jnode = jnode->next; } } return double_items; } ////////////////////////////////////// // Download a singe file NOT in parallel GlyrMemCache * download_single (const char* url, GlyrQuery * s, const char * end) { if (url != NULL && is_blacklisted ( (gchar*) url) == false) { CURL *curl = NULL; CURLcode res = 0; /* Init handles */ curl = curl_easy_init(); GlyrMemCache * dldata = DL_init(); /* DL_buffer needs the 'end' mark. * As I didnt want to introduce a new struct just for this * I save it in->dsrc */ if (end != NULL) { dldata->dsrc = g_strdup (end); } if (curl != NULL) { /* Configure curl */ DLBufferContainer * dlbuffer = DL_setopt (curl,dldata,url,s,NULL, (s) ? s->timeout : 5, NULL); /* Perform transaction */ res = curl_easy_perform (curl); /* Free the pointer buff */ g_free (dlbuffer); /* Better check again */ if (res != CURLE_OK && res != CURLE_WRITE_ERROR) { glyr_message (3,s,"glyr: E: singledownload: %s [E:%d]\n", curl_easy_strerror (res),res); DL_free (dldata); dldata = NULL; } else { /* Set the source URL */ if (dldata->dsrc != NULL) { g_free (dldata->dsrc); } dldata->dsrc = g_strdup (url); } curl_easy_cleanup (curl); update_md5sum (dldata); return dldata; } DL_free (dldata); } return NULL; } ////////////////////////////////////// // Init a callback object and a curl_easy_handle static GlyrMemCache * init_async_cache (CURLM * cm, cb_object * capo, GlyrQuery *s, long timeout, gchar * endmark) { GlyrMemCache * dlcache = NULL; if (capo && capo->url) { /* Init handle */ CURL *eh = curl_easy_init(); /* Init cache */ dlcache = DL_init(); /* Remind this handle */ capo->handle = eh; /* Make sure this is null at start */ capo->dlbuffer = NULL; /* Configure this handle */ capo->dlbuffer = DL_setopt (eh, dlcache, capo->url, s, (void*) capo,timeout, endmark); /* Add handle to multihandle */ curl_multi_add_handle (cm, eh); /* This is set to true once DL_buffer is reached */ capo->was_buffered = FALSE; } return dlcache; } ////////////////////////////////////// static GList * init_async_download (GList * url_list, GList * endmark_list, CURLM * cmHandle, GlyrQuery * s, int abs_timeout) { GList * cb_list = NULL; for (GList * elem = url_list; elem; elem = elem->next) { if (is_blacklisted ( (gchar*) elem->data) == false) { cb_object * obj = g_malloc0 (sizeof (cb_object) ); obj->s = s; obj->url = g_strdup ( (gchar*) (elem->data) ); cb_list = g_list_prepend (cb_list,obj); obj->consumed = FALSE; /* Get the endmark from the endmark list */ gint endmark_pos = g_list_position (url_list,elem); GList * glist_m = g_list_nth (endmark_list,endmark_pos); gchar * endmark = (glist_m==NULL) ? NULL : glist_m->data; obj->cache = init_async_cache (cmHandle,obj,s,abs_timeout,endmark); } } return cb_list; } ////////////////////////////////////// static void destroy_async_download (GList * cb_list, CURLM * cmHandle, gboolean free_caches) { /* Free ressources */ curl_multi_cleanup (cmHandle); if (cb_list != NULL) { for (GList * elem = cb_list; elem; elem = elem->next) { cb_object * item = elem->data; if (item->handle != NULL) { curl_easy_cleanup (item->handle); } /* Also free unbuffered items, that don't appear in the queue, * even if free_caches was set to FALSE */ if ( (free_caches == TRUE || item->was_buffered == FALSE) && item->consumed == FALSE) { DL_free (item->cache); item->cache = NULL; } g_free (item->dlbuffer); g_free (item->url); } glist_free_full (cb_list,g_free); } } ////////////////////////////////////// /* ----------------- THE HEART OF GOLD ------------------ */ ////////////////////////////////////// GList * async_download (GList * url_list, GList * endmark_list, GlyrQuery * s, long parallel_fac, long timeout_fac, AsyncDLCB asdl_callback, void * userptr, gboolean free_caches) { /* Storage for result items */ GList * item_list = NULL; if (url_list != NULL && s != NULL) { /* total timeout and parallel tries */ long abs_timeout = ABS (timeout_fac * s->timeout); long abs_parallel = ABS (parallel_fac * s->parallel); /* select() control */ int max_fd, queue_msg, running_handles = -1; long wait_time; fd_set ReadFDS, WriteFDS, ErrorFDS; /* Curl Multi Handles (~ container for easy handlers) */ CURLM * cmHandle = curl_multi_init(); curl_multi_setopt (cmHandle, CURLMOPT_MAXCONNECTS,abs_parallel); curl_multi_setopt (cmHandle, CURLMOPT_PIPELINING, 1L); /* Once set to true this will terminate the download */ gboolean terminate = FALSE; /* Now create cb_objects */ GList * cb_list = init_async_download (url_list,endmark_list,cmHandle,s,abs_timeout); while (GET_ATOMIC_SIGNAL_EXIT (s) == FALSE && running_handles != 0 && terminate == FALSE) { CURLMcode merr = CURLM_CALL_MULTI_PERFORM; while (merr == CURLM_CALL_MULTI_PERFORM) { merr = curl_multi_perform (cmHandle, &running_handles); } if (merr != CURLM_OK) { glyr_message (1,s,"Error: curl_multi_perform() failed!"); return NULL; } if (running_handles != 0) { /* Set up fds */ FD_ZERO (&ReadFDS); FD_ZERO (&WriteFDS); FD_ZERO (&ErrorFDS); if (curl_multi_fdset (cmHandle, &ReadFDS, &WriteFDS, &ErrorFDS, &max_fd) || curl_multi_timeout (cmHandle, &wait_time) ) { glyr_message (1,s,"Error while selecting stream. Might be a bug.\n"); return NULL; } if (wait_time == -1) wait_time = 100; if (wait_time >= s->timeout * 1000) wait_time = s->timeout * 1000; /* Nothing happens.. */ if (max_fd == -1) { g_usleep (wait_time * 100); } else { struct timeval Tmax; Tmax.tv_sec = (wait_time/1000); Tmax.tv_usec = (wait_time%1000) *1000; /* Now block till something interesting happens with the download */ if (select (max_fd+1, &ReadFDS, &WriteFDS, &ErrorFDS, &Tmax) == -1) { glyr_message (1,s,"Error: select(%i <=> %li): %i: %s\n",max_fd+1, wait_time, errno, strerror (errno) ); return NULL; } } } /* select() returned. There might be some fresh flesh! - Check. */ CURLMsg * msg; while (GET_ATOMIC_SIGNAL_EXIT (s) == FALSE && terminate == FALSE && (msg = curl_multi_info_read (cmHandle, &queue_msg) ) ) { /* That download is ready to be viewed */ if (msg->msg == CURLMSG_DONE) { /* Easy handle of this particular DL */ CURL *easy_handle = msg->easy_handle; /* Get the callback object associated with the curl handle * for some odd reason curl requires a char * pointer */ cb_object * capo = NULL; curl_easy_getinfo (msg->easy_handle, CURLINFO_PRIVATE, ( ( (char**) &capo) ) ); /* It's useless if it's empty */ if (capo && capo->cache && capo->cache->data == NULL) { capo->consumed = TRUE; DL_free (capo->cache); capo->cache = NULL; } /* Mark this cb_object as */ capo->was_buffered = TRUE; /* capo contains now the downloaded cache, ready to parse */ if (msg->data.result == CURLE_OK && capo && capo->cache) { /* How many items from the callback will actually be added */ gint to_add = 0; /* Stop download after this came in */ bool stop_download = false; GList * cb_results = NULL; /* Set origin */ if (capo->cache->dsrc != NULL) { g_free (capo->cache->dsrc); } capo->cache->dsrc = g_strdup (capo->url); /* Call it if present */ if (asdl_callback != NULL) { /* Add parsed results or nothing if parsed result is empty */ cb_results = asdl_callback (capo,userptr,&stop_download,&to_add); } if (cb_results != NULL) { /* Fill in the source filed (dsrc) if not already done */ for (GList * elem = cb_results; elem; elem = elem->next) { GlyrMemCache * item = elem->data; if (item && item->dsrc == NULL) { /* Plugin didn't do any special download */ item->dsrc = g_strdup (capo->url); } item_list = g_list_prepend (item_list,item); } g_list_free (cb_results); } else if (to_add != 0) { /* Add it as raw data */ item_list = g_list_prepend (item_list,capo->cache); } else { capo->consumed = TRUE; DL_free (capo->cache); capo->cache = NULL; } /* So, shall we stop? */ terminate = stop_download; } else { /* Something in this download was wrong. Tell us what. */ char * errstring = (char*) curl_easy_strerror (msg->data.result); glyr_message (3,capo->s,"- glyr: Downloaderror: %s [errno:%d]\n", errstring ? errstring : "Unknown Error", msg->data.result); glyr_message (3,capo->s," On URL: "); glyr_message (3,capo->s,"%s\n",capo->url); DL_free (capo->cache); capo->cache = NULL; capo->consumed = TRUE; } /* We're done with this one.. bybebye */ curl_multi_remove_handle (cmHandle,easy_handle); curl_easy_cleanup (easy_handle); capo->handle = NULL; } else { /* Something in the multidownloading gone wrong */ glyr_message (1,s,"Error: multiDownload-errorcode: %d\n",msg->msg); } } } destroy_async_download (cb_list,cmHandle,free_caches); } return item_list; } ////////////////////////////////////// struct wrap_retrieve_pass_data { gchar * url; GlyrQuery * query; }; static void * wrap_retrieve_content (gpointer data) { struct header_data * head = NULL; if (data != NULL) { struct wrap_retrieve_pass_data * passed = data; GlyrQuery * query = passed->query; head = retrieve_content_info (passed->url, (gchar*) query->proxy, (gchar*) query->useragent,query); g_free (passed); passed = NULL; } return head; } ////////////////////////////////////// static void check_all_types_in_url_list (GList * cache_list, GlyrQuery * s) { if (cache_list != NULL) { GHashTable * thread_id_table = g_hash_table_new (g_direct_hash,g_direct_equal); GList * thread_list = NULL; glyr_message (2,s,"#[%02d/%02d] Checking image-types: [",s->itemctr,s->number); for (GList * elem = cache_list; elem; elem = elem->next) { GlyrMemCache * item = elem->data; if (item != NULL) { struct wrap_retrieve_pass_data * passer = g_malloc0 (sizeof (struct wrap_retrieve_pass_data) ); passer->url = item->data; passer->query = s; GThread * thread = g_thread_new("content_retriever", (GThreadFunc) wrap_retrieve_content, passer); if (thread != NULL) { thread_list = g_list_prepend (thread_list,thread); } g_hash_table_insert (thread_id_table,thread,item); } } for (GList * thread = thread_list; thread; thread = thread->next) { gboolean success = FALSE; struct header_data * info = g_thread_join (thread->data); if (info != NULL) { GlyrMemCache * linked_cache = g_hash_table_lookup (thread_id_table,thread->data); if (linked_cache != NULL) { if (g_strcmp0 (info->type,"image") == 0) { linked_cache->img_format = g_strdup (info->format); success = TRUE; } } else { glyr_message (1,s,"glyr: Uh oh.. empty link in hashtable..\n"); } g_free (info->format); g_free (info->type); g_free (info->extra); g_free (info); } glyr_message (2,s,"%c", (success) ? '.' : '!'); } g_list_free (thread_list); g_hash_table_destroy (thread_id_table); glyr_message (2,s,"]"); } } ////////////////////////////////////// static gboolean format_is_allowed (gchar * format, gchar * allowed) { /* Let everything pass */ if (allowed == NULL) { return TRUE; } gboolean result = FALSE; if (format != NULL && allowed != NULL) { gchar * token; gsize offset = 0; gsize len = strlen (allowed); while (!result && (token = get_next_word (allowed,GLYR_DEFAULT_FROM_ARGUMENT_DELIM,&offset,len) ) != NULL) { result = (g_strcmp0 (token,format) == 0); g_free (token); } } return result; } ////////////////////////////////////// static gint delete_wrong_formats (GList ** list, GlyrQuery * s) { /* Now compare it agains the format. */ gsize invalid_format_counter = 0; GList * new_head = *list; GList * elem = new_head; gchar * allowed_formats = s->allowed_formats; if (allowed_formats == NULL) { allowed_formats = GLYR_DEFAULT_ALLOWED_FORMATS; } while (elem != NULL) { GlyrMemCache * item = elem->data; if (item != NULL) { if (format_is_allowed (item->img_format,allowed_formats) == FALSE) { GList * to_delete = elem; elem = elem->next; invalid_format_counter++; new_head = g_list_delete_link (new_head,to_delete); DL_free (item); item = NULL; continue; } } elem = elem->next; } *list = new_head; return invalid_format_counter; } ////////////////////////////////////// static GList * kick_out_wrong_formats (GList * data_list, GlyrQuery * s) { GList * new_head = data_list; /* Parallely check if the format is what we wanted */ check_all_types_in_url_list (new_head,s); /* Kick the wrong ones */ gint invalid_format_counter = delete_wrong_formats (&new_head,s); glyr_message (2,s," (-%d item(s) less)\n",invalid_format_counter); return new_head; } ////////////////////////////////////// static void do_charset_conversion (MetaDataSource * source, GList * text_list) { if (source != NULL && text_list != NULL) { for (GList * elem = text_list; elem; elem = elem->next) { GlyrMemCache * cache = elem->data; /* We might need to unescape the HTML Utf8 encoded strings first, this is done later anyway. */ gchar * utf8_string = unescape_html_UTF8 (cache->data); if (utf8_string != NULL) { gsize new_len; gchar * conv = convert_charset (utf8_string,"UTF-8",source->encoding,&new_len); if (conv != NULL) { cache->size = new_len; g_free (cache->data); cache->data = conv; } g_free (utf8_string); } } } } ////////////////////////////////////// static GList * check_for_forced_utf8 (GlyrQuery * query, GList * text_list) { gint deleted = 0; GList * new_head = text_list; if (query != NULL && text_list != NULL && query->force_utf8 == TRUE) { glyr_message (2,query,"#[%02d/%02d] Checking encoding [",g_list_length (text_list),query->number); GList * elem = text_list; while (elem != NULL) { GlyrMemCache * cache = elem->data; const gchar * end_of_valid_utf8 = NULL; if (cache && g_utf8_validate (cache->data,cache->size,&end_of_valid_utf8) == FALSE) { /* UTF8 was forced, and this cache didn't pass -> deletre */ glyr_message (2,query,"!"); DL_free (cache); deleted++; GList * to_delete = elem; elem = elem->next; new_head = g_list_delete_link (new_head,to_delete); continue; } else { glyr_message (2,query,"."); elem = elem->next; } } glyr_message (2,query,"] (-%d item(s) less)\n",deleted); } return new_head; } ////////////////////////////////////// static void normalize_utf8 (GList * text_list) { for (GList * elem = text_list; elem; elem = elem->next) { GlyrMemCache * cache = elem->data; if (cache != NULL && cache->data) { if (g_utf8_validate (cache->data,-1,NULL) != FALSE) { gchar * normalized_utf8 = g_utf8_normalize (cache->data,-1,G_NORMALIZE_NFKC); if (normalized_utf8 != NULL) { /* Swap cache contents */ g_free (cache->data); cache->data = normalized_utf8; cache->size = strlen (normalized_utf8); } } } } } ////////////////////////////////////// static gint delete_already_cached_items (cb_object * capo, GList ** list) { if (capo->s->db_autoread == FALSE) { return 0; } gint deleted = 0; if (capo && capo->s->local_db) { GList * elem = *list; while (elem != NULL) { GlyrMemCache * item = elem->data; if (item != NULL && item->dsrc == NULL && capo->s->imagejob) { item->dsrc = g_strdup (item->data); } if (item && db_contains (capo->s->local_db,item) ) { GList * to_delete = elem; elem = elem->next; *list= g_list_delete_link (*list,to_delete); DL_free (item); deleted++; continue; } elem = elem->next; } } return deleted; } ////////////////////////////////////// static void fix_data_types (GList * list, MetaDataSource * src, GlyrQuery * query) { for (GList * elem = list; elem; elem = elem->next) { GlyrMemCache * item = elem->data; if (item != NULL && item->type == GLYR_TYPE_UNKNOWN) { if (TYPE_IS_IMAGE (query->type) && query->download == FALSE) { item->type = GLYR_TYPE_IMG_URL; } else { item->type = src->data_type; } } } } ////////////////////////////////////// /* The actual call to the metadata provider here, coming from the downloader, triggered by start_engine() */ static GList * call_provider_callback (cb_object * capo, void * userptr, bool * stop_download, gint * to_add) { GList * parsed = NULL; if (userptr != NULL) { /* Get MetaDataSource correlated to this URL */ GHashTable * assoc = (GHashTable*) userptr; MetaDataSource * plugin = g_hash_table_lookup (assoc,capo->url); if (plugin != NULL) { if (capo->s->itemctr < capo->s->number) { /* Call the provider's parser */ GList * raw_parsed_data = plugin->parser (capo); /* Set the default type if not known otherwise */ fix_data_types (raw_parsed_data,plugin,capo->s); /* Also do some duplicate check already */ gsize less = delete_dupes (raw_parsed_data,capo->s); if (less > 0) { gsize items_now = g_list_length (raw_parsed_data) + capo->s->itemctr - less; glyr_message (2,capo->s,"#[%02d/%02d] Inner check found %ld dupes\n",items_now,capo->s->number,less); } /* Look up if items already in cache */ less = delete_already_cached_items (capo,&raw_parsed_data); if (less > 0) { gsize items_now = g_list_length (raw_parsed_data) + capo->s->itemctr - less; glyr_message (2,capo->s,"#[%02d/%02d] DB lookup found %ld dupes\n",items_now,capo->s->number,less); } /* Any items left to kill? */ if (g_list_length (raw_parsed_data) != 0) { /* We shouldn't check (e.g) lyrics if they are a valid URL ;-) */ if (capo->s->imagejob == TRUE) { raw_parsed_data = kick_out_wrong_formats (raw_parsed_data,capo->s); } else /* We should look if charset conversion is requested */ { normalize_utf8 (raw_parsed_data); if (plugin->encoding != NULL) { glyr_message (2,capo->s,"#[%02d/%02d] Attempting to convert charsets\n",g_list_length (raw_parsed_data),capo->s->number); do_charset_conversion (plugin, raw_parsed_data); } raw_parsed_data = check_for_forced_utf8 (capo->s,raw_parsed_data); } if (g_list_length (raw_parsed_data) != 0) { for (GList * elem = raw_parsed_data; elem; elem = elem->next) { GlyrMemCache * item = elem->data; if (item != NULL) { if (capo->s->itemctr < capo->s->number) { /* Only reference to the plugin -> copy providername */ item->prov = g_strdup (plugin->name); parsed = g_list_prepend (parsed,item); capo->s->itemctr++; } else /* Not needed anymore. Forget this item */ { DL_free (item); item = NULL; /* Also skip other downloads */ *stop_download = TRUE; } } } /* Forget those pointers */ g_list_free (raw_parsed_data); } } } } else { glyr_message (1,capo->s,"glyr: hashmap lookup failed. Cannot call plugin => Bug.\n"); } } /* We replace the cache with a new one -> free the old one */ if (capo->cache != NULL) { DL_free (capo->cache); capo->cache = NULL; } /* Do not add raw data */ if (parsed == NULL) { *to_add = 0; } return parsed; } ////////////////////////////////////// gboolean provider_is_enabled (GlyrQuery * q, MetaDataSource * f) { if (q->lang_aware_only && f->lang_aware == false && q->imagejob == false && g_strcmp0 (f->name,"local") != 0) { return FALSE; } /* Assume 'all we have' */ if (q->from == NULL) { return TRUE; } /* You need to take a little break to read this through at once */ gboolean is_found = FALSE; gboolean is_excluded = FALSE; gboolean all_occured = FALSE; /* split string */ if (f->name != NULL) { gsize name_len = strlen (f->name); gsize len = strlen (q->from); gsize offset = 0; gchar * token = NULL; while ( (token = get_next_word (q->from,GLYR_DEFAULT_FROM_ARGUMENT_DELIM,&offset,len) ) ) { if (token != NULL) { gsize token_len = strlen (token); gchar * back = token; gboolean minus; if ( (minus = token[0] == '-') || token[0] == '+') token++; if (!g_ascii_strncasecmp (token,"all",token_len) ) all_occured = TRUE; if ( (token[0] == f->key && token_len == 1) || !g_ascii_strncasecmp (token,f->name,name_len) ) { is_excluded = minus; is_found = !minus; } g_free (back); } } } return (all_occured) ? (is_excluded == FALSE) : is_found; } ////////////////////////////////////// /* GnuPlot: plot3d(1/X*Y + (100-Y)*1/(1-X) + 1000,[X,0.1,0.9],[Y,0,100]); */ static gfloat calc_rating (gfloat qsratio, gint quality, gint speed) { gfloat cratio = CLAMP (qsratio,0.1,0.9); return 1000.0f + ( (1.0/ (1-cratio) *quality) + (1.0/cratio*speed) ); } ////////////////////////////////////// static GList * get_queued (GlyrQuery * s, MetaDataFetcher * fetcher, gint * fired) { GList * source_list = NULL; for (gint it = 0; it < s->parallel; it++) { gint pos = 0; gint max_pos = -1; gfloat max = G_MINFLOAT; for (GList * elem = fetcher->provider; elem; elem = elem->next, ++pos) { MetaDataSource * src = elem->data; if (provider_is_enabled (s,src) == TRUE) { if (fired[pos] == 0) { gfloat rating = calc_rating (s->qsratio,src->quality,src->speed); if (rating > max) { max = rating; max_pos = pos; } } } } if (max_pos != -1) { GList * wanted = g_list_nth (fetcher->provider,max_pos); if (wanted != NULL) { MetaDataSource * src = wanted->data; source_list = g_list_prepend (source_list,src); } fired[max_pos]++; } } if (source_list != NULL) { source_list = g_list_reverse (source_list); } return source_list; } ////////////////////////////////////// gboolean is_in_result_list (GlyrMemCache * cache, GList * result_list) { gboolean result = FALSE; if (cache != NULL) { for (GList * elem = result_list; elem; elem = elem->next) { GlyrMemCache * item = elem->data; if (memcmp (cache->md5sum, item->md5sum, 16) == 0) { result = TRUE; } } } return result; } ////////////////////////////////////// static void execute_query (GlyrQuery * query, MetaDataFetcher * fetcher, GList * source_list, gboolean * stop_me, GList ** result_list) { GList * url_list = NULL; GList * endmarks = NULL; GList * offline_provider = NULL; GHashTable * url_table = g_hash_table_new (g_str_hash,g_str_equal); /* Iterate over all sources for this fetcher */ for (GList * source = source_list; source != NULL; source = source->next) { MetaDataSource * item = source->data; if (item != NULL) { /* get the url of this MetaDataSource */ const gchar * lookup_url = item->get_url (query); /* Add this to the list */ if (lookup_url != NULL) { if (g_ascii_strncasecmp (lookup_url,OFFLINE_PROVIDER, (sizeof OFFLINE_PROVIDER) - 1) != 0) { /* make a sane URL out of it */ const gchar * prepared = prepare_url (lookup_url,query,TRUE); /* add it to the hash table and relate it to the MetaDataSource */ g_hash_table_insert (url_table, (gpointer) prepared, (gpointer) item); url_list = g_list_prepend (url_list, (gpointer) prepared); endmarks = g_list_prepend (endmarks, (gpointer) item->endmarker); /* If the URL was dyn. allocated, we should go and free it */ if (item->free_url == TRUE) { g_free ( (gchar*) lookup_url); } } else { /* This providers offers some autogenerated content */ offline_provider = g_list_prepend (offline_provider,item); } } } } GList * sub_result_list = NULL; gsize url_list_length = g_list_length (url_list); if (url_list_length != 0 || g_list_length (offline_provider) != 0) { gboolean proceed = TRUE; GList * cached_items = NULL; GList * raw_parsed = NULL; GList * ready_caches = NULL; /* Handle offline provider, that don't need to download something */ for (GList * off_source = offline_provider; proceed && off_source && query->itemctr < query->number; off_source = off_source->next) { MetaDataSource * source = off_source->data; if (source != NULL && source->parser != NULL) { cb_object pseudo_capo; memset (&pseudo_capo, 0, sizeof pseudo_capo); pseudo_capo.s = query; GList * offline_list = source->parser (&pseudo_capo); if (query->imagejob) { delete_wrong_formats (&offline_list,query); } else { offline_list = check_for_forced_utf8 (query,offline_list); } for (GList * off_elem = offline_list; off_elem && query->itemctr < query->number; off_elem = off_elem->next) { GLYR_ERROR result = GLYRE_OK; if (query->callback.download != NULL) { result = query->callback.download (off_elem->data,query); } if (result != GLYRE_STOP_PRE && result != GLYRE_SKIP) { cached_items = g_list_prepend (cached_items,off_elem->data); query->itemctr++; } if (result == GLYRE_STOP_PRE || result == GLYRE_STOP_POST) { proceed = FALSE; break; } } g_list_free (offline_list); } } /* Now start the downloadmanager - and call the specified callback with the URL table when an item is ready */ if (proceed == TRUE && url_list_length != 0 && query->itemctr < query->number) { raw_parsed = async_download (url_list, endmarks, query, url_list_length / query->timeout + 1, MIN ( (gint) (url_list_length / query->parallel + 3), query->number + 2), call_provider_callback, url_table, TRUE); } /* Now finalize our retrieved items */ if (g_list_length (raw_parsed) != 0) { /* Kill duplicates before finalizing */ int pre_less = delete_dupes (raw_parsed,query); if (pre_less > 0) { glyr_message (2,query,"- Prefiltering double data: (-%d item(s) less)\n",pre_less); query->itemctr -= pre_less; } glyr_message (2,query,"---- \n"); if (g_list_length (raw_parsed) != 0) { /* Call finalize to sanitize data, or download given URLs */ ready_caches = fetcher->finalize (query, raw_parsed,stop_me, result_list); /* Raw data not needed anymore */ g_list_free (raw_parsed); raw_parsed = NULL; } } if (cached_items && ready_caches) { sub_result_list = g_list_concat (cached_items, ready_caches); } else { sub_result_list = (cached_items) ? cached_items : ready_caches; } } /* Free ressources */ glist_free_full (url_list,g_free); g_list_free (endmarks); g_list_free (offline_provider); g_hash_table_destroy (url_table); for (GList * result = sub_result_list; result; result = result->next) { GlyrMemCache * result_cache = result->data; if (result_cache != NULL) { *result_list = g_list_prepend (*result_list,result->data); } } g_list_free (sub_result_list); } ////////////////////////////////////// static void print_trigger (GlyrQuery * query, GList * src_list) { glyr_message (2,query,"---- Triggering: "); for (GList * elem = src_list; elem; elem = elem->next) { MetaDataSource * info = elem->data; glyr_message (2,query,"%s ",info->name); } glyr_message (2,query,"\n"); } ////////////////////////////////////// GList * start_engine (GlyrQuery * query, MetaDataFetcher * fetcher, GLYR_ERROR * err) { gsize list_len = g_list_length (fetcher->provider); gint fired[list_len]; memset (fired,0,list_len * sizeof (gint) ); gboolean something_was_searched = FALSE; gboolean stop_now = FALSE; GList * src_list = NULL, * result_list = NULL; while ( (stop_now == FALSE) && (g_list_length (result_list) < (gsize) query->number) && (src_list = get_queued (query, fetcher, fired) ) != NULL) { /* Print what provider were triggered */ print_trigger (query,src_list); /* Send this list of sources to the download manager */ execute_query (query,fetcher,src_list, &stop_now, &result_list); /* Do not report errors */ something_was_searched = TRUE; /* Next list please */ g_list_free (src_list); /* Check is exit was signaled */ stop_now = (GET_ATOMIC_SIGNAL_EXIT (query) ) ? TRUE : stop_now; } if (something_was_searched == FALSE) { if (err != NULL) { *err = GLYRE_NO_PROVIDER; } if (query != NULL) { query->q_errno = GLYRE_NO_PROVIDER; } } return result_list; } ////////////////////////////////////// /* New glib implementation, thanks to Etienne Millon */ void update_md5sum (GlyrMemCache * c) { if (c && c->data && c->size > 0) { gsize bufsize = 16; GChecksum *checksum = g_checksum_new (G_CHECKSUM_MD5); g_checksum_update (checksum, (const guchar*) c->data, c->size); g_checksum_get_digest (checksum, c->md5sum, &bufsize); g_checksum_free (checksum); } } ////////////////////////////////////// void glist_free_full (GList * List, void (* free_func) (void * ptr) ) { #if GLIB_CHECK_VERSION(2,28,0) /* Use official version */ g_list_free_full (List,free_func); #else /* Fallback to simple own implementation */ for (GList * elem = List; elem; elem = elem->next) { if (free_func != NULL) { free_func (elem->data); } } g_list_free (List); #endif } ////////////////////////////////////// glyr-1.0.10/lib/core.h000066400000000000000000000150511301162614000144110ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of musicrelated metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #ifndef CORE_H #define CORE_H #include "types.h" #include "apikeys.h" #include "config.h" /* Global */ #include #include #include /* * Logdomain. * Use --log-filter="Glyr" for gmpc to * just see glyrs debug output */ #ifdef G_LOG_DOMAIN #undef G_LOG_DOMAIN #endif #define G_LOG_DOMAIN "Glyr" /* libglyr uses checksums to filter double items * Also you can use those as easy comparasion method * There is no valid reason to diasable this actually */ #define CALC_MD5SUMS true /* Returned by get_url() in case of offline provider */ #define OFFLINE_PROVIDER "autogenerated_content" /* This needs to be updated in case of new image getters.. this is a bit silly */ #define TYPE_IS_IMAGE(TYPE) (TYPE == GLYR_GET_COVERART || TYPE == GLYR_GET_ARTIST_PHOTOS || TYPE == GLYR_GET_BACKDROPS) /* Get signal_exit with atomic operations */ #define GET_ATOMIC_SIGNAL_EXIT(QUERY) (g_atomic_int_get(&((QUERY)->signal_exit))) #define SET_ATOMIC_SIGNAL_EXIT(QUERY,V) (g_atomic_int_set(&((QUERY)->signal_exit),V)) /* Feels a little hackish - but works with extremely high probability :-) */ #define QUERY_INITIALIZER 0xDEADBEEF #define QUERY_IS_INITALIZED(Q) (Q && Q->is_initalized == QUERY_INITIALIZER) /*------------------------------------------------------*/ /* ----------------- Messages ------------------------- */ /*------------------------------------------------------*/ int glyr_message (int v, GlyrQuery * s, const char * fmt, ...); /*------------------------------------------------------*/ /* Used to pass arguments to DL_buffer() */ typedef struct { GlyrMemCache * cache; GlyrQuery * query; char * endmarker; } DLBufferContainer; /*------------------------------------------------------*/ // Internal calback object, used for cover, lyrics and other // This is only used inside the core and the plugins // Other parts of the program shall not use this struct // GlyrMemCache is what you're searching // It models the data that one plugin needs. typedef struct cb_object { // What url to download before the callback is called char *url; // What curl handle this is attached CURL *handle; // pointer to settings struct (artist,album,etc) GlyrQuery * s; // internal cache attached to this url GlyrMemCache *cache; // has this struct been consumed? gboolean consumed; // If this item reached DL_buff gboolean was_buffered; // DLBuffer data DLBufferContainer * dlbuffer; } cb_object; /*------------------------------------------------------*/ // Internal representation of one metadataprovider // PLEASE FILL _ALL_ FIELDS! typedef struct MetaDataFetcher { /* cover, lyrics, stuff */ const char * name; /* A List of MetaDataSources */ GList * provider; /* what this thing delievers; e.g. GLYR_GET_COVERART */ GLYR_GET_TYPE type; /* callbacks */ void (*init) (void); void (*destroy) (void); GList* (*finalize) (GlyrQuery*,GList*,gboolean*,GList**); /* Default value for ->parallel, if set to auto */ long default_parallel; /* Optionaly and required fields for this getter */ GLYR_FIELD_REQUIREMENT reqs; /* Wether this Fetcher delievers the full data (lyrics), or just URLs of the data. */ gboolean full_data; /* Default data type to assume for providers */ GLYR_DATA_TYPE default_data_type; } MetaDataFetcher; /*------------------------------------------------------*/ /* Internal representation of one provider */ typedef struct MetaDataSource { gchar * name; /* Name of this provider */ gchar key; /* A key that may be used in --from */ GList * (* parser) (struct cb_object *); /* called when parsing is needed */ const char * (* get_url) (GlyrQuery *); /* called when the url of this provider is needed */ GLYR_GET_TYPE type; /* For what fetcher this provider is working.. */ gboolean free_url; /* URL is dyn. allocated - set this always! */ gchar * encoding; /* Encoding, NULL defaults to UTF-8, this will only take place for textparser */ gchar * endmarker; /* Download stops if this mark is found */ gint quality; /* Measurement of how good the content usually is [0-100] */ gint speed; /* Measurement of how fast the provider usually is [0-100] */ gboolean lang_aware; /* Has language specific content? */ GLYR_DATA_TYPE data_type; /* Default datatype this provider delievers */ } MetaDataSource; /*------------------------------------------------------*/ typedef GList* (*AsyncDLCB) (cb_object*,void *,bool*,gint*); GList * async_download (GList * url_list, GList * endmark_list, GlyrQuery * s, long parallel_fac, long timeout_fac, AsyncDLCB callback, void * userptr, gboolean free_caches); GList * start_engine (GlyrQuery * query, MetaDataFetcher * fetcher, GLYR_ERROR * err); GlyrMemCache * download_single (const char* url, GlyrQuery * s, const char * end); /*------------------------------------------------------*/ GlyrMemCache * DL_init (void); GlyrMemCache * DL_copy (GlyrMemCache * cache); void DL_free (GlyrMemCache *cache); void DL_set_data (GlyrMemCache * cache, const gchar * data, gint len); /*------------------------------------------------------*/ void update_md5sum (GlyrMemCache * c); void glist_free_full (GList * List, void (* free_func) (void * ptr) ); /*------------------------------------------------------*/ gboolean size_is_okay (int sZ, int min, int max); gboolean is_in_result_list (GlyrMemCache * cache, GList * result_list); gboolean provider_is_enabled (GlyrQuery * q, MetaDataSource * f); gboolean continue_search (gint current, GlyrQuery * s); #endif glyr-1.0.10/lib/glyr.c000066400000000000000000001112351301162614000144320ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a command-line tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include #include #include "glyr.h" #include "core.h" #include "register_plugins.h" #include "blacklist.h" #include "cache.h" #include "stringlib.h" ////////////////////////////////// static volatile gboolean is_initalized = FALSE; ////////////////////////////////// static const char * err_strings[] = { [GLYRE_UNKNOWN] = "Unknown error", [GLYRE_OK] = "No error", [GLYRE_BAD_VALUE] ="Bad value for glyr_opt_[...]()", [GLYRE_BAD_OPTION] = "Bad option passed to gly_opt_[...]()", [GLYRE_NO_PROVIDER] = "No valid provider specified in glyr_opt_from()", [GLYRE_EMPTY_STRUCT] = "Empty Query structure (NULL)", [GLYRE_UNKNOWN_GET] = "Unknown GLYR_GET_TYPE in glyr_get(); Use glyr_opt_type()", [GLYRE_INSUFF_DATA] = "Insufficient data supplied for this getter", [GLYRE_SKIP] = "Item was skipped due to user", [GLYRE_STOP_POST] = "Stopped by callback (POST)", [GLYRE_STOP_PRE] = "Stopped by callback (PRE)", [GLYRE_NO_INIT] = "Library is not yet initialized, use glyr_init()", [GLYRE_WAS_STOPPED] = "Library was stopped by glyr_signal_exit()" }; static const char * type_strings[] = { [GLYR_TYPE_COVERART] = "cover", [GLYR_TYPE_LYRICS] = "songtext", [GLYR_TYPE_ARTIST_PHOTO] = "artistphoto", [GLYR_TYPE_ALBUM_REVIEW] = "albumreview", [GLYR_TYPE_ARTIST_BIO] = "artistbio", [GLYR_TYPE_SIMILAR_ARTIST] = "similar_artist", [GLYR_TYPE_SIMILAR_SONG] = "similar_song", [GLYR_TYPE_TRACK] = "trackname", [GLYR_TYPE_ALBUMLIST] = "albumname", [GLYR_TYPE_TAG] = "tag", [GLYR_TYPE_TAG_ARTIST] = "artisttag", [GLYR_TYPE_TAG_ALBUM] = "albumtag", [GLYR_TYPE_TAG_TITLE] = "titletag", [GLYR_TYPE_RELATION] = "relation", [GLYR_TYPE_IMG_URL] = "ImageURL", [GLYR_TYPE_TXT_URL] = "HTMLURL", [GLYR_TYPE_GUITARTABS] = "guitartabs", [GLYR_TYPE_BACKDROPS] = "backdrop", [GLYR_TYPE_UNKNOWN] = "unknown" }; ///////////////////////////////// const gchar * map_language[][2] = { {"en_US","us"}, {"en_CA","ca"}, {"en_UK","uk"} }; ///////////////////////////////// void glyr_internal_log (const gchar *log_domain,GLogLevelFlags log_level,const gchar *message, gpointer user_data) { if (message != NULL) fputs (message,GLYR_OUTPUT); } ///////////////////////////////// /* Local scopes are cool. */ #define END_STRING(STR,CHAR) \ { \ gchar * term = strchr(STR,CHAR); \ if(term) *term = 0; \ } /** * @brief Guesses the users language (in ISO639-1 codes like 'de') by the system locale * * @return a newly allocated language code. Free. */ gchar * guess_language (void) { /* Default to 'en' in any case */ gchar * result_lang = g_strdup ("en"); #if GLIB_CHECK_VERSION(2,28,0) gboolean break_out = FALSE; /* Please never ever free this */ const gchar * const * languages = g_get_language_names(); for (gint i = 0; languages[i] && break_out == FALSE; i++) { gchar ** variants = g_get_locale_variants (languages[i]); for (gint j = 0; variants[j] && !break_out; j++) { /* Look up if we need to map a language */ gchar * to_investigate = variants[j]; gint map_size = (sizeof (map_language) / (2 * sizeof (char*) ) ); for (gint map = 0; map < map_size; map++) { const gchar * to_map = map_language[map][0]; gsize map_len = strlen (to_map); if (g_ascii_strncasecmp (to_map,to_investigate,map_len) == 0) { to_investigate = (gchar*) map_language[map][1]; break; } } gboolean allowed_lang = TRUE; if (allowed_lang && g_ascii_strncasecmp ("en",to_investigate,2) != 0 && g_ascii_strncasecmp ("C", to_investigate,1) != 0 && !strchr (to_investigate,'@') && !strchr (to_investigate,'.') ) { g_free (result_lang); result_lang = g_strdup (to_investigate); break_out = TRUE; } } g_strfreev (variants); } #elif GLIB_CHECK_VERSION(2,26,0) /* Fallback to simpler version of the above, * g_get_locale_variants is not there in this version */ const gchar * const * possible_locales = g_get_language_names(); if (possible_locales != NULL) { /* might be a bit weird */ for (gint i = 0; possible_locales[i]; i++) { if (g_ascii_strncasecmp ("en",possible_locales[i],2) != 0 && g_ascii_strncasecmp ("C", possible_locales[i],1) != 0) { g_free (result_lang); result_lang = g_strdup (possible_locales[i]); break; } } } #else /* Fallback for version prior GLib 2.26: * Fallback to "en" always. * Gaaah... shame on you if this happens to you ;-) */ #endif /* Properly terminate string */ END_STRING (result_lang,'_'); END_STRING (result_lang,'@'); END_STRING (result_lang,'.'); /* We don't need it anymore */ #undef END_STRING return result_lang; } ///////////////////////////////// static int glyr_set_info (GlyrQuery * s, int at, const char * arg); static void set_query_on_defaults (GlyrQuery * glyrs); ///////////////////////////////// // OTHER ///////////////////////////////// // return a descriptive string on error ID __attribute__ ( (visibility ("default") ) ) const char * glyr_strerror (GLYR_ERROR ID) { if (ID < (sizeof (err_strings) /sizeof (const char *) ) ) { return err_strings[ID]; } return err_strings[GLYRE_UNKNOWN]; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_signal_exit (GlyrQuery * query) { SET_ATOMIC_SIGNAL_EXIT (query,1); } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_cache_update_md5sum (GlyrMemCache * cache) { update_md5sum (cache); } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_cache_set_data (GlyrMemCache * cache, const char * data, int len) { DL_set_data (cache,data,len); } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GlyrMemCache * glyr_cache_copy (GlyrMemCache * cache) { return DL_copy (cache); } ///////////////////////////////// #ifndef BUILD_DATE #define BUILD_DATE __DATE__ #endif #ifndef BUILD_TIME #define BUILD_TIME __TIME__ #endif __attribute__ ( (visibility ("default") ) ) const char * glyr_version (void) { return "Version "GLYR_VERSION_MAJOR"."GLYR_VERSION_MINOR"."GLYR_VERSION_MICRO" ("GLYR_VERSION_NAME") of ["BUILD_DATE"] compiled at ["BUILD_TIME"]"; } ///////////////////////////////// // _opt_ ///////////////////////////////// // Seperate method because va_arg struggles with function pointers __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_dlcallback (GlyrQuery * settings, DL_callback dl_cb, void * userp) { if (settings) { settings->callback.download = dl_cb; settings->callback.user_pointer = userp; return GLYRE_OK; } return GLYRE_EMPTY_STRUCT; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_type (GlyrQuery * s, GLYR_GET_TYPE type) { if (s == NULL) return GLYRE_EMPTY_STRUCT; if (type != GLYR_GET_UNKNOWN) { s->type = type; return GLYRE_OK; } return GLYRE_BAD_VALUE; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_artist (GlyrQuery * s, const char * artist) { if (s == NULL) return GLYRE_EMPTY_STRUCT; glyr_set_info (s,0,artist); return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_album (GlyrQuery * s, const char * album) { if (s == NULL) return GLYRE_EMPTY_STRUCT; glyr_set_info (s,1,album); return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_title (GlyrQuery * s, const char * title) { if (s == NULL) return GLYRE_EMPTY_STRUCT; glyr_set_info (s,2,title); return GLYRE_OK; } ///////////////////////////////// static int size_set (int * ref, int size) { if (size < -1 && ref) { *ref = -1; return GLYRE_BAD_VALUE; } if (ref) { *ref = size; return GLYRE_OK; } return GLYRE_BAD_OPTION; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_img_maxsize (GlyrQuery * s, int size) { if (s == NULL) return GLYRE_EMPTY_STRUCT; return size_set (&s->img_max_size,size); } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_img_minsize (GlyrQuery * s, int size) { if (s == NULL) return GLYRE_EMPTY_STRUCT; return size_set (&s->img_min_size,size); } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_parallel (GlyrQuery * s, unsigned long val) { if (s == NULL) return GLYRE_EMPTY_STRUCT; s->parallel = (long) val; return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_timeout (GlyrQuery * s, unsigned long val) { if (s == NULL) return GLYRE_EMPTY_STRUCT; s->timeout = (long) val; return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_redirects (GlyrQuery * s, unsigned long val) { if (s == NULL) return GLYRE_EMPTY_STRUCT; s->redirects = (long) val; return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_useragent (GlyrQuery * s, const char * useragent) { if (s == NULL) return GLYRE_EMPTY_STRUCT; glyr_set_info (s,6, (useragent) ? useragent : ""); return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_lang (GlyrQuery * s, const char * langcode) { if (s == NULL) return GLYRE_EMPTY_STRUCT; if (langcode != NULL) { if (g_ascii_strncasecmp ("auto",langcode,4) == 0) { gchar * auto_lang = guess_language(); glyr_set_info (s,7,auto_lang); g_free (auto_lang); } else { glyr_set_info (s,7,langcode); } return GLYRE_OK; } return GLYRE_BAD_VALUE; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_lang_aware_only (GlyrQuery * s, bool lang_aware_only) { if (s == NULL) return GLYRE_EMPTY_STRUCT; s->lang_aware_only = lang_aware_only; return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_number (GlyrQuery * s, unsigned int num) { if (s == NULL) return GLYRE_EMPTY_STRUCT; s->number = num == 0 ? INT_MAX : num; return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_verbosity (GlyrQuery * s, unsigned int level) { if (s == NULL) return GLYRE_EMPTY_STRUCT; s->verbosity = level; return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_from (GlyrQuery * s, const char * from) { if (s == NULL) return GLYRE_EMPTY_STRUCT; if (from != NULL) { glyr_set_info (s,4,from); return GLYRE_OK; } return GLYRE_BAD_VALUE; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_allowed_formats (GlyrQuery * s, const char * formats) { if (s == NULL) return GLYRE_EMPTY_STRUCT; glyr_set_info (s,5, (formats==NULL) ? GLYR_DEFAULT_ALLOWED_FORMATS : formats); return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_musictree_path (GlyrQuery * s, const char * musictree_path) { if (s == NULL) return GLYRE_EMPTY_STRUCT; glyr_set_info (s,8, (musictree_path==NULL) ? GLYR_DEFAULT_MUISCTREE_PATH : musictree_path); return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_plugmax (GlyrQuery * s, int plugmax) { if (s == NULL) return GLYRE_EMPTY_STRUCT; if (plugmax < 0) { return GLYRE_BAD_VALUE; } s->plugmax = plugmax; return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_qsratio (GlyrQuery * s, float ratio) { if (s == NULL) return GLYRE_EMPTY_STRUCT; s->qsratio = MIN (MAX (ratio,0.0),1.0); return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_proxy (GlyrQuery * s, const char * proxystring) { if (s == NULL) return GLYRE_EMPTY_STRUCT; glyr_set_info (s,3,proxystring); return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_fuzzyness (GlyrQuery * s, int fuzz) { if (s == NULL) return GLYRE_EMPTY_STRUCT; s->fuzzyness = fuzz; return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_download (GlyrQuery * s, bool download) { if (s == NULL) return GLYRE_EMPTY_STRUCT; s->download = download; return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_force_utf8 (GlyrQuery * s, bool force_utf8) { if (s == NULL) return GLYRE_EMPTY_STRUCT; s->force_utf8 = force_utf8; return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_lookup_db (GlyrQuery * s, GlyrDatabase * db) { if (s == NULL) return GLYRE_EMPTY_STRUCT; if (db != NULL) { s->local_db = db; return GLYRE_OK; } return GLYRE_BAD_VALUE; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_db_autowrite (GlyrQuery * s, bool db_autowrite) { if (s == NULL) return GLYRE_EMPTY_STRUCT; s->db_autowrite = db_autowrite; return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_db_autoread (GlyrQuery * s, bool db_autoread) { if (s == NULL) return GLYRE_EMPTY_STRUCT; s->db_autoread = db_autoread; return GLYRE_OK; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_ERROR glyr_opt_normalize (GlyrQuery * s, GLYR_NORMALIZATION norm) { if (s == NULL) return GLYRE_EMPTY_STRUCT; s->normalization = norm; return GLYRE_OK; } ///////////////////////////////// ///////////////////////////////// ///////////////////////////////// static void set_query_on_defaults (GlyrQuery * glyrs) { if (glyrs == NULL) { return; } /* Initialize free pointer pool */ memset (glyrs,0,sizeof (GlyrQuery) ); glyrs->type = GLYR_GET_UNKNOWN; glyrs->artist = NULL; glyrs->album = NULL; glyrs->title = NULL; glyrs->local_db = NULL; glyrs->callback.download = NULL; glyrs->callback.user_pointer = NULL; glyrs->musictree_path = NULL; glyrs->q_errno = GLYRE_OK; glyrs->db_autoread = GLYR_DEFAULT_DB_AUTOREAD; glyrs->db_autowrite = GLYR_DEFAULT_DB_AUTOWRITE; glyrs->from = GLYR_DEFAULT_FROM; glyrs->img_min_size = GLYR_DEFAULT_CMINSIZE; glyrs->img_max_size = GLYR_DEFAULT_CMAXSIZE; glyrs->number = GLYR_DEFAULT_NUMBER; glyrs->parallel = GLYR_DEFAULT_PARALLEL; glyrs->redirects = GLYR_DEFAULT_REDIRECTS; glyrs->timeout = GLYR_DEFAULT_TIMEOUT; glyrs->verbosity = GLYR_DEFAULT_VERBOSITY; glyrs->plugmax = GLYR_DEFAULT_PLUGMAX; glyrs->download = GLYR_DEFAULT_DOWNLOAD; glyrs->fuzzyness = GLYR_DEFAULT_FUZZYNESS; glyrs->proxy = GLYR_DEFAULT_PROXY; glyrs->qsratio = GLYR_DEFAULT_QSRATIO; glyrs->allowed_formats = GLYR_DEFAULT_ALLOWED_FORMATS; glyrs->useragent = GLYR_DEFAULT_USERAGENT; glyrs->force_utf8 = GLYR_DEFAULT_FORCE_UTF8; glyrs->lang = GLYR_DEFAULT_LANG; glyrs->lang_aware_only = GLYR_DEFAULT_LANG_AWARE_ONLY; glyrs->normalization = GLYR_NORMALIZE_AGGRESSIVE | GLYR_NORMALIZE_ALL; glyrs->signal_exit = FALSE; glyrs->itemctr = 0; /* Set on a very specific value, so we can pretty sure, we are not accessing bad memory - feels little hackish.. */ glyrs->is_initalized = QUERY_INITIALIZER; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_query_init (GlyrQuery * glyrs) { if (glyrs != NULL) { set_query_on_defaults (glyrs); } } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_query_destroy (GlyrQuery * sets) { if (sets != NULL && QUERY_IS_INITALIZED (sets) ) { for (gsize i = 0; i < 10; i++) { if (sets->info[i] != NULL) { g_free ( (char*) sets->info[i]); sets->info[i] = NULL; } } /* Reset query so it can be used again */ set_query_on_defaults (sets); } } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GlyrMemCache * glyr_download (const char * url, GlyrQuery * s) { return download_single (url,s,NULL); } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_free_list (GlyrMemCache * head) { if (head != NULL) { GlyrMemCache * next = head; GlyrMemCache * prev = head->prev; while (next != NULL) { GlyrMemCache * p = next; next = next->next; DL_free (p); } while (prev != NULL) { GlyrMemCache * p = prev; prev = prev->prev; DL_free (p); } } } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_cache_free (GlyrMemCache * c) { DL_free (c); } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GlyrMemCache * glyr_cache_new (void) { return DL_init(); } ///////////////////////////////// // !! NOT THREADSAFE !! // __attribute__ ( (visibility ("default") ) ) void glyr_init (void) { /* Protect agains double initialization */ if (is_initalized == FALSE) { /* Set loghandler */ g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, glyr_internal_log, NULL); if (curl_global_init (CURL_GLOBAL_ALL) ) { glyr_message (-1,NULL,"Fatal: libcurl failed to init\n"); } /* Locale */ if (setlocale (LC_ALL, "") == NULL) { glyr_message (-1,NULL,"Cannot set locale!\n"); } /* Register plugins */ register_fetcher_plugins(); /* Init the smallest blacklist in the world :-) */ blacklist_build(); is_initalized = TRUE; } } ///////////////////////////////// // !! NOT THREADSAFE !! // __attribute__ ( (visibility ("default") ) ) void glyr_cleanup (void) { if (is_initalized == TRUE) { /* Curl no longer needed */ curl_global_cleanup(); /* Destroy all fetchers */ unregister_fetcher_plugins(); /* Kill it again */ blacklist_destroy(); is_initalized = FALSE; } } ///////////////////////////////// /* Sets parallel field depending on the get_type */ static void auto_detect_parallel (MetaDataFetcher * fetcher, GlyrQuery * query) { if (query->parallel <= 0) { if (fetcher->default_parallel <= 0) { gint div = (int) (1.0/ (CLAMP (query->qsratio,0.01,0.99) * 2) ); query->parallel = (div == 0) ? 3 : g_list_length (fetcher->provider) / div; } else { query->parallel = fetcher->default_parallel; } } } ///////////////////////////////// static gboolean check_if_valid (GlyrQuery * q, MetaDataFetcher * fetch) { gboolean isValid = TRUE; if (fetch->reqs & GLYR_REQUIRES_ARTIST && q->artist == NULL) { glyr_message (2,q,"Artist is required for this getter\n"); isValid = FALSE; } if (fetch->reqs & GLYR_REQUIRES_ALBUM && q->album == NULL) { glyr_message (2,q,"Albumname is required for this getter\n"); isValid = FALSE; } if (fetch->reqs & GLYR_REQUIRES_TITLE && q->title == NULL) { glyr_message (2,q,"Songname is required for this getter\n"); isValid = FALSE; } if (isValid == FALSE && fetch->reqs > GLYR_REQUIRES_TITLE) { char * f1 = "",* f2 = "", * f3 = ""; if (fetch->reqs & GLYR_OPTIONAL_ARTIST) f1 = "Artist "; if (fetch->reqs & GLYR_OPTIONAL_ALBUM) f2 = "Album "; if (fetch->reqs & GLYR_OPTIONAL_TITLE) f3 = "Songtitle "; if (*f1 || *f2 || *f3) { glyr_message (2,q,"\nFollowing fields are optional: %s%s%s\n",f1,f2,f3); } } return isValid; } ///////////////////////////////// static void set_error (GLYR_ERROR err_in, GlyrQuery * query, GLYR_ERROR * err_out) { if (query) query->q_errno = err_in; if (err_out) *err_out = err_in; } ///////////////////////////////// #define PRINT_NORMALIZED_ATTR(name, mode, var) \ if(var != NULL && query->verbosity >= 2) \ { \ char * prepared = prepare_string(var, mode, FALSE); \ if(prepared != NULL) \ { \ glyr_message(2, query, name); \ glyr_message(2, query, "%s\n", prepared); \ g_free(prepared); \ } \ } \ __attribute__ ( (visibility ("default") ) ) GlyrMemCache * glyr_get (GlyrQuery * query, GLYR_ERROR * e, int * length) { if (is_initalized == FALSE || QUERY_IS_INITALIZED (query) == FALSE) { glyr_message (-1,NULL,"Warning: Either query or library is not initialized.\n"); if (e != NULL) { set_error (GLYRE_NO_INIT, query, e); } return NULL; } set_error (GLYRE_OK, query, e); if (query != NULL) { if (g_ascii_strncasecmp (query->lang,"auto",4) == 0) { glyr_opt_lang (query,"auto"); } GList * result = NULL; set_error (GLYRE_UNKNOWN_GET, query, e); for (GList * elem = r_getFList(); elem; elem = elem->next) { MetaDataFetcher * item = elem->data; if (query->type == item->type) { if (check_if_valid (query,item) == TRUE) { /* Print some user info, always useful */ if (query->normalization & GLYR_NORMALIZE_ARTIST) { PRINT_NORMALIZED_ATTR("- Artist : ", query->normalization, query->artist); } else { PRINT_NORMALIZED_ATTR("- Artist : ", GLYR_NORMALIZE_NONE, query->artist); } if (query->normalization & GLYR_NORMALIZE_ALBUM) { PRINT_NORMALIZED_ATTR("- Album : ", query->normalization, query->album); } else { PRINT_NORMALIZED_ATTR("- Album : ", GLYR_NORMALIZE_NONE, query->album); } if (query->normalization & GLYR_NORMALIZE_TITLE) { PRINT_NORMALIZED_ATTR("- Title : ", query->normalization, query->title); } else { PRINT_NORMALIZED_ATTR("- Title : ", GLYR_NORMALIZE_NONE, query->title); } if (query->lang != NULL) { glyr_message (2,query,"- Language : "); glyr_message (2,query,"%s\n",query->lang); } set_error (GLYRE_OK, query, e); glyr_message (2,query,"- Type : %s\n\n",item->name); /* Lookup what we search for here: Images (url, or raw) or text */ query->imagejob = ! (item->full_data); /* If ->parallel is <= 0, it gets autodetected */ auto_detect_parallel (item, query); /* Now start your engines, gentlemen */ result = start_engine (query,item,e); break; } else { set_error (GLYRE_INSUFF_DATA, query, e); } } } /* Make this query reusable */ query->itemctr = 0; /* Start of the returned list */ GlyrMemCache * head = NULL; /* Librarby was stopped, just return NULL. */ if (result != NULL && GET_ATOMIC_SIGNAL_EXIT (query) ) { for (GList * elem = result; elem; elem = elem->next) DL_free (elem->data); g_list_free (result); result = NULL; set_error (GLYRE_WAS_STOPPED, query, e); } /* Set the length */ if (length != NULL) { *length = g_list_length (result); } /* free if empty */ if (result != NULL) { /* Count inserstions */ gint db_inserts = 0; /* link caches to each other */ for (GList * elem = result; elem; elem = elem->next) { GlyrMemCache * item = elem->data; item->next = (elem->next) ? elem->next->data : NULL; item->prev = (elem->prev) ? elem->prev->data : NULL; if (query->db_autowrite && query->local_db && item->cached == FALSE) { db_inserts++; glyr_db_insert (query->local_db,query,item); } } if (db_inserts > 0) { glyr_message (2,query,"--- Inserted %d item%s into db.\n",db_inserts, (db_inserts == 1) ? "" : "s"); } /* Finish. */ if (g_list_first (result) ) { head = g_list_first (result)->data; } g_list_free (result); result = NULL; } /* Done! */ SET_ATOMIC_SIGNAL_EXIT (query,0); return head; } set_error (GLYRE_EMPTY_STRUCT, query, e); SET_ATOMIC_SIGNAL_EXIT (query,0); return NULL; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) int glyr_cache_write (GlyrMemCache * data, const char * path) { int bytes = -1; if (path) { if (!g_ascii_strcasecmp (path,"null") ) { bytes = 0; } else if (!g_ascii_strcasecmp (path,"stdout") ) { bytes=fwrite (data->data,1,data->size,stdout); fputc ('\n',stdout); } else if (!g_ascii_strcasecmp (path,"stderr") ) { bytes=fwrite (data->data,1,data->size,stderr); fputc ('\n',stderr); } else { FILE * fp = fopen (path,"w"); if (fp) { if (data->data != NULL) { bytes=fwrite (data->data,1,data->size,fp); } fclose (fp); } else { glyr_message (-1,NULL,"glyr_cache_write: Unable to write to '%s'!\n",path); } } } return bytes; } ///////////////////////////////// static int glyr_set_info (GlyrQuery * s, int at, const char * arg) { gint result = GLYRE_OK; if (s && arg && at >= 0 && at < 10) { if (s->info[at] != NULL) { g_free ( (char*) s->info[at]); } s->info[at] = g_strdup (arg); switch (at) { case 0: s->artist = (gchar*) s->info[at]; break; case 1: s->album = (gchar*) s->info[at]; break; case 2: s->title = (gchar*) s->info[at]; break; case 3: s->proxy = s->info[at]; break; case 4: s->from = (gchar*) s->info[at]; break; case 5: s->allowed_formats = (gchar*) s->info[at]; break; case 6: s->useragent = (gchar*) s->info[at]; break; case 7: s->lang = (gchar*) s->info[at]; break; case 8: s->musictree_path = (gchar * ) s->info[at]; break; default: glyr_message (2,s,"Warning: wrong for glyr_info_at!\n"); } } else { result = GLYRE_BAD_VALUE; } return result; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) const char * glyr_get_type_to_string (GLYR_GET_TYPE type) { const gchar * result = "unknown"; GList * fetcher_list = r_getFList(); for (GList * elem = fetcher_list; elem != NULL; elem = elem->next) { MetaDataFetcher * fetch = elem->data; if (fetch->type == type) { result = fetch->name; break; } } return result; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_GET_TYPE glyr_string_to_get_type (const char * string) { if (string != NULL) { GList * fetcher_list = r_getFList(); for (GList * elem = fetcher_list; elem != NULL; elem = elem->next) { MetaDataFetcher * fetch = elem->data; if (g_ascii_strcasecmp (fetch->name,string) == 0) { return fetch->type; break; } } } return GLYR_GET_UNKNOWN; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) const char * glyr_data_type_to_string (GLYR_DATA_TYPE type) { if (type > 0 && type < (sizeof (type_strings) /sizeof (const char*) ) ) { return type_strings[type]; } else { return type_strings[GLYR_TYPE_UNKNOWN]; } } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_DATA_TYPE glyr_string_to_data_type (const char * string) { if (string != NULL) { gsize table_size = (sizeof (type_strings) /sizeof (const char*) ); for (gsize i = 0; i < table_size; i++) { if (g_ascii_strcasecmp (string,type_strings[i]) == 0) return i; } } return GLYR_TYPE_UNKNOWN; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_cache_print (GlyrMemCache * cacheditem) { if (cacheditem != NULL) { glyr_message (-1,NULL,"FROM: <%s>\n",cacheditem->dsrc); glyr_message (-1,NULL,"PROV: %s\n",cacheditem->prov); glyr_message (-1,NULL,"SIZE: %d Bytes\n", (int) cacheditem->size); glyr_message (-1,NULL,"MSUM: "); /* Print md5sum */ for (int i = 0; i < 16; i++) { fprintf (stderr,"%02x", cacheditem->md5sum[i]); } // Each cache identified it's data by a constant glyr_message (-1,NULL,"\nTYPE: "); if (cacheditem->type == GLYR_TYPE_TRACK) { glyr_message (-1,NULL,"[%02d:%02d] ",cacheditem->duration/60, cacheditem->duration%60); } glyr_message (-1,NULL,"%s",glyr_data_type_to_string (cacheditem->type) ); glyr_message (-1,NULL,"\nSAFE: %s", (cacheditem->cached) ? "Yes" : "No"); glyr_message (-1,NULL,"\nRATE: %d",cacheditem->rating); glyr_message (-1,NULL,"\nSTMP: %f",cacheditem->timestamp); /* Print the actual data. * This might have funny results if using cover/photos */ if (cacheditem->is_image == FALSE) { glyr_message (-1,NULL,"\nDATA: \n%s",cacheditem->data); } else { glyr_message (-1,NULL,"\nFRMT: %s",cacheditem->img_format); glyr_message (-1,NULL,"\nDATA: "); } glyr_message (-1,NULL,"\n"); } } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GlyrFetcherInfo * glyr_info_get (void) { return get_plugin_info(); } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_info_free (GlyrFetcherInfo * info) { free_plugin_info (info); } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) char * glyr_md5sum_to_string (unsigned char * md5sum) { gchar * md5str = NULL; if (md5sum != NULL) { const gchar * hex = "0123456789abcdef"; md5str = g_malloc0 (33); for (int i = 0; i < 16; i++) { gint index = i * 2; md5str[index + 0] = hex[md5sum[i] / 16]; md5str[index + 1] = hex[md5sum[i] % 16]; } } return md5str; } ///////////////////////////////// #define CHAR_TO_NUM(c) (unsigned char)(g_ascii_isdigit(c) ? c - '0' : (c - 'a') + 10) __attribute__ ( (visibility ("default") ) ) void glyr_string_to_md5sum (const char * string, unsigned char * md5sum) { if (string != NULL && strlen (string) >= 32 && md5sum) { for (gint i = 0; i < 16; i++) { gint index = i * 2; md5sum[i] = (CHAR_TO_NUM (string[index]) << 4) + CHAR_TO_NUM (string[index+1]); } } } #undef CHAR_TO_NUM ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) GLYR_FIELD_REQUIREMENT glyr_get_requirements (GLYR_GET_TYPE type) { GLYR_FIELD_REQUIREMENT result = 0; GlyrFetcherInfo * info = glyr_info_get(); GlyrFetcherInfo * head = info; while (head != NULL) { if (type == head->type) { result = head->reqs; } head = head->next; } glyr_info_free (info); return result; } /* --------------------------------------------------------- * Setters for MemCache * There is not a setter for everything, just for things, * you might be interested in to set. * (No setter for is_image e.g.) * --------------------------------------------------------- */ #define SET_CACHE_STRING(Cache, Field, Value) \ if(Cache != NULL) { \ if(Field != NULL) { \ g_free(Field); \ } \ Field = g_strdup(Value); \ } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_cache_set_dsrc (GlyrMemCache * cache, const char * download_source) { SET_CACHE_STRING (cache,cache->dsrc,download_source); } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_cache_set_prov (GlyrMemCache * cache, const char * provider) { SET_CACHE_STRING (cache,cache->prov,provider); } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_cache_set_img_format (GlyrMemCache * cache, const char * img_format) { SET_CACHE_STRING (cache,cache->img_format,img_format); } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_cache_set_type (GlyrMemCache * cache, GLYR_DATA_TYPE type) { if (cache != NULL) cache->type = MAX (type,GLYR_TYPE_UNKNOWN); } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) void glyr_cache_set_rating (GlyrMemCache * cache, int rating) { if (cache != NULL) cache->rating = rating; } ///////////////////////////////// __attribute__ ( (visibility ("default") ) ) bool glyr_type_is_image (GLYR_GET_TYPE type) { return TYPE_IS_IMAGE (type); } ///////////////////////////////// glyr-1.0.10/lib/glyr.h000066400000000000000000001024001301162614000144310ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of musicrelated metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #ifndef GLYR_H #define GLYR_GLYR_H /** * SECTION:glyr * @short_description: Main interface to search metadata * @title: Glyr * @section_id: * @stability: Stable * @include: glyr/glyr.h * * All functions used to search metadata and use the essential of libglyr * is located here. */ /* All structs used by glyr are here */ #include "types.h" #ifdef __cplusplus extern "C" { #endif /** * glyr_init: * * Init the library, this has to be called before any other calls from this library are made. * * You should call glyr_cleanup() once for every call of glyr_init() * * * This function is not threadsafe. * * **/ void glyr_init (void); /** * glyr_cleanup: * * Cleanup all parts of the library, you can use atexit(): * * * * * glyr_init(); * atexit(glyr_destroy); * * * * * * This function is not threadsafe. * * **/ void glyr_cleanup (void); /** * glyr_get: * @settings: The setting struct controlling glyr. (See the glyr_opt_* methods) * @error: An optional pointer to an int, which gets filled with an error message, or GLYRE_OK on success, or %NULL * @length: length An optional pointer storing the length of the returned list, or %NULL * * @settings is pointer to a #GlyrQuery struct filled to your needs via the glyr_opt_* methods, * * Once an item is found the callback (set via glyr_opt_dlcallback()) is called anytime a item is ready * * * Returns:: a doubly linked list of #GlyrMemCache, which should be freed by passing any element of the to glyr_free_list() * */ GlyrMemCache * glyr_get (GlyrQuery * settings, GLYR_ERROR * error, int * length); /** * glyr_query_init: * @query: The GlyrQuery to initialize to defaultsettings. * * This functions may allocate dynamic memory. It should be freed with glyr_query_init() after use. * */ void glyr_query_init (GlyrQuery * query); /** * glyr_query_destroy: * @query: The GlyrQuery to destroy. * * Deletes all modifications and frees dynamic memory. It can be reused, as fresh from glyr_query_init() * */ void glyr_query_destroy (GlyrQuery * query); /** * glyr_signal_exit: * @query: The currently running query you want to stop. * * Try to stop libglyr as soon as possible. * This is supposed to be called on another thread. * Calling this function twice on the same query will do nothing. * * * This function is threadsafe - but use with care anyway, * since it causes libglyr to do really a hard stop. * The returned data is NOT guaranteed to yield best results. * * */ void glyr_signal_exit (GlyrQuery * query); /** * glyr_free_list: * @head: The head of the doubly linked list that should be freed. * * Deletes all dynamic memory by calling glyr_cache_free() on each cache. * */ void glyr_free_list (GlyrMemCache * head); /** * glyr_cache_new: * * Initializes a new memcache. * * Normally you never need to do this. * * Don't forget to free the cache with glyr_cache_free() * * Returns:: A newly allocated and initialized memcache with no data. */ GlyrMemCache * glyr_cache_new (void); /** * glyr_cache_free: * @cache: Frees the (valid allocated) cache pointed to by @cache */ void glyr_cache_free (GlyrMemCache * cache); /** * glyr_cache_copy: * @cache: The cache to copy * * Allocate a new cache and * copy all contents (= deep copy) from the original @cache, * The pointers next and prev are set to NULL. * * Returns: A newly allocated cache. */ GlyrMemCache * glyr_cache_copy (GlyrMemCache * cache); /** * glyr_cache_set_dsrc: * @cache: The cache to change * @download_source: The string to be changed too * * Copies download_source to the dsrc field, clearing all previously allocated content safely. */ void glyr_cache_set_dsrc (GlyrMemCache * cache, const char * download_source); /** * glyr_cache_set_prov: * @cache: The cache to change * @provider: The string to be changed too * * Copies provider to the prov field, clearing all previously allocated content safely. */ void glyr_cache_set_prov (GlyrMemCache * cache, const char * provider); /** * glyr_cache_set_img_format: * @cache: The cache to change * @img_format: The string to be changed too * * Copies img_format to the img_format field, clearing all previously allocated content safely. */ void glyr_cache_set_img_format (GlyrMemCache * cache, const char * img_format); /** * glyr_cache_set_type: * @cache: The cache to change * @type: The new type */ void glyr_cache_set_type (GlyrMemCache * cache, GLYR_DATA_TYPE type); /** * glyr_cache_set_rating: * @cache: The cache to change * @rating: The new rating */ void glyr_cache_set_rating (GlyrMemCache * cache, int rating); /** * glyr_cache_set_data: * @cache: The cache where to set the data. * @data: The data * @len: Length of data * * Safely sets the data of the cache. It frees the old data first, updates * the checksum and adjusts the size fields accordingly to len. * If len is a negative number strlen() is used to determine the size. * * Attention: @data is set directly! It get's freed once you free the cache. Be sure it's safe to be free'd. * */ void glyr_cache_set_data (GlyrMemCache * cache, const char * data, int len); /** * glyr_cache_write: * @cache: The data to write. * @path: The path to write data at. * * Write @cache to the path specified by @path. * * There are three special files: * * * * "stdout" -> Outputs file to stdout * * * * * "stderr" -> Outputs file to stderr * * * * * "null" -> Outputs item nowhere * * * * * Returns: the number of written bytes. */ int glyr_cache_write (GlyrMemCache * cache, const char * path); /** * glyr_cache_update_md5sum: * @cache: a valid memcahe * * Updates the md5sum field of @cache. * */ void glyr_cache_update_md5sum (GlyrMemCache * cache); /** * glyr_cache_print: * @cache: The GlyrMemCache to be printed. * * A debug method to print all fields of @cache. * */ void glyr_cache_print (GlyrMemCache * cache); /******************************************************** * GlyOpt methods ahead - use them to control glyr_get() * ********************************************************/ /** * glyr_opt_dlcallback: * @settings: The GlyrQuery settings struct to store this option in. * @dl_cb: The callback to register, must have a prototype like this. * @userp: A pointer to a custom variable you can access inside the callback via s->callback.user_pointer * * The callback should have the following form: * * * GLYR_ERROR my_callback(GlyrMemCache * dl, struct GlyrQuery * s); * * * * Note that you can return certaing members of %GLYR_ERROR in the callback: * %GLYRE_SKIP: To not add this item to the results. * %GLYRE_OK: To add this item to the results and continue happily. * %GLYRE_STOP_POST: To stop right now and return the results. The current element will be added. * %GLYRE_STOP_PRE: To stop right now and return the results. The current element will NOT be added. * * Returns: an error ID */ GLYR_ERROR glyr_opt_dlcallback (GlyrQuery * settings, DL_callback dl_cb, void * userp); /** * glyr_opt_type: * @s: The GlyrQuery settings struct to store this option in. * @type: The type of metadata you want to get. * * Example: %GLYR_GET_COVERART * * Returns: an error ID */ GLYR_ERROR glyr_opt_type (GlyrQuery * s, GLYR_GET_TYPE type); /** * glyr_opt_artist: * @s: The GlyrQuery settings struct to store this option in. * @artist: The artist you want to search for, %NULL and "" is not valid. * * This is needed for all types of metadata. * Libglyr keeps a copy of this string internally. * * * * libglyr applies some basic normalization, like " artistX feat. artistY" -> "artistX" * * * * Returns: an error ID */ GLYR_ERROR glyr_opt_artist (GlyrQuery * s, const char * artist); /** * glyr_opt_album: * @s: The GlyrQuery settings struct to store this option in. * @album: The album you want to search for, %NULL and "" is not valid. * * This field is required for the following types: * * * * %GLYR_GET_COVERART * * * * * %GLYR_GET_ALBUM_REVIEW * * * * * %GLYR_GET_TRACKLIST * * * * * Optional for the following types: * * * * %GLYR_GET_RELATIONS * * * * * %GLYR_GET_TAGS * * * * * Libglyr keeps a copy of this string internally. * * * * libglyr applies some basic normalization, like " CoOl_album CD01 (20.7)" -> "cool_album" * * * * Returns: an error ID */ GLYR_ERROR glyr_opt_album (GlyrQuery * s, const char * album); /** * glyr_opt_title: * @s: The GlyrQuery settings struct to store this option in. * @title: The album you want to search for, %NULL and "" is not valid. * * This field is required for the following types: * * * * %GLYR_GET_LYRICS * * * * * %GLYR_GET_SIMILAR_SONGS * * * * * Optional for the following types: * * * * %GLYR_GET_RELATIONS * * * * * %GLYR_GET_TAGS * * * * * Libglyr keeps a copy of this string internally. * * * * libglyr applies some basic normalization, like "Songtitle (blahblah remix)" -> "Songtitle" * * * * Returns: an error ID */ GLYR_ERROR glyr_opt_title (GlyrQuery * s, const char * title); /** * glyr_opt_img_minsize: * @s: The GlyrQuery settings struct to store this option in. * @size: The minimum size in pixels an image may have, assuming it to be quadratic * * * * This is only taken as a hint, returned images are not necessarily higher than this size, but should be around it. * * * * Returns: an error ID */ GLYR_ERROR glyr_opt_img_minsize (GlyrQuery * s, int size); /** * glyr_opt_img_maxsize: * @s: The GlyrQuery settings struct to store this option in. * @size: The maxmimum size in pixels an image may have, assuming it to be quadratic * * * * This is only taken as a hint, returned images are not necessarily below this size, but should be around it. * * * * Returns: an error ID */ GLYR_ERROR glyr_opt_img_maxsize (GlyrQuery * s, int size); /** * glyr_opt_parallel: * @s: The GlyrQuery settings struct to store this option in. * @parallel_jobs: The number of providers that are queried in parallel. * * A value of 0 lets libglyr chooses this value itself. This is the default. * * Returns: an error ID */ GLYR_ERROR glyr_opt_parallel (GlyrQuery * s, unsigned long parallel_jobs); /** * glyr_opt_timeout: * @s: The GlyrQuery settings struct to store this option in. * @timeout: Maximum number of seconds to wait before canceling a download. * * Default is 20 seconds * * Returns: an error ID */ GLYR_ERROR glyr_opt_timeout (GlyrQuery * s, unsigned long timeout); /** * glyr_opt_redirects: * @s: The GlyrQuery settings struct to store this option in. * @redirects: Maximum number of redirects before canceling a download. * * A value of 0 is allowed but may break some plugins. * * Returns: an error ID */ GLYR_ERROR glyr_opt_redirects (GlyrQuery * s, unsigned long redirects); /** * glyr_opt_useragent: * @s: The GlyrQuery settings struct to store this option in. * @useragent: A string that is used as useragent in HTTP requests. * * Some providers require an valid useragent, an empty string might break these therefore. * * Returns: an error ID */ GLYR_ERROR glyr_opt_useragent (GlyrQuery * s, const char * useragent); /** * glyr_opt_lang: * @s: The GlyrQuery settings struct to store this option in. * @langcode: An ISO 639-1 language code. * * Some providers offer localized content, or content only being available in certain countries. * Examples are: last.fm, amazon and google. * The language is given in ISO 639-1 codes like 'de' or 'en'. * Alternatively you can set it to 'auto', which will cause libglyr to guess your language by your locale. * "auto" is the default behavior. * * Returns: an error ID */ GLYR_ERROR glyr_opt_lang (GlyrQuery * s, const char * langcode); /** * glyr_opt_lang_aware_only: * @s: The GlyrQuery settings struct to store this option in. * @lang_aware_only: Boolean, set to true if you want language specific providers only. * * Note: Not for all types of metadata there may be localized content, and only fetchers, that provide text items are affected by this setting. * The special provider 'local' is an exception here, it is queried, but delievers only language specific content too. * * Returns: an error ID */ GLYR_ERROR glyr_opt_lang_aware_only (GlyrQuery * s, bool lang_aware_only); /** * glyr_opt_number: * @s: The GlyrQuery settings struct to store this option in. * @num: Maximum number of items to get or 0 * * The maximum number of items to get in a glyr_get(), resulting number of items may be below @num but not higher. * A value of 0 causes libglyr to search till infinity. * Default is 1. * * Returns: an error ID */ GLYR_ERROR glyr_opt_number (GlyrQuery * s, unsigned int num); /** * glyr_opt_verbosity: * @s: The GlyrQuery settings struct to store this option in. * @level: Define how verbose the library is. * * The verbosity level that is used by libglyr: * * * * 0: No output, but fatal errors. * * * * * 1: Basic warnings. * * * * * 2: Normal informal output * * * * * 3: Basic debug output * * * * * 4: Full debug output * * * * * Returns: an error ID */ GLYR_ERROR glyr_opt_verbosity (GlyrQuery * s, unsigned int level); /** * glyr_opt_from: * @s: The GlyrQuery settings struct to store this option in. * @from: A comma separated list of provider names. * * * Tell libglyr where you want your metadata want from. * You can get a full list of providers for each getter by running @glyrc @-L * The string you can pass here looks like this example for _cover_: * * * "lastfm;google" * * * * This would query to everybody's surprise"lastfm" and "google" * Alternatively you may use the string "all" in it: * * * "all;-lastfm;" * * * * All providers except "lastfm" (therefore the '-') are used, a '+' is also allowed, which does plain nothing. * By default all built-in providers are used. * You can access the providernames by calling glyr_info_get() * * Returns: an error ID */ GLYR_ERROR glyr_opt_from (GlyrQuery * s, const char * from); /** * glyr_opt_plugmax: * @s: The GlyrQuery settings struct to store this option in. * @plugmax: Maximum number of items a single provider may retrieve. * * Restricts providers to retrieve at max. @plugmax items, you might use this to get results * over several providers when glyr_opt_number() is set to something higher than 1. * * May be removed in future releases. * * Returns: an error ID */ GLYR_ERROR glyr_opt_plugmax (GlyrQuery * s, int plugmax); /** * glyr_opt_allowed_formats: * @s: The GlyrQuery settings struct to store this option in. * @formats: A commaseparated list of allowed formats. * * Restricts providers to retrieve at max. @plugmax items, you might use this to get results * over several providers when glyr_opt_number() is set to something higher than 1. * * For the getters %GLYR_GET_COVERART and %GLYR_GET_ARTIST_PHOTOS only. * The allowed formats for images, in a comma separated list. * Examples: * * * * "png;jpeg" * * * * * "png;jpeg;tiff;jpg;" (default) * * * * * * * 'jpeg' *and* 'jpg' because some websites return strange mimetypes (should be 'jpeg' only) * * * * Returns: an error ID */ GLYR_ERROR glyr_opt_allowed_formats (GlyrQuery * s, const char * formats); /** * glyr_opt_download: * @s: The GlyrQuery settings struct to store this option in. * @download: Wether to downlaod images or just to return the found URL. * * Imageproviders only return URLs, by default libglyr downloads these and * gives you the cache. By settings glyr_opt_download() to #FALSE you tell * libglyr that you want only the URLs (in a searchengine like fashion) * * An check for valid images is done however. * * Returns: an error ID */ GLYR_ERROR glyr_opt_download (GlyrQuery * s, bool download); /** * glyr_opt_fuzzyness: * @s: The GlyrQuery settings struct to store this option in. * @fuzz: Maximal Levenshtein-distance tolerance may have, see below. * * libglyr features fuzzy matching to enhance search results. * Look at the string "Equilibrium" and the accidentally mistyped version "Aquillibriu": * Those strings will be compares using the "Levenshtein distance" (http://en.wikipedia.org/wiki/Levenshtein_distance) which basically counts * the number of insert, substitute and delete operations to transform Equilibrium"" into "Aquillibriu". * The distance in this case is 3 since three edit-operations are needed (one insert, substitute and deletion) * * The fuzziness parameter is the maximum distance two strings may have to match. * A high distance (like about 10) matches even badly mistyped strings, but also introduces bad results. * Low settings however will omit some good results. * * The default values is currently 4. * To be more secure some correction is applied: * * Examples: * * * * artist:Adele - album:19 * * * * * artist:Adele - album:21 * * * * * lv-distance = 2 which is <= 4 * * * * * But since the lv-distance is the same as the length "21" it won't match. * * * * * The easiest way to prevent this though, is to properly tag your music. (http://musicbrainz.org/doc/MusicBrainz_Picard). * * Returns: an error ID */ GLYR_ERROR glyr_opt_fuzzyness (GlyrQuery * s, int fuzz); /** * glyr_opt_qsratio: * @s: The GlyrQuery settings struct to store this option in. * @ratio: A float, in the range [0.0..1.0] specifying the ratio between quality and speed. * * 0.00 means highest speed, querying fast providers first. * 1.00 Takes possibly longer, but should deliver best results. * 0.85 is the current default value. * * All other values, smaller 0.0, greater 1.0 are clamped to [0.0..1.0] * * Returns: an error ID */ GLYR_ERROR glyr_opt_qsratio (GlyrQuery * s, float ratio); /** * glyr_opt_proxy: * @s: The GlyrQuery settings struct to store this option in. * @proxystring: The proxy to use, see below for the notation. * * The proxy to use, if any. * It is passed in the form: [protocol://][user:pass@]yourproxy.domain[:port] * Example: * * * * Proxy.fh-hof.de:3128 * * * * * http://hman:rootroot @ godserve.com:666 * * * * * The environment variables http_proxy, ftp_proxy, all_proxy are respected, but are overwritten by this. * * Returns: an error ID */ GLYR_ERROR glyr_opt_proxy (GlyrQuery * s, const char * proxystring); /** * glyr_opt_force_utf8: * @s: The GlyrQuery settings struct to store this option in. * @force_utf8: To force, or not to force. * * For textitems only. * Some providers (like metrolyrics) might return text with strange encodings, * that can not be converted to regular UTF8, but might return a subset of UTF8. * This options forces libglyr to prohibit those. * * Returns: an error ID */ GLYR_ERROR glyr_opt_force_utf8 (GlyrQuery * s, bool force_utf8); /** * glyr_opt_lookup_db: * @s: The GlyrQuery settings struct to store this option in. * @db: a GlyrDatabase object. * * Bind the previosly created @db to the query @s. * By doing this you add a new 'local' provider, * that is queried before everything else and may speed up * things heavily. * * You can either query it exclusively or disable it completely: * * Enable exclusiv: * * * glyr_opt_from(s,"local"); * * Disable: * * * * glyr_opt_from(s,"all;-local"); * * * * * Returns: an error ID */ GLYR_ERROR glyr_opt_lookup_db (GlyrQuery * s, GlyrDatabase * db); /** * glyr_opt_db_autowrite: * @s: The GlyrQuery settings struct to store this option in. * @write_to_db: true, to write this to the database automatically * * If a database is specified via glyr_opt_lookup_db you can choose * to automatically save newly found items to the database. * They will be looked up from there if you search for it again. * * Returns: an error ID */ GLYR_ERROR glyr_opt_db_autowrite (GlyrQuery * s, bool write_to_db); /** * glyr_opt_db_autoread: * @s: The GlyrQuery settings struct to store this option in. * @read_from_db: Boolean, true for DB lookup while searching * * If set to true libglyr will lookup the database previously given by glyr_opt_lookup_db() * during searching in the web. If there's item that already seems to be in the DB it gets not * mixed into the results. * * * This does not influence the usage of the DB as local provider! * Use glyr_opt_from() with "all;-local" to disable it. * * * * Returns: an error ID **/ GLYR_ERROR glyr_opt_db_autoread (GlyrQuery * s, bool read_from_db); /** * glyr_opt_musictree_path: * @s: The GlyrQuery settings struct to store this option in. * @musictree_path: The concrete path (relative or absolute) where a mediafile reisdes (see below) * * Set the path to a specific mediafile and glyr will try to fetch covers from directories around this, * since many people place things like 'folder.jpg' there. Instead of the actual file you can also pass the * containing directory (see the 'dirname' utility) - the path can be either absolute or relative. * * From there on it works by cascading upwards - i.e. checking all files in the dir (not recursing), go up, repeat. * This will be repeated $(recurse_depth) times or till it cannot go upwards. * How the file is checked depends on the metadata type to search, see below. * * For reference the actual C code is given (${artist} gets expanded): * * Used regexes and recurse_depth * case GLYR_GET_COVERART: search_regex = "^(folder|front|cover|.*${album}.*)\\.(jpg|png|jpeg|gif)"; recurse_depth = 2; break; case GLYR_GET_ARTIST_PHOTOS: search_regex = "^(${artist}|artist)\\.(jpg|png|jpeg|gif)$"; recurse_depth = 3; break; case GLYR_GET_ALBUM_REVIEW: search_regex = "^(${album})\\.(info|txt)$"; recurse_depth = 2; break; case GLYR_GET_ARTIST_BIO: search_regex = "^BIOGRAPHY(\\.txt)?$"; recurse_depth = 2; break; default: search_regex = NULL; recurse_depth = 0; break; * * * * * Returns: an error ID */ GLYR_ERROR glyr_opt_musictree_path (GlyrQuery * s, const char * musictree_path); /** * glyr_opt_normalize: * @s: The GlyrQuery settings struct to store this option in. * @norm: Any members of GLYR_NORMALIZATION, which may be binary or'd * * Defines how much artist/album/title is normalized. * * * * * GLYR_NORMALIZE_NONE: Do no normalization, except strdown and utf-8-normalization. * * * * * GLYR_NORMALIZE_MODERATE: Remove stuff like feat., featuring. Track 01 etc. * * * * * GLYR_NORMALIZE_AGGRESSIVE: Also remove everything between (), [] and <>. Slightly destructive! * * * * * * * Even for None, utf-8 normalization and strdown() is done. * The default is GLYR_NORMALIZE_AGGRESSIVE | GLYR_NORMALIZE_ALL * * * * Returns: an error ID */ GLYR_ERROR glyr_opt_normalize (GlyrQuery * s, GLYR_NORMALIZATION norm); /** * glyr_download: * @url: A valid url, for example returned by libglyr * @s: A settings struct managing timeout, useragent and redirects. * * Downloads the data pointed to by @url and caches in it a GlyrMemCache, which is returned to you. * Use glyr_cache_free() to free it after use. * * Returns: A GlyrMemCache containing the data, or %NULL on failure, use verbose output to find out why. */ GlyrMemCache * glyr_download (const char * url, GlyrQuery * s); /** * glyr_strerror: * @ID: a member of the %GLYR_ERROR enum. * * Gets a descriptive message from an error ID. * * Returns: a descriptive nullterminated string, do NOT pass to free */ const char * glyr_strerror (GLYR_ERROR ID); /** * glyr_version: * * Returns: the current version string. Example below. * * Version 0.4 (Larcenous Locust [dev]) of [May 20 2011] compiled at [19:12:37] * * Retunrs a nullterminated string, do NOT pass it to free! */ const char * glyr_version (void); /** * glyr_info_get: * * get information about existing Fetcher and Source * A Doubly linked list of Fetcher is returned, each having a field 'head', * being a pointer to a doubly linked list of GlyrSourceInfos * * It is best understood by an example: * * Using GlyrFetcherInfo: * * static void visualize_from_options(void) * { * GlyrFetcherInfo * info = glyr_info_get(); * if(info != NULL) * { * for(GlyrFetcherInfo * elem0 = info; elem0; elem0 = elem0->next) * { * printf("%s\n",elem0->name); * for(GlyrSourceInfo * elem1 = elem0->head; elem1; elem1 = elem1->next) * { * printf(" [%c] %s\n",elem1->key,elem1->name); * } * printf("\n"); * } * } * glyr_info_free(info); * } * * * * Returns: A newly allocated GlyrFetcherInfo structure, you can iterate over. */ GlyrFetcherInfo * glyr_info_get (void); /** * glyr_info_free: * @info: The return value of glyr_info_get() * * Free the return value of glyr_info_get() pointed to by @info */ void glyr_info_free (GlyrFetcherInfo * info); /** * glyr_data_type_to_string: * @type: a member of the %GLYR_DATA_TYPE enum, %GLYR_TYPE_LYRICS for example * * Converts a type to a string. * * Returns: a statically allocated string, do not free */ const char * glyr_data_type_to_string (GLYR_DATA_TYPE type); /** * glyr_get_type_to_string: * @type: a member of the %GLYR_GET_TYPE enum, %GLYR_GET_COVERART for example * * Converts a get type to a string (GLYR_GET_COVERART => "cover") * You must not modify the string or daemons will come to you at night! * * Returns: a statically allocated string, do not free nor modify */ const char * glyr_get_type_to_string (GLYR_GET_TYPE type); /** * glyr_md5sum_to_string: * @md5sum: a md5sum (from a cache) * * Convert a md5sum (raw data) to a human readable representation. * String consists only of [0-9] and [a-f]. * * Returns: a newly allocated string, 32 chars long. */ char * glyr_md5sum_to_string (unsigned char * md5sum); /** * glyr_string_to_md5sum: * @string: The string containing a human readable checksum (lowercase) * @md5sum: A at lease 16 byte sized buffer of unsigned chars * * Convert a string to a raw-data md5sum. * Must be a 32 char long string only containing [0-9] and [a-f] * The new checksum is written to m5sum, which must be a buffer with * a size >= 16 bytes. */ void glyr_string_to_md5sum (const char * string, unsigned char * md5sum); /** * glyr_get_requirements: * @type: The type to get the requirements from * * Different getters need different fields set. You can use this * to check if the artist, album and title field of a specific getter * is required or optional. * * * * GLYR_FIELD_REQUIREMENT reqs = glyr_get_requirements(GLYR_GET_COVERART); * if(reqs & GLYR_REQUIRES_ALBUM) * { * // do something when artist is required * } * else * if(reqs & GLYR_OPTIONAL_TITLE) * { * // Title is optional * } * else * { * // None of both * } * * * * Returns: A bitmask out of members of GLYR_FIELD_REQUIREMENT */ GLYR_FIELD_REQUIREMENT glyr_get_requirements (GLYR_GET_TYPE type); bool glyr_type_is_image (GLYR_GET_TYPE type); GLYR_GET_TYPE glyr_string_to_get_type (const char * string); GLYR_DATA_TYPE glyr_string_to_data_type (const char * string); #ifdef __cplusplus } #endif #endif glyr-1.0.10/lib/intern/000077500000000000000000000000001301162614000146055ustar00rootroot00000000000000glyr-1.0.10/lib/intern/ainfo.c000066400000000000000000000037161301162614000160540ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../core.h" #include "../stringlib.h" #include "generic.h" ///////////////////////////////// static GList * factory (GlyrQuery * s, GList * list, gboolean * stop_me, GList ** result_list) { /* Fix up messy text, escape chars etc. */ for (GList * elem = list; elem; elem = elem->next) { GlyrMemCache * item = elem->data; if (item != NULL) { gchar * temp = beautify_string (item->data); g_free (item->data); item->data = temp; item->size = (item->data) ? strlen (item->data) : 0; } } return generic_txt_finalizer (s,list,stop_me,GLYR_TYPE_ARTIST_BIO,result_list); } ///////////////////////////////// /* PlugStruct */ MetaDataFetcher glyrFetcher_artistbio = { .name = "artistbio", .type = GLYR_GET_ARTIST_BIO, .default_data_type = GLYR_TYPE_ARTIST_BIO, .reqs = GLYR_REQUIRES_ARTIST, .full_data = TRUE, .init = NULL, .destroy = NULL, .finalize = factory, .default_parallel = 2 }; glyr-1.0.10/lib/intern/ainfo/000077500000000000000000000000001301162614000157015ustar00rootroot00000000000000glyr-1.0.10/lib/intern/ainfo/bbcmusic.c000066400000000000000000000056251301162614000176440ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../stringlib.h" #include "../../core.h" #include "../common/mbid_lookup.h" #define API_ROOT "http://www.bbc.co.uk/music/artists/%s.xml" ///////////////////////////////// #define CONTENT_BEGIN "" #define CONTENT_ENDIN "" static GlyrMemCache * parse_bbc_xml (GlyrMemCache * input) { GlyrMemCache * result = NULL; char * content = get_search_value (input->data, CONTENT_BEGIN, CONTENT_ENDIN); if (content != NULL) { result = DL_init(); result->data = content; result->dsrc = g_strdup (input->dsrc); result->size = strlen (content); } return result; } ///////////////////////////////// static const char * ainfo_bbcmusic_url (GlyrQuery * qry) { return "http://musicbrainz.org/ws/2/artist?query=artist:${artist}"; } ///////////////////////////////// static GList * ainfo_bbcmusic_parse (cb_object * capo) { GList * result_list = NULL; char * mbid = mbid_parse_data (capo->cache, "artist", "name", capo->s->artist, capo->s); if (mbid != NULL) { char * full_url = g_strdup_printf (API_ROOT, mbid); if (full_url != NULL) { GlyrMemCache * bbc_xml = download_single (full_url, capo->s, NULL); if (bbc_xml != NULL) { GlyrMemCache * item = parse_bbc_xml (bbc_xml); if (item != NULL) { result_list = g_list_prepend (result_list, item); } DL_free(bbc_xml); } g_free(full_url); } g_free(mbid); } return result_list; } ///////////////////////////////// MetaDataSource ainfo_bbcmusic_src = { .name = "bbcmusic", .key = 'b', .free_url = false, .type = GLYR_GET_ARTIST_BIO, .parser = ainfo_bbcmusic_parse, .get_url = ainfo_bbcmusic_url, .quality = 95, .speed = 85, .endmarker = NULL, .lang_aware = false }; glyr-1.0.10/lib/intern/ainfo/echonest.c000066400000000000000000000101201301162614000176470ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #include "../../apikeys.h" /* JSON Parsing */ #include "../../jsmn/jsmn.h" static const gchar * ainfo_echonest_url (GlyrQuery * s) { return "http://developer.echonest.com/api/v4/artist/biographies?api_key="API_KEY_ECHONEST"&results=${number}&format=json&name=${artist}"; } ///////////////////////////////// static char * echonest_strip_escapes(char * src, char * dest, int len) { int offset = 0; for (int i = 0; i < len; ++i) { if (src[i] == '\\') { ++i; if (src[i] == 'n') { dest[offset++] = '\n'; continue; } } dest[offset++] = src[i]; } return dest; } ///////////////////////////////// static bool echonest_check_if_text_is_valid(char * text, int len) { bool rc = false; if (len >= 125) { return true; } return rc; } ///////////////////////////////// static GList * ainfo_echonest_parse (cb_object * capo) { char * json = capo->cache->data; bool is_text = false; const int num_tokens = 512; GList * results = NULL; /* jasmin stuff */ jsmn_parser parser; jsmntok_t tokens[num_tokens]; jsmnerr_t error; /* make sure it terminates */ memset(tokens, 0, num_tokens * sizeof(jsmntok_t)); /* Init the parser */ jsmn_init(&parser); /* Parse the json text */ error = jsmn_parse(&parser, capo->cache->data, tokens, num_tokens); if (error == JSMN_SUCCESS) { for (int i = 0; i < num_tokens; ++i) { jsmntok_t * tok = &tokens[i]; size_t len = tok->end - tok->start; char * text_off = json + tok->start; /* End of tokens? */ if(tok->start == 0 && tok->end == 0) { break; } /* Check for the "text" field. */ if (tok->type == JSMN_STRING) { if (len == 4 && g_ascii_strncasecmp(text_off, "text", len) == 0) { is_text = true; continue; } } /* Interesting part! */ if (is_text == true && tok->type == JSMN_STRING) { if (echonest_check_if_text_is_valid(text_off, len)) { GlyrMemCache * cache = DL_init(); if (cache != NULL) { cache->data = g_strndup(text_off, len); cache->data = echonest_strip_escapes(cache->data, cache->data, len); cache->size = len; results = g_list_prepend(results, cache); } } is_text = false; continue; } } } else { /* No intelligent error handling yet. */ } return results; } ///////////////////////////////// MetaDataSource ainfo_echonest_src = { .name = "echonest", .key = 'e', .free_url = false, .type = GLYR_GET_ARTIST_BIO, .parser = ainfo_echonest_parse, .get_url = ainfo_echonest_url, .quality = 95, .speed = 85, .endmarker = NULL, .lang_aware = false }; glyr-1.0.10/lib/intern/ainfo/lastfm.c000066400000000000000000000066171301162614000173450ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define CONTENT_BEGIN "" #define CONTENT_ENDIN "User-contributed text" #define OTHER_ENDIN "" #define CDATA_BEGIN "artist," ","+"); if (right_artist != NULL) { gchar * lang = "en"; /* Check if this is an allowed language */ if (strstr (GLYR_DEFAULT_SUPPORTED_LANGS,s->lang) != NULL) { lang = (gchar*) s->lang; } /* Do we need to map a language to 'en'? */ if (strstr (locale_map_to_en,s->lang) != NULL) { lang = "en"; } url = g_strdup_printf ("http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&autocorrect=1&artist=%s&lang=%s&api_key="API_KEY_LASTFM,right_artist,lang); g_free (right_artist); } return url; } ///////////////////////////////// static GList * ainfo_lastfm_parse (cb_object * capo) { GList * result_list = NULL; gchar * content_begin = strstr (capo->cache->data,CONTENT_BEGIN); if (content_begin != NULL) { gchar * content_endin = strstr (capo->cache->data,CONTENT_ENDIN); if (content_endin == NULL) { content_endin = strstr (capo->cache->data,OTHER_ENDIN); } if (content_endin != NULL) { content_begin += (sizeof CONTENT_BEGIN) - 1; char * skip_cdata = strstr(content_begin, CDATA_BEGIN); if (skip_cdata != NULL) { content_begin = skip_cdata + (sizeof CDATA_BEGIN) - 1; } gchar * content = copy_value (content_begin,content_endin); if (content != NULL) { GlyrMemCache * result = DL_init(); result->data = content; result->size = strlen (result->data); result_list = g_list_prepend (result_list,result); } } } return result_list; } ///////////////////////////////// MetaDataSource ainfo_lastfm_src = { .name = "lastfm", .key = 'l', .free_url = true, .type = GLYR_GET_ARTIST_BIO, .parser = ainfo_lastfm_parse, .get_url = ainfo_lastfm_url, .quality = 85, .speed = 85, .endmarker = NULL, .lang_aware = true }; glyr-1.0.10/lib/intern/ainfo/lyricsreg.c000066400000000000000000000052521301162614000200540ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define INFO_BEGIN "

" #define OPTN_BEGIN "Biography:
" #define INFO_ENDIN "
" ///////////////////////////////// static const gchar * ainfo_lyricsreg_url (GlyrQuery * s) { return "http://www.lyricsreg.com/biography/${artist}/"; } ///////////////////////////////// static GList * ainfo_lyricsreg_parse (cb_object * capo) { GList * result_list = NULL; gchar * point_to_start = strstr (capo->cache->data,INFO_BEGIN); if (point_to_start != NULL) { gchar * opt_begin = strstr (point_to_start,OPTN_BEGIN); gsize skip_len = (sizeof INFO_BEGIN) - 1; if (opt_begin != NULL) { point_to_start = opt_begin; skip_len = (sizeof OPTN_BEGIN) - 1; } point_to_start += skip_len; gchar * end = strstr (point_to_start, INFO_ENDIN); if (end != NULL) { gsize info_len = end - point_to_start; if (info_len > 200) { gchar * info = copy_value (point_to_start, end); if (info != NULL) { GlyrMemCache * result = DL_init(); result->data = info; result->size = info_len; result_list = g_list_prepend (result_list,result); } } } } return result_list; } ///////////////////////////////// MetaDataSource ainfo_lyricsreg_src = { .name = "lyricsreg", .key = 'r', .free_url = false, .type = GLYR_GET_ARTIST_BIO, .parser = ainfo_lyricsreg_parse, .get_url = ainfo_lyricsreg_url, .quality = 35, .speed = 50, .endmarker = NULL }; glyr-1.0.10/lib/intern/albumlist.c000066400000000000000000000030601301162614000167440ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../core.h" #include "../stringlib.h" #include "generic.h" //------------------------------------- static GList * factory (GlyrQuery * s, GList * list, gboolean * stop_me, GList ** result_list) { return generic_txt_finalizer (s,list,stop_me,GLYR_TYPE_ALBUMLIST,result_list); } //------------------------------------- /* PlugStruct */ MetaDataFetcher glyrFetcher_albumlist = { .name = "albumlist", .type = GLYR_GET_ALBUMLIST, .default_data_type = GLYR_TYPE_ALBUMLIST, .reqs = GLYR_REQUIRES_ARTIST, .full_data = TRUE, .init = NULL, .destroy = NULL, .finalize = factory }; glyr-1.0.10/lib/intern/albumlist/000077500000000000000000000000001301162614000166015ustar00rootroot00000000000000glyr-1.0.10/lib/intern/albumlist/musicbrainz.c000066400000000000000000000100771301162614000213000ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../stringlib.h" #include "../../core.h" static const gchar * albumlist_musicbrainz_url (GlyrQuery * sets) { return "http://musicbrainz.org/ws/1/release/?type=xml&artist=${artist}&limit=100"; } ///////////////////////////////////////////////////////////// //#define ALBUM_BEGIN "" #define ARTIST_ENDIN "" #define TITLE_BEGIN "" #define TITLE_ENDIN "" ///////////////////////////////////////////////////////////// static bool is_in_list (GList * list, const char * to_cmp) { bool rc = false; for (GList * elem = list; elem; elem = elem->next) { GlyrMemCache * item = elem->data; if (item != NULL) { if (g_ascii_strcasecmp (item->data,to_cmp) == 0) { rc = true; break; } } } return rc; } ///////////////////////////////////////////////////////////// static bool type_is_valid (const char * type) { bool result = FALSE; if (type == NULL) return result; static const char * valid_types[] = { "Single Official", "Album Official", NULL }; int check_idx = 0; while(valid_types[check_idx]) { if (g_strcmp0(valid_types[check_idx], type) == 0) { result = TRUE; break; } check_idx++; } return result; } ///////////////////////////////////////////////////////////// static GList * albumlist_musicbrainz_parse (cb_object * capo) { GList * result_list = NULL; gchar * node = capo->cache->data; while (continue_search (g_list_length (result_list),capo->s) && (node = strstr (node + 1, ALBUM_BEGIN) ) != NULL) { gchar * type = get_search_value (node, TYPE_BEGIN, TYPE_ENDIN); if (type_is_valid (type)) { gchar * artist = get_search_value (node,ARTIST_BEGIN, ARTIST_ENDIN); if (artist != NULL && levenshtein_strnormcmp (capo->s, capo->s->artist, artist) <= capo->s->fuzzyness) { gchar * name = get_search_value (node,TITLE_BEGIN,TITLE_ENDIN); if (name != NULL && is_in_list (result_list,name) == false) { GlyrMemCache * result = DL_init(); result->data = name; result->size = (result->data) ? strlen (result->data) : 0; result_list = g_list_prepend (result_list,result); } else { g_free (name); } } g_free (artist); } g_free (type); } return result_list; } ///////////////////////////////////////////////////////////// MetaDataSource albumlist_musicbrainz_src = { .name = "musicbrainz", .key = 'm', .free_url = false, .parser = albumlist_musicbrainz_parse, .get_url = albumlist_musicbrainz_url, .type = GLYR_GET_ALBUMLIST, .quality = 95, .speed = 95, .endmarker = NULL }; glyr-1.0.10/lib/intern/backdrops.c000066400000000000000000000030131301162614000167160ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../core.h" #include "generic.h" ///////////////////////////////// static GList * factory (GlyrQuery * s, GList * list, gboolean * stop_me, GList ** result_list) { return generic_img_finalizer (s,list,stop_me,GLYR_TYPE_BACKDROPS,result_list); } ///////////////////////////////// /* PlugStruct */ MetaDataFetcher glyrFetcher_backdrops = { .name = "backdrops", .type = GLYR_GET_BACKDROPS, .default_data_type = GLYR_TYPE_BACKDROPS, .reqs = GLYR_REQUIRES_ARTIST, .full_data = FALSE, .init = NULL, .destroy = NULL, .finalize = factory }; glyr-1.0.10/lib/intern/backdrops/000077500000000000000000000000001301162614000165555ustar00rootroot00000000000000glyr-1.0.10/lib/intern/backdrops/htbackdrops.c000066400000000000000000000077441301162614000212410ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" #include "../../apikeys.h" #include "../../stringlib.h" #define HTBACKDROPS_URL "http://htbackdrops.org/api/%s" \ "/searchXML?keywords=${artist}&default_operator=and&" \ "fields=title&inc=mb_name&limit=%d&dmax_w=%d&dmin_h=%d" static const gchar * backdrops_htbackdrops_url (GlyrQuery * q) { gchar * result = g_strdup_printf (HTBACKDROPS_URL, API_KEY_HTBACK,q->number * 20, (q->img_max_size < 0) ? (gint) 1e10 : q->img_max_size, (q->img_min_size < 0) ? 0 : q->img_min_size); return result; } ///////////////////////////////// static gboolean check_size (GlyrQuery * query, const gchar * size_string) { gboolean result = FALSE; if (size_string != NULL && query != NULL) { /* "1024x1024" -> ["1024","1024"] */ gchar ** strv = g_strsplit (size_string,"x",0); if (strv && strv[0] && strv[1]) { gint width = strtol (strv[0],NULL,10); gint height = strtol (strv[1],NULL,10); gint img_size = (width + height) / 2; result = size_is_okay (img_size, query->img_min_size, query->img_max_size); } g_strfreev (strv); } return result; } ///////////////////////////////// #define NODE "" static GList * backdrops_htbackdrops_parse (cb_object * capo) { GList * result_list = NULL; gchar * img_list_start = strstr (capo->cache->data,""); if (img_list_start != NULL) { gchar * node = img_list_start; while (continue_search (g_list_length (result_list),capo->s) && (node = strstr (node,NODE) ) ) { node += sizeof NODE; gchar * dimensions = get_search_value (node,"",""); if (check_size (capo->s,dimensions) == TRUE) { gchar * validate_artist = get_search_value (node,"",""); if (levenshtein_strnormcmp (capo->s,validate_artist,capo->s->artist) <= capo->s->fuzzyness) { gchar * id = get_search_value (node,"",""); if (id != NULL) { GlyrMemCache * result = DL_init(); result->data = g_strdup_printf ("http://htbackdrops.org/api/"API_KEY_HTBACK"/download/%s/fullsize",id); result->size = strlen (result->data); result_list = g_list_prepend (result_list,result); g_free (id); } } g_free (validate_artist); } g_free (dimensions); } } return result_list; } ///////////////////////////////// MetaDataSource backdrops_htbackdrops_src = { .name = "htbackdrops", .key = 'h', .parser = backdrops_htbackdrops_parse, .get_url = backdrops_htbackdrops_url, .type = GLYR_GET_BACKDROPS, .quality = 80, .speed = 80, .endmarker = NULL, .free_url = true }; glyr-1.0.10/lib/intern/cache/000077500000000000000000000000001301162614000156505ustar00rootroot00000000000000glyr-1.0.10/lib/intern/cache/db_provider.c000066400000000000000000000043011301162614000203110ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" #include "../../cache.h" ///////////////////////////////// static GList * local_provider_parse (cb_object * capo) { GList * converter_list = NULL; if (capo->s && capo->s->local_db != NULL) { gint counter = 0; GlyrMemCache * head = glyr_db_lookup (capo->s->local_db,capo->s); while (head != NULL) { if (counter < capo->s->number) { converter_list = g_list_prepend (converter_list,head); head->cached = TRUE; head = head->next; counter++; } else { GlyrMemCache * to_delete = head; head = head->next; DL_free (to_delete); } } } return converter_list; } ///////////////////////////////// static const gchar * local_provider_url (GlyrQuery * sets) { return OFFLINE_PROVIDER; } ///////////////////////////////// MetaDataSource local_provider_src = { .name = "local", .key = 'l', .parser = local_provider_parse, .get_url = local_provider_url, .type = GLYR_GET_ANY, .quality = 4200, .speed = 4200, .endmarker = NULL, .free_url = false }; glyr-1.0.10/lib/intern/common/000077500000000000000000000000001301162614000160755ustar00rootroot00000000000000glyr-1.0.10/lib/intern/common/amazon.c000066400000000000000000000063611301162614000175340ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../apikeys.h" #include "../../core.h" #include "../../stringlib.h" #include "amazon.h" ///////////////////////////////// #define ACCESS_KEY API_KEY_AMAZON #define rg_markup "__RESPONSE_GROUP__" const gchar * generic_amazon_url (GlyrQuery * sets, const gchar * response_group) { const char * lang_link = NULL; if (sets->img_min_size <= 500 || sets->img_min_size) { if (!strcmp (sets->lang,"us") ) lang_link = "http://free.apisigning.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId="ACCESS_KEY"&Operation=ItemSearch&SearchIndex=Music&ResponseGroup="rg_markup"&Keywords=${artist}+${album}\0"; else if (!strcmp (sets->lang,"ca") ) lang_link = "http://ca.free.apisigning.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId="ACCESS_KEY"&Operation=ItemSearch&SearchIndex=Music&ResponseGroup="rg_markup"&Keywords=${artist}+${album}\0"; else if (!strcmp (sets->lang,"uk") ) lang_link = "http://co.uk.free.apisigning.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId="ACCESS_KEY"&Operation=ItemSearch&SearchIndex=Music&ResponseGroup="rg_markup"&Keywords=${artist}+${album}\0"; else if (!strcmp (sets->lang,"fr") ) lang_link = "http://fr.free.apisigning.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId="ACCESS_KEY"&Operation=ItemSearch&SearchIndex=Music&ResponseGroup="rg_markup"&Keywords=${artist}+${album}\0"; else if (!strcmp (sets->lang,"de") ) lang_link = "http://de.free.apisigning.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId="ACCESS_KEY"&Operation=ItemSearch&SearchIndex=Music&ResponseGroup="rg_markup"&Keywords=${artist}+${album}\0"; else if (!strcmp (sets->lang,"jp") ) lang_link = "http://co.jp.free.apisigning.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId="ACCESS_KEY"&Operation=ItemSearch&SearchIndex=Music&ResponseGroup="rg_markup"&Keywords=${artist}+${album}\0"; else lang_link = "http://free.apisigning.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId="ACCESS_KEY"&Operation=ItemSearch&SearchIndex=Music&ResponseGroup="rg_markup"&Keywords=${artist}+${album}\0"; } if (lang_link != NULL) { return strreplace (lang_link,rg_markup,response_group); } return NULL; } ///////////////////////////////// glyr-1.0.10/lib/intern/common/amazon.h000066400000000000000000000021601301162614000175320ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of musicrelated metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #ifndef GLYR_COMMON_AMAZON_H #define GLYR_COMMON_AMAZON_H #include "../../core.h" const gchar * generic_amazon_url (GlyrQuery * sets, const gchar * response_group); #endif glyr-1.0.10/lib/intern/common/google.c000066400000000000000000000110511301162614000175130ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "google.h" #include "../../stringlib.h" #include "../../core.h" ///////////////////////////////// /* Methods used by more than one provider go here */ const gchar * generic_google_url (GlyrQuery * sets, const gchar * searchterm) { const gchar * lang = NULL; if (!g_ascii_strncasecmp (sets->lang,"us",2) ) lang = "com" ; else if (!g_ascii_strncasecmp (sets->lang,"ca",2) ) lang = "ca" ; else if (!g_ascii_strncasecmp (sets->lang,"uk",2) ) lang = "co.uk"; else if (!g_ascii_strncasecmp (sets->lang,"fr",2) ) lang = "fr" ; else if (!g_ascii_strncasecmp (sets->lang,"de",2) ) lang = "de" ; else if (!g_ascii_strncasecmp (sets->lang,"jp",2) ) lang = "co.jp"; else lang = "com"; const gchar * back = NULL; if (sets->img_min_size == -1) { back = ""; } else if (sets->img_min_size < 75) { back = "&tbs=isz:i"; } else if (sets->img_min_size < 300) { back = "&tbs=isz:m"; } else if (sets->img_min_size < 450) { back = "&tbs=isz:lt,islt:qsvga"; } else if (sets->img_min_size < 550) { back = "&tbs=isz:lt,islt:vg/a"; } else if (sets->img_min_size < 700) { back = "&tbs=isz:lt,islt:svga"; } else /* High enough. */ { back = "&tbs=isz:lt,islt:xga"; } return g_strdup_printf ("http://www.google.%s/images?q=%s&safe=off%s%s",lang,searchterm, (back!=NULL) ? "" : "&", back); } ///////////////////////////////// #define IMG_SRC_START "&usg=" #define WIDTH_START "&w=" #define HEIGHT_START "&h=" #define MAX_NUM_BUF 16 static gint google_get_size_value (gchar * ref, gchar * name) { gint number = 0; gchar * start = g_strstr_len (ref,256,name); if (start != NULL) { start += strlen (name); gchar * end = strchr (start,' '); if (end != NULL) { gchar numbuf[MAX_NUM_BUF] = {}; gsize span = MIN (end - start,MAX_NUM_BUF-1); strncpy (numbuf,start,span); number = strtol (numbuf,NULL,10); } } return number; } ///////////////////////////////// static gboolean google_check_image_size (GlyrQuery * s, gchar * ref) { gboolean result = FALSE; gchar * img_src_after = strstr (ref,IMG_SRC_START); if (img_src_after != NULL) { gint width = google_get_size_value (img_src_after,WIDTH_START); gint height = google_get_size_value (img_src_after,HEIGHT_START); gint ratio = (width+height) /2; result = size_is_okay (ratio,s->img_min_size,s->img_max_size); } return result; } ///////////////////////////////// #define FIRST_RESULT ". **************************************************************/ #include "../../core.h" #include "../../stringlib.h" ////////////////////////////////// char * mbid_parse_data (GlyrMemCache * data, const char * lookup_entity, const char * find_entity, const char * compre_entity, GlyrQuery * qry) { char * key = g_strdup_printf ("<%s ", lookup_entity); size_t keylen = strlen (key); char * node = data->data; char * result = NULL; char * find_ent_start = g_strdup_printf ("<%s>", find_entity); char * find_ent_end = g_strdup_printf ("", find_entity); while ( (node = strstr (node + keylen, key) ) ) { char * name = get_search_value (node, find_ent_start, find_ent_end); if (name && levenshtein_strnormcmp (qry, name, compre_entity) <= qry->fuzzyness) { result = get_search_value (node, "id=\"", "\""); g_free (name); break; } g_free (name); } g_free (find_ent_start); g_free (find_ent_end); g_free (key); return result; } ////////////////////////////////// #define LOOKUP_QUERY "http://musicbrainz.org/ws/2/%s?query=%s:%s" ////////////////////////////////// char * mbid_lookup (const char * query, GLYR_DATA_TYPE type, GlyrQuery * qry) { char * result_mbid = NULL; if (query == NULL) return result_mbid; const char * lookup_entity = ""; const char * compre_entity = qry->artist; const char * find_entity = "name"; switch (type) { case GLYR_TYPE_TAG_ARTIST: lookup_entity = "artist"; compre_entity = qry->artist; break; case GLYR_TYPE_TAG_ALBUM: lookup_entity = "release"; compre_entity = qry->album; find_entity = "title"; break; case GLYR_TYPE_TAG_TITLE: lookup_entity = "work"; compre_entity = qry->title; break; default: lookup_entity = "artist"; compre_entity = qry->artist; find_entity = "name"; break; } char * lookup_url = g_strdup_printf (LOOKUP_QUERY, lookup_entity, lookup_entity, query); GlyrMemCache * parseable_data = download_single (lookup_url, qry, NULL); if (parseable_data != NULL) { result_mbid = mbid_parse_data (parseable_data, lookup_entity, find_entity, compre_entity, qry); DL_free (parseable_data); } g_free (lookup_url); return result_mbid; } ////////////////////////////////// glyr-1.0.10/lib/intern/common/mbid_lookup.h000066400000000000000000000023141301162614000205520ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of musicrelated metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" char * mbid_lookup (const char * query, GLYR_DATA_TYPE type, GlyrQuery * qry); char * mbid_parse_data (GlyrMemCache * data, const char * lookup_entity, const char * find_entity, const char * compre_entity, GlyrQuery * qry); glyr-1.0.10/lib/intern/common/musicbrainz.c000066400000000000000000000131031301162614000205650ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "musicbrainz.h" #include "../../stringlib.h" #include "../../core.h" ///////////////////////////////// /* 'please' is important. gcc won't compile without. */ gint please_what_type (GlyrQuery * s) { int result = -1; if (s->artist && !s->album && !s->title) result = GLYR_TYPE_TAG_ARTIST; else if (!s->artist && !s->album && s->title) result = GLYR_TYPE_TAG_TITLE; else if (!s->artist && s->album && !s->title) result = GLYR_TYPE_TAG_ALBUM; else if (s->artist && s->album && s->title) result = GLYR_TYPE_TAG_TITLE; else if (s->artist && !s->album && s->title) result = GLYR_TYPE_TAG_TITLE; else if (s->artist && s->album && !s->title) result = GLYR_TYPE_TAG_ALBUM; else result = -1; return result; } ///////////////////////////////// const gchar * generic_musicbrainz_url (GlyrQuery * sets) { const gchar * wrap_a = sets->artist ? "${artist}" : ""; const gchar * wrap_b = sets->album ? "${album}" : ""; const gchar * wrap_t = sets->title ? "${title}" : ""; switch (please_what_type (sets) ) { case GLYR_TYPE_TAG_TITLE : return g_strdup_printf ("http://musicbrainz.org/ws/1/track/?type=xml&title=%s&artist=%s&release=%s",wrap_t,wrap_a,wrap_b); case GLYR_TYPE_TAG_ALBUM : return g_strdup_printf ("http://musicbrainz.org/ws/1/release/?type=xml&title=%s&artist=%s",wrap_b,wrap_a); case GLYR_TYPE_TAG_ARTIST: return g_strdup_printf ("http://musicbrainz.org/ws/1/artist/?type=xml&name=%s",wrap_a); default: return NULL; } } ///////////////////////////////// #define ID_BEGIN "id=\"" const gchar * get_mbid_from_xml (GlyrQuery * s, GlyrMemCache * c, gint * offset) { if (c==NULL || s==NULL || offset==NULL) return NULL; const gchar * searchterm = NULL; const gchar * checkstring = NULL; const gchar * comparestr = NULL; switch (please_what_type (s) ) { case GLYR_TYPE_TAG_TITLE: checkstring = ""; searchterm = "<track "; comparestr = s->title; break; case GLYR_TYPE_TAG_ALBUM: checkstring = "<title>"; searchterm = "<release "; comparestr = s->album; break; case GLYR_TYPE_TAG_ARTIST: checkstring = "<name>"; searchterm = "<artist "; comparestr = s->artist; break; default: glyr_message (1,s,"Warning: (tags/musicbrainz.c) Unable to determine type.\n"); } const gchar * mbid = NULL; if (searchterm != NULL) { gchar * node = c->data + *offset; gchar * search_check = NULL; gsize nlen = (sizeof ID_BEGIN) - 1; gsize clen = strlen (checkstring); while (node && (node = strstr (node,searchterm) ) && mbid == NULL) { if (! (node = strstr (node,ID_BEGIN) ) ) break; if (! (search_check = strstr (node,checkstring) ) ) break; search_check += clen; gchar * to_compare = copy_value (search_check,strstr (search_check,"</") ); if (to_compare != NULL) { if (levenshtein_strnormcmp (s,to_compare,comparestr) <= s->fuzzyness) { mbid = (gchar*) copy_value (node+nlen,strchr (node+nlen,'"') ); } g_free (to_compare); } node += (sizeof ID_BEGIN) - 1; } *offset = node - c->data; } return mbid; } ///////////////////////////////// /* Returns only a parseable memcache */ GlyrMemCache * generic_musicbrainz_parse (cb_object * capo, gint * last_mbid, const gchar * include) { gsize offset = 0; const gchar * mbid = NULL; GlyrMemCache * info = NULL; while (offset < capo->cache->size && info==NULL && (mbid = get_mbid_from_xml (capo->s,capo->cache,last_mbid) ) ) { if (mbid != NULL) { const gchar * type = NULL; switch (please_what_type (capo->s) ) { case GLYR_TYPE_TAG_TITLE: type = "track"; break; case GLYR_TYPE_TAG_ALBUM: type = "release"; break; case GLYR_TYPE_TAG_ARTIST: type = "artist"; break; } gchar * info_page_url = g_strdup_printf ("http://musicbrainz.org/ws/1/%s/%s?type=xml&inc=%s",type,mbid,include); if (info_page_url) { info = download_single (info_page_url,capo->s,NULL); g_free (info_page_url); } g_free ( (gchar*) mbid); } } return info; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/common/musicbrainz.h���������������������������������������������������������0000664�0000000�0000000�00000002500�13011626140�0020571�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of musicrelated metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #ifndef GLYR_COMMON_MUSICBRAINZ_H #define GLYR_COMMON_MUSICBRAINZ_H #include "../../core.h" gint please_what_type (GlyrQuery * s); const gchar * generic_musicbrainz_url (GlyrQuery * sets); const gchar * get_mbid_from_xml (GlyrQuery * s, GlyrMemCache * c, gint * offset); GlyrMemCache * generic_musicbrainz_parse (cb_object * capo, gint * last_mbid, const gchar * include); #endif ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/common/picsearch.c�����������������������������������������������������������0000664�0000000�0000000�00000007427�13011626140�0020214�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #include "picsearch.h" const gchar * generic_picsearch_url (GlyrQuery * s, const char * fmt) { /* picsearch offers a nice way to set img_min / img_max */ gchar * base_url = "http://www.picsearch.com/index.cgi?q=%s&face=both&orientation=square&size=%dt%d"; gint img_min_size = s->img_min_size; if (img_min_size == -1) { img_min_size = 0; } gint img_max_size = s->img_max_size; if (img_max_size == -1) { img_max_size = INT_MAX; } return g_strdup_printf (base_url, fmt, img_min_size, img_max_size); } ///////////////////////////////// #define IMG_HOOK "div class=\"thumbnailTop\"" #define IMG_HOOK_BEGIN "<a rel=\"nofollow\" href=\"" #define IMG_HOOK_ENDIN "\"><img src=\"" static GlyrMemCache * parse_details_page (GlyrMemCache * to_parse) { GlyrMemCache * result = NULL; if (to_parse != NULL) { char * start = strstr (to_parse->data,IMG_HOOK); if (start != NULL) { char * img_url = get_search_value (start,IMG_HOOK_BEGIN,IMG_HOOK_ENDIN); if (img_url != NULL) { result = DL_init(); result->data = img_url; result->size = strlen (img_url); result->dsrc = g_strdup (to_parse->dsrc); } } } return result; } ///////////////////////////////// #define NODE "<div class=\"imgContainer\">" #define NODE_NEEDS_TO_BEGIN "/imageDetail.cgi" GList * generic_picsearch_parse (cb_object * capo) { GList * result_list = NULL; gchar * node = capo->cache->data; gint nodelen = (sizeof NODE) - 1; node = strstr (node,"<div id=\"results_table\">"); int items = 0, tries = 0; const int MAX_TRIES = capo->s->number * 4; while (continue_search (items,capo->s) && (node = strstr (node, "<a href=\"") ) && tries++ < MAX_TRIES) { node += nodelen; gchar * details_url = get_search_value (node,"<a href=\"","\" "); if (details_url != NULL && strncmp (details_url,NODE_NEEDS_TO_BEGIN,sizeof (NODE_NEEDS_TO_BEGIN)-1) == 0) { gchar * full_url = g_strdup_printf ("www.picsearch.com%s",details_url); if (full_url != NULL) { GlyrMemCache * to_parse = download_single (full_url,capo->s,NULL); if (to_parse != NULL) { GlyrMemCache * result = parse_details_page (to_parse); if (result != NULL) { result_list = g_list_prepend (result_list,result); items++; } DL_free (to_parse); } g_free (full_url); } g_free (details_url); } } return result_list; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/common/picsearch.h�����������������������������������������������������������0000664�0000000�0000000�00000002237�13011626140�0020213�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of musicrelated metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #ifndef GLYR_COMMON_PICSEARCH_H #define GLYR_COMMON_PICSEARCH_H #include "../../core.h" const gchar * generic_picsearch_url (GlyrQuery * s, const char * fmt); GList * generic_picsearch_parse (cb_object * capo); #endif �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/cover.c����������������������������������������������������������������������0000664�0000000�0000000�00000003200�13011626140�0016062�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../core.h" #include "../stringlib.h" #include "generic.h" ///////////////////////////////// static GList * factory (GlyrQuery * s, GList * list, gboolean * stop_me, GList ** result_list) { return generic_img_finalizer (s,list,stop_me,GLYR_TYPE_COVERART,result_list); } ///////////////////////////////// /* PlugStruct */ MetaDataFetcher glyrFetcher_cover = { .name = "cover", .type = GLYR_GET_COVERART, .default_data_type = GLYR_TYPE_COVERART, .reqs = GLYR_REQUIRES_ARTIST | GLYR_REQUIRES_ALBUM, .full_data = FALSE, .init = NULL, .destroy = NULL, .finalize = factory, .default_parallel = 3 }; ///////////////////////////////// ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/cover/�����������������������������������������������������������������������0000775�0000000�0000000�00000000000�13011626140�0015723�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/cover/albumart.c�������������������������������������������������������������0000664�0000000�0000000�00000007024�13011626140�0017701�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../core.h" #include "../../stringlib.h" static const gchar * cover_albumart_url (GlyrQuery * sets) { gint i = sets->img_min_size; gint e = sets->img_max_size; if ( (e >= 50 || e == -1) && (i == -1 || i < 450) ) { return "http://www.albumart.org/index.php?searchkey=${artist}&itempage=1&newsearch=1&searchindex=Music"; } return NULL; } ///////////////////////////////// #define NODE_START "<div id=\"main\">" #define NODE_NEXT "<li><div style=\"" #define AMZ "http://ecx.images-amazon.com/images/" #define IMG_FORMAT ".jpg" static GList * cover_albumart_parse (cb_object * capo) { GList * result_list = NULL; gchar * node = strstr (capo->cache->data,NODE_START); if (node != NULL) { /* Decide what size we want */ gsize size_it = 2; if (capo->s->img_max_size < 450 && capo->s->img_max_size != -1 && capo->s->img_min_size < 160) { size_it = 1; } /* Go through all nodes */ while (continue_search (g_list_length (result_list),capo->s) && (node = strstr (node + (sizeof NODE_NEXT) - 1,NODE_NEXT) ) ) { gchar * img_tag = node; gchar * img_end = NULL; gchar * album_name = get_search_value (node,"title=\"","\""); if (levenshtein_strnormcmp (capo->s,album_name,capo->s->album) <= capo->s->fuzzyness) { for (gsize it = 0; it < size_it; it++, img_tag += (sizeof AMZ) - 1) { if ( (img_tag = strstr (img_tag,AMZ) ) == NULL) { break; } } if ( (img_end = strstr (img_tag,IMG_FORMAT) ) != NULL) { gchar * img_url = copy_value (img_tag,img_end); if (img_url != NULL) { GlyrMemCache * result = DL_init(); result->data = g_strdup_printf (AMZ"%s"IMG_FORMAT, img_url); result->size = strlen (result->data); result_list = g_list_prepend (result_list,result); g_free (img_url); } } } g_free (album_name); } } return result_list; } ///////////////////////////////// MetaDataSource cover_albumart_src = { .name = "albumart", .key = 'b', .parser = cover_albumart_parse, .get_url = cover_albumart_url, .type = GLYR_GET_COVERART, .quality = 80, .speed = 65, .free_url = false }; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/cover/amazon.c���������������������������������������������������������������0000664�0000000�0000000�00000007772�13011626140�0017371�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #include "../common/amazon.h" // Example snippet of what we parse: /*** <SmallImage> <URL> http://ecx.images-amazon.com/images/I/51rnlRwtsiL._SL75_.jpg </URL> <Height Units="pixels">75</Height> <Width Units="pixels">75</Width> </SmallImage> <MediumImage> <URL> http://ecx.images-amazon.com/images/I/51rnlRwtsiL._SL160_.jpg </URL> <Height Units="pixels">160</Height> <Width Units="pixels">160</Width> </MediumImage> <LargeImage> <URL> http://ecx.images-amazon.com/images/I/51rnlRwtsiL.jpg </URL> <Height Units="pixels">455</Height> <Width Units="pixels">455</Width> </LargeImage> ***/ // A short note: // Since some time amazon does not allow anonymous acces to their webservices. // So you have to register an (free) account and you'll get an Accesskey and a Secretkey, // sadly amazon does not allow you to publish the secretkey to the public, // therefore I had to use freeapisign.com to access the webservices, the bad thing is: // it is limited to 30K requests per month. If you have an own account: replace the Acceskey here, // as an fallback there is the albumart and coverhunt plugin which implement a search on amazon (on serverside) ///////////////////////////////// static const gchar * cover_amazon_url (GlyrQuery * sets) { return generic_amazon_url (sets,"Images"); } ///////////////////////////////// #define END_OF_URL "</URL>" #define C_MAX(X) (capo->s->img_max_size < X && capo->s->img_max_size != -1) #define C_MIN(X) (capo->s->img_min_size >= X && capo->s->img_min_size != -1) static GList * cover_amazon_parse (cb_object *capo) { const gchar *tag_ssize = (capo->s->img_max_size == -1 && capo->s->img_min_size == -1) ? "<LargeImage>" : (C_MAX ( 30) && C_MIN (-1) ) ? "<SwatchImage>" : (C_MAX ( 70) && C_MIN (30) ) ? "<SmallImage>" : (C_MAX (150) && C_MIN (70) ) ? "<MediumImage>" : "<LargeImage>" ; GList * result_list = NULL; gchar * find = capo->cache->data; while (continue_search (g_list_length (result_list),capo->s) && (find = strstr (find + strlen (tag_ssize), tag_ssize) ) != NULL) { /* Next two XML tags not relevant */ nextTag (find); nextTag (find); gchar * endTag = NULL; if ( (endTag = strstr (find, END_OF_URL) ) != NULL) { gchar * result_url = copy_value (find,endTag); if (result_url != NULL) { GlyrMemCache * result = DL_init(); result->data = result_url; result->size = endTag - find; result_list = g_list_prepend (result_list,result); } } } return result_list; } ///////////////////////////////// MetaDataSource cover_amazon_src = { .name = "amazon", .key = 'a', .parser = cover_amazon_parse, .get_url = cover_amazon_url, .type = GLYR_GET_COVERART, .quality = 90, .speed = 85, .endmarker = NULL, .free_url = true, .lang_aware = true }; ������glyr-1.0.10/lib/intern/cover/coverartarchive.c������������������������������������������������������0000664�0000000�0000000�00000006333�13011626140�0021263�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../stringlib.h" #include "../../core.h" #include "../common/mbid_lookup.h" ////////////////////////////////////////////////// static const char * cover_coverartarchive_url (GlyrQuery * qry) { /* Return a search for the mbid, later we'll search for the cover */ return "http://musicbrainz.org/ws/2/release?query=artist:${artist}%20AND%20release:${album}"; } ////////////////////////////////////////////////// #define IMAGE_NODE "\"image\":\"" /* This should work, but apparently there is no real data yet there... */ static GList * parse_archive_json (GlyrMemCache * input, GlyrQuery * qry) { GList * result_list = NULL; char * node = input->data; while ( (node = strstr (node + sizeof (IMAGE_NODE), IMAGE_NODE) ) ) { char * url = copy_value (node, strstr (node + sizeof (IMAGE_NODE), "\"") ); if (url != NULL) { GlyrMemCache * item = DL_init(); item->data = url; item->size = strlen (url); item->dsrc = g_strdup (input->dsrc); result_list = g_list_prepend (result_list, item); } } return result_list; } ////////////////////////////////////////////////// #define API_ROOT "http://coverartarchive.org/release/%s/" ////////////////////////////////////////////////// static GList * cover_coverartarchive_parse (cb_object * capo) { GList *result_list = NULL; char * mbid = mbid_parse_data (capo->cache, "release", "title", capo->s->album, capo->s); if (mbid != NULL) { char * full_url = g_strdup_printf (API_ROOT, mbid); if (full_url != NULL) { GlyrMemCache * json_data = download_single (full_url, capo->s, NULL); if (json_data != NULL) { result_list = parse_archive_json (json_data, capo->s); DL_free (json_data); } g_free (full_url); } } return result_list; } ////////////////////////////////////////////////// MetaDataSource cover_coverartarchive_src = { .name = "coverartarchive", .key = 'z', .parser = cover_coverartarchive_parse, .get_url = cover_coverartarchive_url, .type = GLYR_GET_COVERART, .quality = 90, .speed = 80, .endmarker = NULL, .free_url = false }; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/cover/coverhunt.c������������������������������������������������������������0000664�0000000�0000000�00000012267�13011626140�0020114�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../stringlib.h" #include "../../core.h" /* * Coverhunt seems to have some load problems at time of writing... */ static const gchar * cover_coverhunt_url (GlyrQuery * sets) { if (sets->img_min_size <= 500 || sets->img_min_size == -1) { return "http://www.coverhunt.com/index.php?query=${artist}+${album}&action=Find+my+CD+Covers"; } return NULL; } static gboolean check_size (const char * art_root, const char *hw, cb_object * capo) { gchar * begin = strstr (art_root,hw); if (begin != NULL) { gchar * end = strchr (begin,' '); gchar * buf = copy_value (begin+strlen (hw),end); if (buf != NULL) { gint atoid = strtol (buf,NULL,10); g_free (buf); if ( (atoid >= capo->s->img_min_size || capo->s->img_min_size == -1) && (atoid <= capo->s->img_max_size || capo->s->img_max_size == -1) ) return TRUE; } } return FALSE; } #define SEARCH_RESULT_BEGIN "<table><tr><td" #define IMG_START "<img src=\"" #define NODE_BEGIN "<a href=\"/go/" /* Take the first link we find. * coverhunt sadly offers no way to check if the * image is really related to the query we're searching for */ static GList * cover_coverhunt_parse (cb_object *capo) { GList * result_list = NULL; /* navigate to start of search results */ gchar * table_start; if ( (table_start = strstr (capo->cache->data,SEARCH_RESULT_BEGIN) ) == NULL) { /* Whoops, nothing to see here */ return NULL; } while (continue_search (g_list_length (result_list),capo->s) && (table_start = strstr (table_start + 1,NODE_BEGIN) ) ) { gchar * table_end = NULL; if ( (table_end = strstr (table_start,"\">") ) != NULL) { gchar * go_url = copy_value (table_start + strlen (NODE_BEGIN),table_end); if (go_url) { gchar * real_url = g_strdup_printf ("http://www.coverhunt.com/go/%s",go_url); if (real_url != NULL) { GlyrMemCache * search_buf = download_single (real_url,capo->s,"<div id=\"right\">"); if (search_buf != NULL) { gchar * artwork = strstr (search_buf->data, "<div class=\"artwork\">"); if (artwork != NULL) { if (check_size (artwork,"height=",capo) && check_size (artwork,"width=",capo) ) { gchar * img_start = strstr (artwork,IMG_START); if (img_start != NULL) { img_start += (sizeof IMG_START) - 1; gchar * img_end = strstr (img_start,"\" "); if (img_end != NULL) { gchar * url = copy_value (img_start,img_end); if (url != NULL) { GlyrMemCache * shell = DL_init(); shell->data = url; shell->size = img_end - img_start; shell->dsrc = g_strdup (real_url); result_list = g_list_prepend (result_list,shell); } } } } } DL_free (search_buf); } g_free (real_url); } g_free (go_url); } } } return result_list; } /* Queued last, as long coverhunt is down */ MetaDataSource cover_coverhunt_src = { .name = "coverhunt", .key = 'c', .parser = cover_coverhunt_parse, .get_url = cover_coverhunt_url, .type = GLYR_GET_COVERART, .endmarker = "<div id=\"footer\">", .quality = 0, /* ex. 70 */ .speed = 0, /* ex. 40 */ .free_url = false }; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/cover/discogs.c��������������������������������������������������������������0000664�0000000�0000000�00000011472�13011626140�0017527�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define API_ENTRY "https://api.discogs.com/database/search?type=release&q=${artist}&token=gUtGiYqikFIMrDYStQNgewQbrCdRoTBOTXDNtRgB" ///////////////////////////////// /* * # Example Snippet (for type = release) * { # Start of item * "style": ["Grunge"], * "thumb": "http://api.discogs.com/image/R-90-2845667-1303704896.jpeg", * "title": "Nirvana - Nirvana", * "country": "Russia", * "format": ["CD"], * "uri": "/Nirvana-Nirvana/release/2845667", * "label": "\u0414\u043e\u043c\u0430\u0448\u043d\u044f\u044f \u041a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044f", * "catno": "none", * "year": "2001", * "genre": ["Rock"], * "resource_url": "http://api.discogs.com/releases/2845667", * "type": "release", * "id": 2845667 * }, # End of item * { * ..more data.. * } */ /* Note: "thumb": null is ignored! */ #define TITLE_SUBNODE "\"title\": \"" #define THUMB_SUBDNOE "\"thumb\": \"" #define FOLLR_SUBNODE "\"uri\": \"" #define NODE THUMB_SUBDNOE #define ENDOF_SUBNODE "\"," ///////////////////////////////////////////////////// static bool check_artist_album (GlyrQuery * q, const char * artist_album) { bool rc = false; char ** split = g_strsplit (artist_album," - ",2); if (split && split[0] && split[1]) { rc = levenshtein_strnormcmp (q,q->artist,split[0]) <= q->fuzzyness && levenshtein_strnormcmp (q,q->album, split[1]) <= q->fuzzyness; } g_strfreev (split); return rc; } ///////////////////////////////////////////////////// static GlyrMemCache * transform_url (cb_object * s, const char * url) { GlyrMemCache * rc = NULL; char * rc_url = g_strdup (url); if (rc_url != NULL) { char * slash = strrchr (rc_url,'/'); if (slash != NULL) { char * sp = strchr (slash,'-'); if (sp != NULL) { char * ep = strchr (sp + 1, '-'); if(ep != NULL) { // size_t rest_len = rc_size - (ep - rc_url) + 1; // memmove (sp,ep,rest_len); rc = DL_init(); rc->data = (char*) rc_url; rc->size = strlen (rc_url); rc->dsrc = g_strdup (s->url); } } } } return rc; } ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// static const char * cover_discogs_url (GlyrQuery * q) { return API_ENTRY; } ///////////////////////////////////////////////////// static GList * cover_discogs_parse (cb_object * capo) { GList * result_list = NULL; /* Jump to the very first node 'directly' */ gchar * node = capo->cache->data; while (continue_search (g_list_length (result_list),capo->s) && (node = strstr (node + (sizeof NODE) - 1,NODE) ) != NULL) { char * artist_album = get_search_value (node,TITLE_SUBNODE,ENDOF_SUBNODE); if (artist_album && check_artist_album (capo->s,artist_album) ) { char * thumb_url = get_search_value (node,THUMB_SUBDNOE,ENDOF_SUBNODE); if (thumb_url) { GlyrMemCache * p = transform_url (capo,thumb_url); if (p != NULL) { result_list = g_list_prepend (result_list,p); } g_free (thumb_url); } } g_free (artist_album); } return result_list; } ///////////////////////////////// MetaDataSource cover_discogs_src = { .name = "discogs", .key = 'd', .parser = cover_discogs_parse, .get_url = cover_discogs_url, .type = GLYR_GET_COVERART, .quality = 60, .speed = 70, .endmarker = NULL, .free_url = false }; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/cover/google.c���������������������������������������������������������������0000664�0000000�0000000�00000003444�13011626140�0017350�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../stringlib.h" #include "../common/google.h" ///////////////////////////////// static const gchar * cover_google_url (GlyrQuery * s) { const gchar * result = NULL; gchar * searchterm = g_strdup ("${artist}+${album}+album"); if (searchterm != NULL) { result = generic_google_url (s,searchterm); g_free (searchterm); } return result; } ///////////////////////////////// static GList * cover_google_parse (cb_object * capo) { return generic_google_parse (capo); } ///////////////////////////////// MetaDataSource cover_google_src = { .name = "google", .key = 'g', .parser = cover_google_parse, .get_url = cover_google_url, .type = GLYR_GET_COVERART, .quality = 10, .speed = 80, .endmarker = NULL, .free_url = true, .lang_aware = true }; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/cover/jamendo.c��������������������������������������������������������������0000664�0000000�0000000�00000007727�13011626140�0017521�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * Jamendo provider written by: * Christoph Piechula (christoph@nullcat.de) * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../stringlib.h" #include "../../core.h" #define RESULT_URL "http://www.jamendo.com/get/album/id/album/artworkurl/redirect/%s/?artwork_size=%d" #define SOURCE_URL "http://api.jamendo.com/get2/id+name+artist_name/album/plain/?order=searchweight_desc&n=100&searchquery=${album}" static int get_cover_size (GlyrQuery * query); static bool check_values (GlyrQuery * query, char * artist, char * album); ///////////////////////////////// static const char * cover_jamendo_url (GlyrQuery * sets) { return SOURCE_URL; } ///////////////////////////////// void do_line_split (char ** p_arr, char * line) { g_return_if_fail (p_arr && line); char * hop = line; *p_arr = line; while ( (hop = strchr (hop,'\t') ) != NULL) { p_arr++; *hop = 0; (*p_arr) = ++hop; } } ///////////////////////////////// static GList * cover_jamendo_parse (cb_object *capo) { // A nice parser with zero memory overhead.. GList * result_list = NULL; gchar * line = capo->cache->data; while (continue_search (g_list_length (result_list),capo->s) ) { char * line_end; if ( (line_end = strchr (line,'\n') ) != NULL) { *line_end = 0; char * line_split[3] = {0,0,0}; do_line_split (line_split,line); if (check_values (capo->s,line_split[2],line_split[1]) ) { char * url = g_strdup_printf (RESULT_URL,line_split[0],get_cover_size (capo->s) ); GlyrMemCache * result = DL_init(); result->data = url; result->size = strlen (url); result_list = g_list_prepend (result_list,result); } line = ++line_end; } else { break; } } return result_list; } ///////////////////////////////// static bool check_values (GlyrQuery * query, char * artist, char * album) { if (levenshtein_strnormcmp (query, query->artist, artist) <= query->fuzzyness && levenshtein_strnormcmp (query, query->album,album) <= query->fuzzyness) { return true; } return false; } ///////////////////////////////// static int get_cover_size (GlyrQuery * query) { int cover_size[] = {50,50,100,200,300,400,600,INT_MAX}; int array_len = (sizeof (cover_size) /sizeof (int) ); if (query->img_max_size == -1) { return 400; } else { for (int i=1; i<array_len; i++) { if (query->img_max_size <= cover_size[i]) { return cover_size[i-1]; } } } return 400; } ///////////////////////////////// MetaDataSource cover_jamendo_src = { .name = "jamendo", .key = 'j', .parser = cover_jamendo_parse, .get_url = cover_jamendo_url, .type = GLYR_GET_COVERART, .quality = 90, .speed = 75, .endmarker = NULL, .free_url = false }; �����������������������������������������glyr-1.0.10/lib/intern/cover/lastfm.c���������������������������������������������������������������0000664�0000000�0000000�00000007527�13011626140�0017370�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../stringlib.h" #include "../../core.h" ///////////////////////////////// static const char * cover_lastfm_url (GlyrQuery * sets) { return "http://ws.audioscrobbler.com/2.0/?method=album.search&autocorrect=1&album=${artist}+${album}&api_key="API_KEY_LASTFM; } ///////////////////////////////// #define ALBUM_NODE "<album>" #define BAD_DEFAULT_IMAGE "http://cdn.last.fm/flatness/catalogue/noimage/2/default_album_medium.png" static GList * cover_lastfm_parse (cb_object *capo) { /* Handle size requirements (Default to large) */ const gchar * tag_ssize = NULL ; const gchar * tag_esize = "</image>"; /* find desired size */ if ( size_is_okay (300,capo->s->img_min_size,capo->s->img_max_size) ) tag_ssize = "<image size=\"extralarge\">"; else if ( size_is_okay (125,capo->s->img_min_size,capo->s->img_max_size) ) tag_ssize = "<image size=\"large\">"; else if ( size_is_okay (64, capo->s->img_min_size,capo->s->img_max_size) ) tag_ssize = "<image size=\"middle\">"; else if ( size_is_okay (34, capo->s->img_min_size,capo->s->img_max_size) ) tag_ssize = "<image size=\"small\">"; else if ( true || false ) tag_ssize = "<image size=\"extralarge\">"; /* The result (perhaps) */ GList * result_list = NULL; gchar * find = capo->cache->data; while (continue_search (g_list_length (result_list),capo->s) && (find = strstr (find + sizeof(ALBUM_NODE), ALBUM_NODE) ) != NULL) { gchar * artist = get_search_value (find, "<artist>", "</artist>"); gchar * album = get_search_value (find, "<name>", "</name>"); if (levenshtein_strnormcmp (capo->s, artist, capo->s->artist) <= capo->s->fuzzyness && levenshtein_strnormcmp (capo->s, album, capo->s->album) <= capo->s->fuzzyness) { gchar * img_start = strstr(find, tag_ssize); if (img_start != NULL) { gchar * url = get_search_value (find, (gchar*) tag_ssize, (gchar*) tag_esize); if (url != NULL) { if (strcmp (url,BAD_DEFAULT_IMAGE) != 0) { GlyrMemCache * result = DL_init(); result->data = url; result->size = strlen (url); result_list = g_list_prepend (result_list,result); } else { g_free (url); } } } } g_free (artist); g_free (album); } return result_list; } ///////////////////////////////// MetaDataSource cover_lastfm_src = { .name = "lastfm", .key = 'l', .parser = cover_lastfm_parse, .get_url = cover_lastfm_url, .type = GLYR_GET_COVERART, .quality = 90, .speed = 75, .endmarker = NULL, .free_url = false }; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/cover/lyricswiki.c�����������������������������������������������������������0000664�0000000�0000000�00000010731�13011626140�0020262�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../stringlib.h" #include "../../core.h" const gchar * cover_lyricswiki_url (GlyrQuery * sets) { const gchar * url = NULL; if (sets->img_min_size < 650) { url = "http://lyrics.wikia.com/api.php?format=xml&action=query&list=allimages&aiprefix=${artist}"; } return url; } ///////////////////////////////// static gboolean check_file_format (GlyrQuery * query, gchar * filename) { gboolean result = FALSE; gsize length = strlen (filename); gsize offset = 0; gchar *token = NULL; gchar ** token_list = g_strsplit (query->allowed_formats,GLYR_DEFAULT_FROM_ARGUMENT_DELIM,0); while ( (token = token_list[offset]) != NULL) { if (g_str_has_suffix (filename,token) == TRUE) { result = TRUE; gsize format_pos = length - strlen (token); filename[format_pos] = '\0'; if (format_pos != 0 && filename[format_pos-1] == '.') { filename[format_pos-1] = '\0'; } break; } offset++; } g_strfreev (token_list); return result; } /** -- Example snippet <img name="Axxis_-_Access_All_Areas.jpg" timestamp="2010-08-03T17:05:20Z" url="http://images.wikia.com/lyricwiki/images/f/f9/Axxis_-_Access_All_Areas.jpg" descriptionurl="http://lyrics.wikia.com/File:Axxis_-_Access_All_Areas.jpg" /> **/ #define IMG_TAG "_-_" #define END_TAG "\" timestamp=\"" #define URL_MARKER "url=\"" #define URL_END "\" descriptionurl=" #define NEXT_NAME "<img name=\"" GList * cover_lyricswiki_parse (cb_object * capo) { gchar * find = capo->cache->data; gchar * endTag = NULL; GList * result_list = NULL; gchar * escaped_album_name = strreplace (capo->s->album," ","_"); if (escaped_album_name != NULL) { /* Go through all names and compare them with Levenshtein */ while (continue_search (g_list_length (result_list),capo->s) && (find = strstr (find+ (sizeof IMG_TAG) - 1,IMG_TAG) ) != NULL) { /* Find end & start of the name */ find += (sizeof IMG_TAG) - 1; endTag = strstr (find,END_TAG); if (endTag == NULL || endTag <= find) continue; /* Copy the name of the current album */ gchar * name = copy_value (find,endTag); if (name != NULL) { if (check_file_format (capo->s,name) && levenshtein_strnormcmp (capo->s,escaped_album_name,name) <= capo->s->fuzzyness) { gchar * url = get_search_value (endTag, URL_MARKER, URL_END); if (url != NULL) { GlyrMemCache * result = DL_init(); result->data = url; result->size = strlen (url); result_list = g_list_prepend (result_list,result); } } /* Get next img tag */ find = strstr (endTag,NEXT_NAME); g_free (name); /* Whoops, right into nonexistence.. */ if (find == NULL) break; } } g_free (escaped_album_name); } return result_list; } ///////////////////////////////// MetaDataSource cover_lyricswiki_src = { .name = "lyricswiki", .key = 'w', .parser = cover_lyricswiki_parse, .get_url = cover_lyricswiki_url, .type = GLYR_GET_COVERART, .quality = 75, .speed = 85, .endmarker = NULL, .free_url = false }; ���������������������������������������glyr-1.0.10/lib/intern/cover/musicbrainz.c����������������������������������������������������������0000664�0000000�0000000�00000007435�13011626140�0020426�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../stringlib.h" #include "../../core.h" ///////////////////////////////// static const char * cover_musicbrainz_url (GlyrQuery * q) { return "http://musicbrainz.org/ws/2/release?query=${album}&limit=10&offset=0"; } ///////////////////////////////// #define COVERART "<div class=\"cover-art\">" #define AMZ_URL_START "\"http://ecx.images-amazon.com/" /* * This is silly overall, * but coverartarchive.org does not seem to work fully yet. */ static GlyrMemCache * parse_web_page (GlyrMemCache * page) { GlyrMemCache * retv = NULL; if (page && page->data) { char * begin = strstr (page->data,COVERART); if (begin != NULL) { char * amz_url = strstr (begin,AMZ_URL_START); if (amz_url != NULL) { char * img_url = get_search_value (amz_url,"\"","\""); if (img_url != NULL) { retv = DL_init(); retv->dsrc = g_strdup (page->dsrc); retv->data = img_url; retv->size = strlen (img_url); } } } DL_free (page); } return retv; } ///////////////////////////////// #define NODE "<release " #define DL_URL "http://musicbrainz.org/release/%s" ///////////////////////////////// static GList * cover_musicbrainz_parse (cb_object * capo) { GList * result_list = NULL; char * node = capo->cache->data; while (continue_search (g_list_length (result_list),capo->s) && (node = strstr (node + 1,NODE) ) ) { char * album = get_search_value (node,"<title>",""); char * artist = get_search_value (node,"" ,"" ); if (levenshtein_strnormcmp (capo->s,artist,capo->s->artist) <= capo->s->fuzzyness && levenshtein_strnormcmp (capo->s,album ,capo->s->album ) <= capo->s->fuzzyness) { char * ID = get_search_value (node,"id=\"","\" "); if (ID != NULL) { char * url = g_strdup_printf (DL_URL,ID); if (url != NULL) { GlyrMemCache * item = parse_web_page (download_single (url,capo->s,NULL) ); if (item != NULL) { result_list = g_list_prepend (result_list,item); } } g_free (url); } g_free (ID); } g_free (artist); g_free (album); } return result_list; } ///////////////////////////////// MetaDataSource cover_musicbrainz_src = { .name = "musicbrainz", .key = 'z', .parser = cover_musicbrainz_parse, .get_url = cover_musicbrainz_url, .type = GLYR_GET_COVERART, .quality = 85, .speed = 70, .endmarker = NULL, .free_url = false }; glyr-1.0.10/lib/intern/cover/picsearch.c000066400000000000000000000032211301162614000200260ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #include "../common/picsearch.h" ///////////////////////////////// static const gchar * cover_picsearch_url (GlyrQuery * s) { return generic_picsearch_url (s,"${artist}+${album}+coverart"); } ///////////////////////////////// static GList * cover_picsearch_parse (cb_object * capo) { return generic_picsearch_parse (capo); } ///////////////////////////////// MetaDataSource cover_picsearch_src = { .name = "picsearch", .key = 'p', .parser = cover_picsearch_parse, .get_url = cover_picsearch_url, .type = GLYR_GET_COVERART, .quality = 50, .speed = 60, .endmarker = NULL, .free_url = true }; glyr-1.0.10/lib/intern/cover/rhapsody.c000066400000000000000000000114521301162614000177230ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" #include "../../stringlib.h" ///////////////////////////////// static gchar * translate_string (gchar * string) { gchar * result = NULL; if (string != NULL) { gchar * beautiful = beautify_string (string); if (beautiful != NULL) { gchar * downcase = g_utf8_strdown (beautiful,-1); if (downcase != NULL) { result = translate_umlauts (downcase); g_free (downcase); } g_free (beautiful); } } return result; } ///////////////////////////////// static const gchar * cover_rhapsody_url (GlyrQuery * query) { gchar * result = NULL; gchar * corrected_artist = translate_string (query->artist); gchar * corrected_album = translate_string (query->album); if (corrected_artist && corrected_album) { result = g_strdup_printf ("http://feeds.rhapsody.com/%s/%s/data.xml",corrected_artist,corrected_album); } g_free (corrected_artist); g_free (corrected_album); return result; } ///////////////////////////////// static gboolean check_size (GlyrQuery * s, gchar * ref) { gboolean result = FALSE; if (ref != NULL) { gchar * width_str = get_search_value (ref,"width=\"","\""); gchar * height_str = get_search_value (ref,"height=\"","\""); if (width_str && height_str) { gint width = strtol (width_str, NULL,10); gint height = strtol (height_str,NULL,10); result = size_is_okay ( (width+height) /2,s->img_min_size,s->img_max_size); } g_free (width_str); g_free (height_str); } return result; } ///////////////////////////////// #define DELIM_BEG "" #define DELIM_END "" #define NODE "cache->data,DELIM_BEG); gchar * delim_end = strstr (capo->cache->data,DELIM_END); GlyrMemCache * special_size = NULL; if (delim_beg && delim_end) { gchar * node = delim_beg; gsize nd_len = (sizeof NODE) - 1; while (continue_search (g_list_length (result_list),capo->s) && (node = strstr (node + nd_len, NODE) ) && node < delim_end) { node += nd_len; if (check_size (capo->s,node) == TRUE) { gchar * url = get_search_value (node,"src=\"","\""); if (url != NULL) { GlyrMemCache * result = DL_init(); result->data = url; result->size = strlen (url); result_list = g_list_prepend (result_list,result); /* A very cool hack. Thanks Bansheeproject! */ if (strstr (result->data,HACK_SIZE) != NULL) { special_size = result; } } } } /* Awesome hack continues.. */ if (special_size != NULL) { /* Note: Prepend the large size at begin: * If only one item requested it will just * be thrown away (the small size) */ GlyrMemCache * result = DL_init(); result->data = strreplace (special_size->data,HACK_SIZE,HIGH_SIZE); result->size = strlen (result->data); result_list = g_list_prepend (result_list,result); } } return result_list; } ///////////////////////////////// MetaDataSource cover_rhapsody_src = { .name = "rhapsody", .key = 'r', .parser = cover_rhapsody_parse, .get_url = cover_rhapsody_url, .type = GLYR_GET_COVERART, .quality = 50, .speed = 80, .endmarker = NULL, .free_url = true }; glyr-1.0.10/lib/intern/cover/slothradio.c000066400000000000000000000101151301162614000202350ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../stringlib.h" #include "../../core.h" #define API_URL "http://www.slothradio.com/covers/?adv=1&artist=${artist}&album=${album}&imgsize=%c&locale=%s&sort=salesrank" #define RESULT_LIST_START "" #define RESULT_ITEM_START "" #define RESULT_LIST_END "" #define RESULT_ITEM_END "" /////////////////////// #define STREQ(s1,s2) (g_ascii_strcasecmp(s1,s2) == 0) static const char * cover_slothradio_url (GlyrQuery * s) { const char * locale = "us"; if (STREQ (s->lang,"uk") || STREQ (s->lang,"de") ) locale = s->lang; /* * Possible image sizes: * x = largest available * l = large (300**2) * m = medium (130**2) * s = small (50 **2) **/ char image_size = 'x'; if (s->img_max_size != -1) { if (s->img_max_size < 75) image_size = 's'; else if (s->img_max_size < 150) image_size = 'm'; else if (s->img_max_size < 350) image_size = 'l'; } return g_strdup_printf (API_URL, image_size, locale); } /////////////////////// static bool check_size (GlyrQuery * q, char * node) { bool rc = false; char * width = get_search_value (node,"width=\"", "\""); char * height = get_search_value (node,"height=\"","\""); if (width && height) { int w = strtol (width, NULL,10); int h = strtol (height,NULL,10); if (size_is_okay (w,q->img_min_size,q->img_max_size) && size_is_okay (h,q->img_min_size,q->img_max_size) ) rc = true; } g_free (width); g_free (height); return rc; } /////////////////////// static GList * cover_slothradio_parse (cb_object * capo) { GList * result_list = NULL; const char * bound_start = strstr (capo->cache->data,RESULT_LIST_START); if (bound_start == NULL) return NULL; const char * bound_end = strstr (bound_start,RESULT_LIST_END); if (bound_end == NULL) return NULL; char * node = (char*) bound_start; while ( (node = strstr (node + sizeof (RESULT_ITEM_START),RESULT_ITEM_START) ) != NULL) { if (node >= bound_end) break; char * url = get_search_value (node,"img src=\"","\""); if (url != NULL) { if (check_size (capo->s,node) ) { GlyrMemCache * result = DL_init(); result->dsrc = g_strdup (capo->url); result->data = url; result->size = strlen (url); result_list = g_list_prepend (result_list,result); } else { g_free (url); } } if (continue_search (g_list_length (result_list),capo->s) == false) break; } return result_list; } /////////////////////// MetaDataSource cover_slothradio_src = { .name = "slothradio", .key = 's', .parser = cover_slothradio_parse, .get_url = cover_slothradio_url, .type = GLYR_GET_COVERART, .quality = 80, .speed = 80, .endmarker = NULL, .free_url = true }; glyr-1.0.10/lib/intern/generic.c000066400000000000000000000154271301162614000163760ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "generic.h" #include "../core.h" #include "../stringlib.h" struct callback_save_struct { GHashTable * table; GLYR_DATA_TYPE type; GList * results; }; ///////////////////////////////// /* Simple finalizer template sufficient for most */ GList * generic_txt_finalizer (GlyrQuery * settings, GList * input_list, gboolean * stop_me, GLYR_DATA_TYPE type, GList ** result_list) { gboolean add_to_list = TRUE; GList * almost_copied = NULL; for (GList * elem = input_list; elem; elem = elem->next) { GlyrMemCache * item = elem->data; if (is_in_result_list (item,result_list[0]) == FALSE && add_to_list == TRUE) { /* Set to some default type */ if (item->type == GLYR_TYPE_UNKNOWN) { item->type = type; } /* call user defined callback */ GLYR_ERROR response = GLYRE_OK; if (settings->callback.download) { /* Call the usercallback */ response = settings->callback.download (item,settings); } if (response != GLYRE_SKIP && response != GLYRE_STOP_PRE) { almost_copied = g_list_prepend (almost_copied,item); } else { DL_free (item); item = NULL; } if (response == GLYRE_STOP_POST || response == GLYRE_STOP_PRE) { add_to_list = FALSE; *stop_me = TRUE; } } else { settings->itemctr--; DL_free (item); item = NULL; } } return almost_copied; } ///////////////////////////////// static GList * async_dl_callback (cb_object * capo, void * userptr, bool * stop_download, gint * add_item) { if (capo->cache != NULL && userptr != NULL) { /* Sanitize this */ struct callback_save_struct * saver = userptr; GHashTable * prov_url_table = saver->table; capo->cache->is_image = true; if (prov_url_table != NULL) { GlyrMemCache * old_cache = g_hash_table_lookup (prov_url_table,capo->cache->dsrc); GLYR_ERROR response = GLYRE_OK; if (old_cache != NULL) { update_md5sum (capo->cache); if (is_in_result_list (capo->cache,saver->results) == FALSE) { capo->cache->prov = (old_cache->prov!=NULL) ? g_strdup (old_cache->prov) : NULL; capo->cache->img_format = (old_cache->img_format) ? g_strdup (old_cache->img_format) : NULL; if (capo->cache->type == GLYR_TYPE_UNKNOWN) { capo->cache->type = saver->type; } if (capo->s->callback.download != NULL) { response = capo->s->callback.download (capo->cache,capo->s); } *add_item = (response != GLYRE_SKIP && response != GLYRE_STOP_PRE); } else { capo->s->itemctr--; *add_item = FALSE; } } if (response == GLYRE_STOP_POST || response == GLYRE_STOP_PRE) { *stop_download = TRUE; } } else { glyr_message (-1,NULL,"glyr: Warn: Hashtable is empty im image_callback!\n"); } } return NULL; } ///////////////////////////////// GList * generic_img_finalizer (GlyrQuery * s, GList * list, gboolean * stop_me, GLYR_DATA_TYPE type, GList ** result_list) { /* Just return URLs */ if (s->download == false) { for (GList * elem = list; elem; elem = elem->next) { GlyrMemCache * img = elem->data; img->is_image = false; } return generic_txt_finalizer (s,list,stop_me,GLYR_TYPE_IMG_URL,result_list); } else { /* Convert to a list of URLs first */ GList * url_list = NULL; /* Hashtable to associate the provider name with the corresponding URL */ GHashTable * cache_url_table = g_hash_table_new_full (g_str_hash,g_str_equal, NULL, (GDestroyNotify) DL_free ); /* Iterate over all caches and turn them to GList */ for (GList * item = list; item; item = item->next) { GlyrMemCache * cache = item->data; /* Make a copy, since we free the cache */ gchar * url_double = g_strdup (cache->data); url_list = g_list_prepend (url_list,url_double); /* Fill in the URL */ g_hash_table_insert (cache_url_table, (gpointer) url_double, (gpointer) cache); } /* We need to pass this to the callback */ struct callback_save_struct userptr = { .table = cache_url_table, .type = type, .results = result_list ? result_list[0] : NULL }; /* Download images in parallel */ GList * dl_raw_images = async_download (url_list,NULL,s,1, (g_list_length (url_list) /2),async_dl_callback,&userptr,FALSE); /* Default to the given type */ for (GList * elem = dl_raw_images; elem; elem = elem->next) { GlyrMemCache * item = elem->data; if (item && item->type == GLYR_TYPE_UNKNOWN) { item->type = type; } } /* Freeing Party */ g_hash_table_destroy (cache_url_table); glist_free_full (url_list,g_free); /* Ready to save images */ return dl_raw_images; } return NULL; } ///////////////////////////////// glyr-1.0.10/lib/intern/generic.h000066400000000000000000000024741301162614000164010ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a command-line tool and library to download various sort of musicrelated metadata. * + Copyright (C) [2011-2016] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #ifndef GLYR_GENERIC_H #define GLYR_GENERIC_H #include #include "../core.h" GList * generic_txt_finalizer (GlyrQuery * settings, GList * input_list, gboolean * stop_me, GLYR_DATA_TYPE type, GList ** result_list); GList * generic_img_finalizer (GlyrQuery * s, GList * list, gboolean * stop_me, GLYR_DATA_TYPE type, GList ** result_list); #endif glyr-1.0.10/lib/intern/guitartabs.c000066400000000000000000000040331301162614000171160ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../core.h" #include "../stringlib.h" #include "generic.h" ///////////////////////////////// static GList * factory (GlyrQuery * s, GList * list, gboolean * stop_me, GList ** result_list) { /* Fix up tabs, escape chars etc. */ for (GList * elem = list; elem; elem = elem->next) { GlyrMemCache * item = elem->data; if (item != NULL) { gchar * temp = beautify_string (item->data); g_free (item->data); item->data = temp; item->size = (item->data) ? strlen (item->data) : 0; } } /* Let the rest do by the norma generic finalizer */ return generic_txt_finalizer (s,list,stop_me,GLYR_TYPE_GUITARTABS,result_list); } ///////////////////////////////// /* PlugStruct */ MetaDataFetcher glyrFetcher_guitartabs = { .name = "guitartabs", .type = GLYR_GET_GUITARTABS, .default_data_type = GLYR_TYPE_GUITARTABS, .reqs = GLYR_REQUIRES_ARTIST | GLYR_REQUIRES_TITLE, .full_data = TRUE, .init = NULL, .destroy = NULL, .finalize = factory, }; ///////////////////////////////// glyr-1.0.10/lib/intern/guitartabs/000077500000000000000000000000001301162614000167525ustar00rootroot00000000000000glyr-1.0.10/lib/intern/guitartabs/chordie_com.c000066400000000000000000000107551301162614000214010ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define BASE_URL "http://www.chordie.com" #define SEARCH_URL BASE_URL"/?q=${artist}%20${title}&np=0&ps=10&wf=2221&s=RPD&wf=2221&wm=wrd&type=&sp=1&sy=1&cat=&ul=&np=0" #define RESULTS_BEGIN "" #define RESULTS_ENDIN "" #define NODE "artist) <= s->fuzzyness && levenshtein_strnormcmp (s,to_check,s->title) <= s->fuzzyness); } } return result; } ///////////////////////////////// static GlyrMemCache * parse_result_page (GlyrQuery * s, gchar * content_url) { GlyrMemCache * result = NULL; if (content_url != NULL) { GlyrMemCache * dl_cache = download_single (content_url,s,NULL); if (dl_cache != NULL) { gchar * content = get_search_value (dl_cache->data,"
","
"); if (content != NULL) { result = DL_init(); result->data = content; result->size = strlen (content); result->dsrc = g_strdup (content_url); } DL_free (dl_cache); } } return result; } ///////////////////////////////// static GList * guitartabs_chordie_parse (cb_object * capo) { GList * result_list = NULL; gchar * search_begin = strstr (capo->cache->data,RESULTS_BEGIN); if (search_begin != NULL) { gchar * search_ending = strstr (search_begin,RESULTS_ENDIN); if (search_ending != NULL) { gchar * node = search_begin; gsize nodelen = (sizeof NODE) - 1; while (continue_search (g_list_length (result_list),capo->s) && (node = strstr (node + nodelen, NODE) ) != NULL && node >= search_begin && node <= search_ending) { gchar * url = get_search_value (node,NODE,"\" "); if (url != NULL) { gchar * name_value = get_search_value (node,"\">","
"); if (check_title_value (capo->s, name_value) == TRUE) { gchar * content_url = g_strdup_printf ("%s%s",BASE_URL,url); GlyrMemCache * result = parse_result_page (capo->s,content_url); if (result != NULL) { result_list = g_list_prepend (result_list,result); } g_free (content_url); } g_free (name_value); g_free (url); } } } } return result_list; } ///////////////////////////////// MetaDataSource guitartabs_chordie_src = { .name = "chordie", .key = 'c', .parser = guitartabs_chordie_parse, .get_url = guitartabs_chordie_url, .type = GLYR_GET_GUITARTABS, .quality = 95, .speed = 75, .endmarker = NULL, .free_url = false }; glyr-1.0.10/lib/intern/guitartabs/guitaretab.c000066400000000000000000000115401301162614000212460ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define GT_BASE "http://www.guitaretab.com" #define GT_URL GT_BASE"/fetch/?type=tab&query=${title}" ///////////////////////////////// static const gchar * gt_guitaretabs_url (GlyrQuery * settings) { return GT_URL; } ///////////////////////////////// static GlyrMemCache * parse_single_page (GlyrQuery * s, const gchar * url) { GlyrMemCache * result = NULL; GlyrMemCache * tab_cache = download_single (url,s,NULL); if (tab_cache != NULL) { gchar * content = get_search_value (tab_cache->data,"
","
"); if (content != NULL) { result = DL_init(); result->data = content; result->size = strlen (content); result->dsrc = g_strdup (url); } DL_free (tab_cache); } return result; } ///////////////////////////////// #define SEARCH_RESULTS_BEGIN "
" #define SEARCH_RESULTS_ENDIN "" #define SEARCH_NODE "" #define ARTIST_END "" #define URL_END "\" " #define TITLE_BEGIN "\">" #define TITLE_ENDIN "" static GList * gt_guitaretabs_parse (cb_object * capo) { GList * result_list = NULL; gchar * begin_search = strstr (capo->cache->data,SEARCH_RESULTS_BEGIN); if (begin_search != NULL) { /* End need to assure we don't get over the search results */ gchar * endin_search = strstr (begin_search,SEARCH_RESULTS_ENDIN); if (endin_search != NULL) { /* Go through all search results */ gchar * node = begin_search; gsize nodelen = (sizeof SEARCH_NODE) - 1; while (continue_search (g_list_length (result_list),capo->s) && (node = strstr (node + nodelen, SEARCH_NODE) ) != NULL && node <= endin_search) { gchar * artist = get_search_value (node,ARTIST_BEGIN,ARTIST_END); node = strstr (node + nodelen, SEARCH_NODE); if (node != NULL) { gchar * url = get_search_value (node,SEARCH_NODE,URL_END); gchar * title = get_search_value (node,TITLE_BEGIN,TITLE_ENDIN); if (title != NULL) { gchar * delim = g_strrstr (title," chords"); if (delim == NULL) { delim = g_strrstr (title," tab"); } if (delim != NULL) { delim[0] = 0; } } /* Check if this is the item we actually search */ if (levenshtein_strnormcmp (capo->s,title, capo->s->title ) <= capo->s->fuzzyness && levenshtein_strnormcmp (capo->s,artist,capo->s->artist) <= capo->s->fuzzyness) { /* Build resulting url */ gchar * result_url = g_strdup_printf ("%s%s",GT_BASE,url); /* Go and parse it */ GlyrMemCache * result = parse_single_page (capo->s,result_url); if (result != NULL) { result_list = g_list_prepend (result_list,result); } g_free (result_url); } g_free (url); g_free (title); } g_free (artist); } } } return result_list; } ///////////////////////////////// MetaDataSource guitartabs_guitaretab_src = { .name = "guitaretab", .key = 'g', .parser = gt_guitaretabs_parse, .get_url = gt_guitaretabs_url, .type = GLYR_GET_GUITARTABS, .quality = 95, .speed = 75, .endmarker = NULL, .free_url = false }; glyr-1.0.10/lib/intern/lyrics.c000066400000000000000000000040431301162614000162570ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../core.h" #include "../stringlib.h" #include "generic.h" ///////////////////////////////// static GList * factory (GlyrQuery * s, GList * list, gboolean * stop_me, GList ** result_list) { /* Fix up lyrics, escape chars etc. */ for (GList * elem = list; elem; elem = elem->next) { GlyrMemCache * item = elem->data; if (item != NULL) { gchar * temp = beautify_string (item->data); g_free (item->data); item->data = temp; item->size = (item->data) ? strlen (item->data) : 0; } } /* Let the rest do by the norma generic finalizer */ return generic_txt_finalizer (s,list,stop_me,GLYR_TYPE_LYRICS,result_list); } ///////////////////////////////// /* PlugStruct */ MetaDataFetcher glyrFetcher_lyrics = { .name = "lyrics", .type = GLYR_GET_LYRICS, .default_data_type = GLYR_TYPE_LYRICS, .reqs = GLYR_REQUIRES_ARTIST | GLYR_REQUIRES_TITLE, .full_data = TRUE, .init = NULL, .destroy = NULL, .finalize = factory, .default_parallel = 1 }; ///////////////////////////////// glyr-1.0.10/lib/intern/lyrics/000077500000000000000000000000001301162614000161125ustar00rootroot00000000000000glyr-1.0.10/lib/intern/lyrics/chartlyrics.c000066400000000000000000000100621301162614000206040ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define CL_API_URL "http://api.chartlyrics.com/apiv1.asmx/SearchLyric?artist=${artist}&song=${title}" #define CL_API_GET "http://api.chartlyrics.com/apiv1.asmx/GetLyric?lyricId=%s&lyricCheckSum=%s" ///////////////////////////////// static const gchar * lyrics_chartlyrics_url (GlyrQuery * s) { return CL_API_URL; } ///////////////////////////////// #define LYRIC_TEXT_BEG "" #define LYRIC_TEXT_END "" static GlyrMemCache * get_lyrics_from_results (GlyrQuery * s, const gchar * url) { GlyrMemCache * result = NULL; GlyrMemCache * dl_cache = download_single (url,s,NULL); if (dl_cache != NULL) { gchar * text = get_search_value (dl_cache->data,LYRIC_TEXT_BEG,LYRIC_TEXT_END); if (text != NULL) { result = DL_init(); result->data = text; result->size = strlen (text); result->dsrc = g_strdup (url); } DL_free (dl_cache); } return result; } ///////////////////////////////// #define LYRIC_NODE "" #define LYRIC_CHECKSUM_BEG "" #define LYRIC_CHECKSUM_END "" #define ARTIST_BEG "" #define ARTIST_END "" #define SONG_BEG "" #define SONG_END "" #define LYRIC_ID_BEG "" #define LYRIC_ID_END "" static GList * lyrics_chartlyrics_parse (cb_object * capo) { GList * result_list = NULL; gchar * node = capo->cache->data; gint nodelen = (sizeof LYRIC_NODE) - 1; while (continue_search (g_list_length (result_list),capo->s) && (node = strstr (node + nodelen, LYRIC_NODE) ) != NULL) { node += nodelen; gchar * artist = get_search_value (node,ARTIST_BEG,ARTIST_END); gchar * title = get_search_value (node,SONG_BEG,SONG_END); if (levenshtein_strnormcmp (capo->s,artist,capo->s->artist) <= capo->s->fuzzyness && levenshtein_strnormcmp (capo->s,title,capo->s->title) <= capo->s->fuzzyness) { gchar * lyric_id = get_search_value (node,LYRIC_ID_BEG,LYRIC_ID_END); gchar * lyric_checksum = get_search_value (node,LYRIC_CHECKSUM_BEG,LYRIC_CHECKSUM_END); if (lyric_id && lyric_checksum && strcmp (lyric_id,"0") != 0) { gchar * content_url = g_strdup_printf (CL_API_GET,lyric_id,lyric_checksum); GlyrMemCache * result = get_lyrics_from_results (capo->s,content_url); if (result != NULL) { result_list = g_list_prepend (result_list,result); } g_free (content_url); } g_free (lyric_id); g_free (lyric_checksum); } g_free (artist); g_free (title); } return result_list; } ///////////////////////////////// MetaDataSource lyrics_chartlyrics_src = { .name = "chartlyrics", .key = 'c', .parser = lyrics_chartlyrics_parse, .get_url = lyrics_chartlyrics_url, .type = GLYR_GET_LYRICS, .quality = 75, .speed = 25, .endmarker = NULL, .free_url = false }; glyr-1.0.10/lib/intern/lyrics/elyrics.c000066400000000000000000000113101301162614000177240ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" #include "../../stringlib.h" /* Simple URL replace scheme - works pretty good here */ #define ELYRICS_BASE_URL "http://www.elyrics.net/read/%c/%s-lyrics/%s-lyrics.html" static const gchar * lyrics_elyrics_url (GlyrQuery * settings) { gchar * result_url = NULL; gchar * space_to_min_artist = strreplace (settings->artist," ","-"); gchar * space_to_min_title = strreplace (settings->title, " ","-"); if (space_to_min_artist && space_to_min_title) { gchar * prep_title = NULL, * prep_artist = NULL; if (settings->normalization & GLYR_NORMALIZE_ARTIST) prep_title = prepare_string (space_to_min_title, settings->normalization,TRUE); else prep_title = prepare_string (space_to_min_title, GLYR_NORMALIZE_NONE,TRUE); if (settings->normalization & GLYR_NORMALIZE_ARTIST) prep_artist = prepare_string (space_to_min_artist, settings->normalization,TRUE); else prep_artist = prepare_string (space_to_min_artist, GLYR_NORMALIZE_NONE,TRUE); if (prep_title && prep_artist) { result_url = g_strdup_printf (ELYRICS_BASE_URL,prep_artist[0],prep_artist,prep_title); g_free (prep_title); g_free (prep_artist); } g_free (space_to_min_artist); g_free (space_to_min_title); } return result_url; } ///////////////////////////////// /* Neat try elyrics to vary this string with every request :-) */ #define FROM_MIDDLE "http://www.elyrics.net" #define FROM_END "

" #define BAD_STRING "Lyrics removed for copyright protection!" /* This data is separated from the actual lyrics => remove it from here */ static void remove_from_from_string (gchar * string) { gchar * from_middle = strstr (string,FROM_MIDDLE); if (from_middle != NULL) { gchar * from_end = strstr (from_middle,FROM_END); if (from_end != NULL) { gchar * from_start = from_middle; while (from_start[0] && from_start[0] != '>') { from_start--; } if (from_start != NULL) { gsize memlen = from_end - from_start; memset (from_start,' ',memlen); } } } } ///////////////////////////////// #define LYRICS_BEGIN "
" #define LYRICS_ALT_END "these lyrics are submitted by" #define LYRICS_END "
" static GList * lyrics_elyrics_parse (cb_object * capo) { GList * results = NULL; gchar * lyrics_begin = strstr (capo->cache->data,LYRICS_BEGIN); if (lyrics_begin != NULL) { if (g_strstr_len (lyrics_begin,250,BAD_STRING) == NULL) { gchar * lyrics_end = strstr (lyrics_begin,LYRICS_ALT_END); if (lyrics_end == NULL) { lyrics_end = strstr (lyrics_begin,LYRICS_END); } if (lyrics_end != NULL) { /* Modifying original buffer is allowed * As long it's not saved in the result cache * */ lyrics_end[0] = '\0'; GlyrMemCache * item = DL_init(); remove_from_from_string (lyrics_begin); item->data = g_strdup (lyrics_begin); item->size = lyrics_end - lyrics_begin; results = g_list_prepend (results,item); } } } return results; } ///////////////////////////////// MetaDataSource lyrics_elyrics_src = { .name = "elyrics", .key = 'e', .encoding = "LATIN1", .parser = lyrics_elyrics_parse, .get_url = lyrics_elyrics_url, .type = GLYR_GET_LYRICS, .endmarker = NULL, .quality = 75, .speed = 75, .free_url = true }; glyr-1.0.10/lib/intern/lyrics/lipwalk.c000066400000000000000000000127711301162614000177310ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define TRACK_BEGIN "
  • " #define TRACK_DESCR "" #define LIPWALK_DOMAIN "http://www.lipwalklyrics.com" #define LIPWALK_URL LIPWALK_DOMAIN"/component/lyrics/search/index.php?search=${artist}%20${title}" #define START "
    " #define END "
    " #define IS_ON_SEARCH_PAGE "Search results for" static const gchar * lyrics_lipwalk_url (GlyrQuery * settings) { return LIPWALK_URL; } ///////////////////////////////// static GlyrMemCache * parse_lyrics_page (GlyrMemCache * cache) { gchar * start = NULL; gchar * end = NULL; gchar * content = NULL; GlyrMemCache * result_cache = NULL; if (cache && (start = strstr (cache->data,START) ) != NULL) { start += (sizeof START) - 1; if (start && (end = strstr (start + (sizeof START) ,END) ) != NULL) { if (ABS (end-start) > 35) { * (end) = 0; content = strreplace (start,"<br />",NULL); if (content != NULL) { result_cache = DL_init(); result_cache->data = content; result_cache->size = strlen (content); result_cache->dsrc = g_strdup (cache->dsrc); } } } } return result_cache; } ///////////////////////////////// static gboolean validate_track_description (GlyrQuery * query, gchar * description) { gboolean result = FALSE; if (description != NULL) { gchar ** splitv = g_strsplit (description," - ",0); if (splitv != NULL) { if (splitv[0] && splitv[1] != NULL) { if (levenshtein_strnormcmp (query,query->artist,splitv[0]) <= query->fuzzyness && levenshtein_strnormcmp (query,query->title, splitv[1]) <= query->fuzzyness) { result = TRUE; } } g_strfreev (splitv); } } return result; } ///////////////////////////////// static GList * lyrics_lipwalk_parse (cb_object *capo) { GList * result_list = NULL; if (strstr (capo->cache->data,IS_ON_SEARCH_PAGE) == NULL) { GlyrMemCache * result_cache = parse_lyrics_page (capo->cache); result_list = g_list_prepend (result_list,result_cache); } else /* Oops, we're on the search results page, things are complicated now */ { /* Happens with "In Flames" - "Trigger" e.g. */ gchar * search_node = capo->cache->data; gsize track_len = (sizeof TRACK_BEGIN) - 1; while (continue_search (g_list_length (result_list),capo->s) && (search_node = strstr (search_node + track_len,TRACK_BEGIN) ) ) { search_node += track_len; gchar * track_end = strstr (search_node,TRACK_ENDIN); if (track_end != NULL) { gchar * lyrics_url = copy_value (search_node,track_end); if (lyrics_url != NULL) { track_end += (sizeof TRACK_ENDIN) - 1; gchar * track_descr = copy_value (track_end,strstr (track_end,TRACK_DESCR) ); if (track_descr != NULL && validate_track_description (capo->s,track_descr) == TRUE) { gchar * full_url = g_strdup_printf ("%s%s",LIPWALK_DOMAIN,lyrics_url); GlyrMemCache * lyrics_page = download_single (full_url,capo->s,NULL); if (lyrics_page != NULL) { GlyrMemCache * result_cache = parse_lyrics_page (lyrics_page); if (result_cache != NULL) { result_list = g_list_prepend (result_list,result_cache); } DL_free (lyrics_page); } g_free (track_descr); g_free (full_url); } g_free (lyrics_url); } } } } return result_list; } ///////////////////////////////// MetaDataSource lyrics_lipwalk_src = { .name = "lipwalk", .key = 'z', .parser = lyrics_lipwalk_parse, .get_url = lyrics_lipwalk_url, .type = GLYR_GET_LYRICS, .quality = 80, .speed = 60, .endmarker = NULL, .free_url = false }; �������glyr-1.0.10/lib/intern/lyrics/lyrdb.c���������������������������������������������������������������0000664�0000000�0000000�00000006053�13011626140�0017376�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define LYRDB_URL "http://webservices.lyrdb.com/lookup.php?q=${artist}|${title}&for=match&agent=libglyr" ///////////////////////////////// static const char * lyrics_lyrdb_url (GlyrQuery * settings) { return LYRDB_URL; } ///////////////////////////////// static GList * lyrics_lyrdb_parse (cb_object * capo) { gchar *slash = NULL; GList * result_list = NULL; if ( (slash = strchr (capo->cache->data,'\\') ) != NULL) { gchar * uID = copy_value (capo->cache->data,slash); if (uID != NULL) { gchar * lyr_url = g_strdup_printf ("http://webservices.lyrdb.com/getlyr.php?q=%s",uID); if (lyr_url != NULL) { GlyrMemCache * new_cache = download_single (lyr_url,capo->s,NULL); if (new_cache != NULL) { gsize i = 0; gchar * buffer = g_malloc0 (new_cache->size + 1); for (i = 0; i < new_cache->size; i++) { buffer[i] = (new_cache->data[i] == '\r') ? ' ' : new_cache->data[i]; } buffer[i] = 0; if (i != 0) { GlyrMemCache * result = DL_init(); result->data = buffer; result->size = i; result->dsrc = g_strdup (lyr_url); result_list = g_list_prepend (result_list,result); } DL_free (new_cache); } g_free (lyr_url); } g_free (uID); } } return result_list; } ///////////////////////////////// MetaDataSource lyrics_lyrdb_src = { .name = "lyrdb", .key = 'd', .encoding = "LATIN1", .parser = lyrics_lyrdb_parse, .get_url = lyrics_lyrdb_url, .type = GLYR_GET_LYRICS, .endmarker = NULL, .quality = 75, .speed = 75, .free_url = false }; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/lyrics/lyricsreg.c�����������������������������������������������������������0000664�0000000�0000000�00000004604�13011626140�0020265�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define INFO_BEGIN "</div><div style=\"text-align:center;\">" #define INFO_ENDIN " <a href=\"" static const char * lyrics_lyricsreg_url (GlyrQuery * s) { return "http://www.lyricsreg.com/lyrics/${artist}/${title}/"; } static GList * lyrics_lyricsreg_parse (cb_object * capo) { GList * result_list = NULL; gchar * start = strstr (capo->cache->data, INFO_BEGIN); if (start != NULL) { start += (sizeof INFO_BEGIN) - 1; gchar * end = strstr (start,INFO_ENDIN); if (end != NULL) { * (end) = 0; gchar * no_br_tags = strreplace (start,"<br />",NULL); if (no_br_tags != NULL) { GlyrMemCache * tmp = DL_init(); tmp->data = beautify_string (no_br_tags); tmp->size = tmp->data ? strlen (tmp->data) : 0; g_free (no_br_tags); if (tmp->data != NULL) { result_list = g_list_prepend (result_list,tmp); } } } } return result_list; } ///////////////////////////////// MetaDataSource lyrics_lyricsreg_src = { .name = "lyricsreg", .key = 'r', .parser = lyrics_lyricsreg_parse, .get_url = lyrics_lyricsreg_url, .type = GLYR_GET_LYRICS, .quality = 42, .speed = 90, .endmarker = NULL, .free_url = false }; ����������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/lyrics/lyricstime.c����������������������������������������������������������0000664�0000000�0000000�00000012631�13011626140�0020445�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define GLYR_GET_URL "http://www.lyricstime.com/search/?q=${artist}+${title}&t=default" ///////////////////////////////// static const char * lyrics_lyricstime_url (GlyrQuery * settings) { return GLYR_GET_URL; } ///////////////////////////////// #define LYR_BEGIN "<div id=\"songlyrics\" >" #define LYR_ENDIN "</div>" static GlyrMemCache * parse_page (GlyrMemCache * dl, cb_object * capo) { GlyrMemCache * result = NULL; if (dl != NULL) { gchar * begin = strstr (dl->data,LYR_BEGIN); if (begin != NULL) { begin += (sizeof LYR_BEGIN) - 1; gchar * end = strstr (begin,LYR_ENDIN); if (end != NULL) { * (end) = 0; gchar * no_br_tags = strreplace (begin,"<br />",NULL); if (no_br_tags != NULL) { result = DL_init(); result->data = beautify_string (no_br_tags); result->size = (result->data) ? strlen (result->data) : 0; result->dsrc = g_strdup (dl->dsrc); g_free (no_br_tags); } } } } return result; } ///////////////////////////////// #define START_SEARCH "<div id=\"searchresult\">" #define SEARCH_ENDIN "</div>" #define NODE_BEGIN "<li><a href=\"" #define NODE_ENDIN "\">" #define SPAN_BEGIN "<span class" #define ARTIST_BEG "<b>" #define ARTIST_END "</b>" static gboolean validate_artist (cb_object * capo, gchar * backpointer) { gboolean i_shall_continue = false; if (backpointer != NULL) { char * span = strstr (backpointer,SPAN_BEGIN); if (span != NULL) { gchar * artist_beg = strstr (span,ARTIST_BEG); if (artist_beg != NULL) { artist_beg += (sizeof ARTIST_BEG) - 1; gchar * artist_end = strstr (artist_beg,ARTIST_END); if (artist_end != NULL) { gchar * artist_val = copy_value (artist_beg,artist_end); if (artist_val != NULL) { if (levenshtein_strnormcmp (capo->s,artist_val,capo->s->artist) <= capo->s->fuzzyness) { i_shall_continue = true; } g_free (artist_val); } } } } } return i_shall_continue; } ///////////////////////////////// static GList * lyrics_lyricstime_parse (cb_object * capo) { GList * rList = NULL; char * start = capo->cache->data; if (start != NULL) { gchar * div_end = strstr (start,SEARCH_ENDIN); gchar * node = capo->cache->data; gchar * backpointer = node; gsize nlen = (sizeof NODE_BEGIN) - 1; while (continue_search (g_list_length (rList),capo->s) && (node = strstr (node+nlen,NODE_BEGIN) ) != NULL) { if (div_end >= node) break; if (validate_artist (capo,backpointer) == TRUE) { gchar * end_of_url = strstr (node+nlen,NODE_ENDIN); if (end_of_url != NULL) { gchar * url = copy_value (node+nlen,end_of_url); if (url != NULL) { gchar * full_url = g_strdup_printf ("http://www.lyricstime.com%s",url); GlyrMemCache * dl_cache = download_single (full_url,capo->s,NULL); if (dl_cache) { GlyrMemCache * parsed_cache = parse_page (dl_cache,capo); if (parsed_cache != NULL) { rList = g_list_prepend (rList,parsed_cache); } DL_free (dl_cache); g_free (full_url); } g_free (url); } } } backpointer = node; } } return rList; } ///////////////////////////////// MetaDataSource lyrics_lyricstime_src = { .name = "lyricstime", .key = 'y', .parser = lyrics_lyricstime_parse, .get_url = lyrics_lyricstime_url, .type = GLYR_GET_LYRICS, .quality = 70, .speed = 60, .endmarker = NULL, .free_url = false }; �������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/lyrics/lyricsvip.c�����������������������������������������������������������0000664�0000000�0000000�00000005772�13011626140�0020315�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define LV_URL "http://www.lyricsvip.com/%s/%s-Lyrics.html" ///////////////////////////////// static const gchar * lyrics_lyricsvip_url (GlyrQuery * settings) { gchar * result = NULL; gchar * artist_clean = strreplace (settings->artist, " ", "-"); if (artist_clean != NULL) { gchar * title_clean = strreplace (settings->title, " ", "-"); if (title_clean != NULL) { gchar * prep_artist = curl_easy_escape (NULL,artist_clean,0); gchar * prep_title = curl_easy_escape (NULL,title_clean,0); result = g_strdup_printf (LV_URL, prep_artist, prep_title); g_free (title_clean); curl_free (prep_artist); curl_free (prep_title); } g_free (artist_clean); } return result; } ///////////////////////////////// #define BEG "<img src=\"http://www.lyricsvip.com/images/phone2.gif\" alt=\"phone\" /></div>" #define END "<br />\n<div class=\"ad\">" static GList * lyrics_lyricsvip_parse (cb_object *capo) { gchar * start = NULL; gchar * end = NULL; gchar * content = NULL; GList * result_list = NULL; if ( (start = strstr (capo->cache->data,BEG) ) != NULL) { if ( (end = strstr (start,END) ) != NULL) { if (ABS (end-start) > 0) { * (end) = 0; content = strreplace (start,"<br />",""); if (content) { GlyrMemCache * result = DL_init(); result->data = content; result->size = strlen (content); result_list = g_list_prepend (result_list,result); } } } } return result_list; } ///////////////////////////////// MetaDataSource lyrics_lyricsvip_src = { .name = "lyricsvip", .key = 'v', .parser = lyrics_lyricsvip_parse, .get_url = lyrics_lyricsvip_url, .type = GLYR_GET_LYRICS, .quality = 60, .speed = 85, .free_url = true }; ������glyr-1.0.10/lib/intern/lyrics/lyricswiki.c����������������������������������������������������������0000664�0000000�0000000�00000013027�13011626140�0020452�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define BAD_STRING "Special:Random" /* This has been a running gag during developement: "I want to edit metadata!" */ #define EXTERNAL_LINKS "<span class=\"plainlinks\"" #define LW_URL "http://lyrics.wikia.com/api.php?action=lyrics&fmt=xml&func=getSong&artist=${artist}&song=${title}" #define NOT_FOUND "<lyrics>Not found</lyrics>" ///////////////////////////////// static const gchar * lyrics_lyricswiki_url (GlyrQuery * settings) { return LW_URL; } ///////////////////////////////// // Compare response, so lyricswiki's search did not fool us static gboolean lv_cmp_content (const gchar * to_artist, const gchar * to_title, cb_object * capo) { gboolean res = false; if (to_artist && to_title && capo) { gchar * tmp_artist = copy_value (to_artist,strstr (to_artist,"</artist>") ); if (tmp_artist != NULL) { gchar * tmp_title = copy_value (to_title, strstr (to_title ,"</song>" ) ); if (tmp_title != NULL) { /* levenshtein_strnormcmp takes care of those brackets */ if ( (levenshtein_strnormcmp (capo->s,capo->s->artist,tmp_artist) <= capo->s->fuzzyness && levenshtein_strnormcmp (capo->s,capo->s->title, tmp_title) <= capo->s->fuzzyness ) ) { res = true; } g_free (tmp_title); } g_free (tmp_artist); } } return res; } ///////////////////////////////// #define LYR_NODE "<div class='lyricbox" #define LYR_BEGIN ">" #define LYR_ENDIN "<!--" #define LYR_FOOTER "<div id=\"songfooter" #define LYR_CREDITS "<table" #define LYR_INSTRUMENTAL "/Category:Instrumental" #define LYR_SCRIPT_TAG "</script" GList * parse_result_page (GlyrQuery * query, GlyrMemCache * to_parse) { GList * result_list = NULL; gchar * node = to_parse->data; while (continue_search (g_list_length (result_list),query) && (node = strstr (node,LYR_NODE))) { node += (sizeof LYR_NODE); char *script_tag = strstr(node, LYR_SCRIPT_TAG); char *end_tag = strstr(node, LYR_ENDIN); char *footer_tag = strstr(node, LYR_FOOTER); char *credits_tag = strstr(node, LYR_CREDITS); char *ending_tag = LYR_ENDIN; if (footer_tag && footer_tag < end_tag) { ending_tag = LYR_FOOTER; } if (credits_tag && credits_tag < end_tag) { ending_tag = LYR_CREDITS; } if(script_tag && script_tag < end_tag) { node = script_tag + sizeof(LYR_SCRIPT_TAG) - 1; } bool is_instrumental = strstr(node, LYR_INSTRUMENTAL) != NULL; gchar * lyr = get_search_value (node,LYR_BEGIN,ending_tag); gchar * beautiness_test = beautify_string (lyr); if (is_instrumental || (beautiness_test != NULL && beautiness_test[0])) { if (is_instrumental || (lyr != NULL && strstr (lyr,BAD_STRING) == NULL && strstr (lyr,EXTERNAL_LINKS) == NULL)) { GlyrMemCache * result = DL_init(); if(is_instrumental) result->data = g_strdup("Instrumental"); else result->data = lyr; result->size = strlen (result->data); result->dsrc = g_strdup (to_parse->dsrc); result_list = g_list_prepend (result_list,result); } } else { g_free (lyr); } g_free (beautiness_test); } return result_list; } ///////////////////////////////// static GList * lyrics_lyricswiki_parse (cb_object * capo) { GList * result_list = NULL; if (strstr (capo->cache->data,NOT_FOUND) == NULL && lv_cmp_content (strstr (capo->cache->data,"<artist>"),strstr (capo->cache->data,"<song>"),capo) ) { gchar * wiki_page_url = get_search_value (capo->cache->data,"<url>","</url>"); if (wiki_page_url != NULL) { GlyrMemCache * new_cache = download_single (wiki_page_url,capo->s,NULL); if (new_cache != NULL) { result_list = parse_result_page (capo->s,new_cache); DL_free (new_cache); } g_free (wiki_page_url); } } return result_list; } ///////////////////////////////// MetaDataSource lyrics_lyricswiki_src = { .name = "lyricswiki", .key = 'w', .parser = lyrics_lyricswiki_parse, .get_url = lyrics_lyricswiki_url, .type = GLYR_GET_LYRICS, .quality = 95, .speed = 75, .endmarker = NULL, .free_url = false }; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glyr-1.0.10/lib/intern/lyrics/lyrix_at.c������������������������������������������������������������0000664�0000000�0000000�00000010663�13011626140�0020117�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define AT_URL "https://www.lyrix.at/lyrics-search/s-${artist},,${title},,any,1321,0.html" const char * lyrics_lyrixat_url (GlyrQuery * settings) { return AT_URL; } ///////////////////////////////// #define SEARCH_START_TAG "<!-- start of result item //-->" #define LYRIC_BEGIN "<div class='songtext' id='stextDIV'>" #define URL_TAG_BEGIN "<a href='/t/" #define URL_TAG_ENDIN "'>" #define TITLE_END "<" #define MAX_TRIES 5 ///////////////////////////////// static void parse_lyrics_page (const gchar * url, GList ** result_list, cb_object * capo) { GlyrMemCache * lyrcache = download_single (url,capo->s,"<!-- eBay Relevance Ad -->"); if (lyrcache != NULL) { gchar * lyr_begin = strstr (lyrcache->data,LYRIC_BEGIN); if (lyr_begin != NULL) { gchar * lyr_endin = strstr (lyr_begin,"</div>"); if (lyr_endin != NULL) { gchar * lyrics = copy_value (lyr_begin,lyr_endin); if (lyrics != NULL) { GlyrMemCache * result = DL_init(); result->data = strreplace (lyrics,"<br />",""); result->size = strlen (result->data); result->dsrc = g_strdup (url); *result_list = g_list_prepend (*result_list,result); } g_free (lyrics); } } DL_free (lyrcache); } } ///////////////////////////////// GList * lyrics_lyrixat_parse (cb_object * capo) { /* lyrix.at does not offer any webservice -> use the searchfield to get some results */ GList * result_list = NULL; gchar * search_begin_tag = capo->cache->data; gint ctr = 0; while (continue_search (g_list_length (result_list),capo->s) && (search_begin_tag = strstr (search_begin_tag+1,SEARCH_START_TAG) ) && MAX_TRIES >= ctr++) { gchar * url_tag = search_begin_tag; url_tag = strstr (url_tag,URL_TAG_BEGIN); if (url_tag != NULL) { gchar * title_tag = strstr (url_tag,URL_TAG_ENDIN); if (title_tag) { gchar * title_end = strstr (title_tag,TITLE_END); if (title_end != NULL) { gsize tag_end_len = (sizeof URL_TAG_ENDIN) - 1; gchar * title = copy_value (title_tag + tag_end_len,title_end); if (title != NULL) { if (levenshtein_strnormcmp (capo->s,title,capo->s->title) <= capo->s->fuzzyness) { gchar * url_part = copy_value (url_tag+strlen (URL_TAG_BEGIN),title_tag); if (url_part != NULL) { gchar * url = g_strdup_printf ("https://www.lyrix.at/t/%s",url_part); parse_lyrics_page (url,&result_list,capo); g_free (url); g_free (url_part); } } g_free (title); } } } } } return result_list; } ///////////////////////////////// MetaDataSource lyrics_lyrix_src = { .name = "lyrix", .key = 'a', .parser = lyrics_lyrixat_parse, .get_url = lyrics_lyrixat_url, .type = GLYR_GET_LYRICS, .quality = 70, .speed = 50, .free_url = false }; �����������������������������������������������������������������������������glyr-1.0.10/lib/intern/lyrics/magistrix.c�����������������������������������������������������������0000664�0000000�0000000�00000010543�13011626140�0020270�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see <http://www.gnu.org/licenses/>. **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define MG_URL "http://www.magistrix.de/lyrics/search?q=${artist}+${title}" static const char * lyrics_magistrix_url (GlyrQuery * settings) { return MG_URL; } /////////////////////////////////// static GlyrMemCache * parse_lyric_page (GlyrMemCache * cache) { GlyrMemCache * result = NULL; g_return_val_if_fail (cache,NULL); g_return_val_if_fail (cache->data,NULL); gchar * data = get_search_value (cache->data,"id='songtext'>","<div class='lyric-actions'>"); if (data != NULL) { result = DL_init(); result->data = data; result->size = strlen (data); result->dsrc = g_strdup (cache->dsrc); } return result; } /////////////////////////////////// #define SEARCH_FIRST_RESULT "<table class='searchresult'>" #define SEARCH_LAST_RESULT "</table>" #define SEARCH_NODE "<div class='title'>" #define SEARCH_LINK_START "–\n<a href=\"" #define SEARCH_LINK_END "\" class" static GList * parse_search_result_page (cb_object * capo) { GList * result_list = NULL; char * first_result = strstr (capo->cache->data, SEARCH_FIRST_RESULT); if (first_result != NULL) { char * end_of_results = strstr (first_result + sizeof (SEARCH_FIRST_RESULT), SEARCH_LAST_RESULT); if (end_of_results) { char * node = first_result; while ( (node = strstr (node + sizeof (SEARCH_NODE), SEARCH_NODE) ) && continue_search (g_list_length (result_list), capo->s) ) { char * new_url = get_search_value (node, SEARCH_LINK_START, SEARCH_LINK_END); if (new_url != NULL) { char * full_url = g_strdup_printf ("www.magistrix.de%s", new_url); GlyrMemCache * lyrics_page = download_single (full_url, capo->s, NULL); if (lyrics_page) { GlyrMemCache * item = parse_lyric_page (lyrics_page); if (item != NULL) { result_list = g_list_prepend (result_list, item); } DL_free (lyrics_page); } g_free (new_url); g_free (full_url); } } } } return result_list; } /////////////////////////////////// static GList * lyrics_magistrix_parse (cb_object * capo) { GList * result_list = NULL; if (strstr (capo->cache->data,"Es wurden keine Songtexte gefunden") == NULL) /* "No songtext" page? */ { if (strstr (capo->cache->data,"<title>Songtext-Suche") == NULL) /* Are we not on the search result page? */ { GlyrMemCache * result = parse_lyric_page (capo->cache); if (result != NULL) { result_list = g_list_prepend (result_list,result); } } else { /* Parse Searchresult page */ result_list = parse_search_result_page (capo); } } return result_list; } /////////////////////////////////// MetaDataSource lyrics_magistrix_src = { .name = "magistrix", .key = 'x', .parser = lyrics_magistrix_parse, .get_url = lyrics_magistrix_url, .type = GLYR_GET_LYRICS, .quality = 60, .speed = 75, .endmarker = NULL, .free_url = false }; glyr-1.0.10/lib/intern/lyrics/metallum.c000066400000000000000000000055541301162614000201070ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a command-line tool and library to download various sort of music related metadata. * + Copyright (C) [2011-2016] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" #include "../../stringlib.h" #define AJAX_URL "http://www.metal-archives.com/search/ajax-advanced/searching/songs/" \ "?songTitle=${title}&bandName=${artist}" \ "&_=1313668588182&sEcho=1&iColumns=5&sColumns=&iDisplayStart=0" \ "&iDisplayLength=100&sNames=%%2C%%2C%%2C%%2C" #define SUBST_URL "http://www.metal-archives.com/release/ajax-view-lyrics/id/%s" #define BAD_STRING "(lyrics not available)" ///////////////////////////////// static const gchar * lyrics_metallum_url (GlyrQuery * s) { return AJAX_URL; } ///////////////////////////////// #define ID_START "id=\\\"lyricsLink_" #define ID_END "\\\"" ///////////////////////////////// static GList * lyrics_metallum_parse (cb_object * capo) { GList * result_items = NULL; gchar * id_start = strstr (capo->cache->data,ID_START); if (id_start != NULL) { id_start += strlen (ID_START); gchar * ID_string = copy_value (id_start,strstr (id_start,ID_END) ); if (ID_string != NULL) { gchar * content_url = g_strdup_printf (SUBST_URL,ID_string); if (content_url != NULL) { GlyrMemCache * content_cache = download_single (content_url,capo->s,NULL); if (content_cache != NULL && strstr (content_cache->data,BAD_STRING) == NULL) { result_items = g_list_prepend (result_items, content_cache); } g_free (content_url); } g_free (ID_string); } } return result_items; } ///////////////////////////////// MetaDataSource lyrics_metallum_src = { .name = "metallum", .key = 'u', .parser = lyrics_metallum_parse, .get_url = lyrics_metallum_url, .endmarker = NULL, .quality = 55, .speed = 70, .free_url = false, .type = GLYR_GET_LYRICS }; glyr-1.0.10/lib/intern/lyrics/metrolyrics.c000066400000000000000000000130151301162614000206320ustar00rootroot00000000000000/*********************************************************** * This file is part of glyr * + a commnadline tool and library to download various sort of music related metadata. * + Copyright (C) [2011] [Christopher Pahl] * + Hosted at: https://github.com/sahib/glyr * * glyr is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glyr 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with glyr. If not, see . **************************************************************/ #include "../../core.h" #include "../../stringlib.h" // Search URL #define ML_URL "http://www.metrolyrics.com/search.php?search=${artist}+${title}&category=artisttitle" #define MAX_TRIES 5 // Just return URL static const gchar * lyrics_metrolyrics_url (GlyrQuery * settings) { return ML_URL; } /////////////////////////////////// static void replace_from_message_inline (gchar * text) { if (text != NULL) { gchar * from_msg_start = strstr (text,"[ From: "); if (from_msg_start != NULL) { while (from_msg_start[0] != '\n' && from_msg_start[0]) { from_msg_start[0] = ' '; from_msg_start++; } if (from_msg_start[0] == '\n') { from_msg_start[0] = ' '; } } } } /////////////////////////////////// #define LYRICS_DIV "
    " #define LYRICS_END "
    " static GlyrMemCache * parse_lyrics_page (const gchar * buffer) { GlyrMemCache * result = NULL; if (buffer != NULL) { gchar * begin = strstr (buffer,LYRICS_DIV); if (begin != NULL) { gchar * end = strstr (begin,LYRICS_END); if (end != NULL) { gchar * lyr = copy_value (begin,end); if (lyr != NULL) { result = DL_init(); replace_from_message_inline (lyr); result->data = lyr; result->size = strlen (result->data); } } } } return result; } /////////////////////////////////// //#define ROOT_NODE "
    " #define ROOT_NODE "