4 #include <libxml/parser.h>
5 #include <libxml/tree.h>
6 #include <libmpd/debug_printf.h>
7 #include <gmpc/plugin.h>
8 #include <gmpc/gmpc_easy_download.h>
10 #define AMAZONKEY "14TC04B24356BPHXW1R2"
13 static char * host
= "http://ecs.amazonaws.%s/onca/xml?Service=AWSECommerceService&Operation=ItemSearch&SearchIndex=Music&ResponseGroup=Images,EditorialReview&SubscriptionId=%s&Artist=%s&%s=%s";
14 static char* search_types
[] = {"Title", "Keywords"};
15 static void amazon_cover_art_pref_construct(GtkWidget
*container
);
16 static void amazon_cover_art_pref_destroy(GtkWidget
*container
);
17 static int fetch_cover_art(mpd_Song
*song
,int type
, char **url
);
18 static int fetch_cover_priority();
20 static int shrink_string(gchar
*string
, int start
, int end
);
22 static int fetch_metadata(mpd_Song
*song
,MetaDataType type
, char **path
);
24 static void amazon_set_enabled(int enabled
);
25 static int amazon_get_enabled();
28 static char *endpoints
[ENDPOINTS
][2] =
30 {"com", "United States"},
31 {"co.uk", "United Kingdom"},
38 static GtkWidget
*wp_pref_vbox
= NULL
;
39 gmpcPrefPlugin cam_pref
= {
40 amazon_cover_art_pref_construct
,
41 amazon_cover_art_pref_destroy
44 typedef struct amazon_song_info
{
52 gmpcMetaDataPlugin cam_cover
= {
57 int plugin_api_version
= PLUGIN_API_VERSION
;
59 "Amazon Cover Fetcher",
60 {PLUGIN_MAJOR_VERSION
,PLUGIN_MINOR_VERSION
,PLUGIN_MICRO_VERSION
},
61 GMPC_PLUGIN_META_DATA
,
67 NULL
, /* status changed */
68 NULL
, /* connection changed */
69 &cam_pref
, /* preferences */
70 &cam_cover
, /** Metadata */
75 /* Convert string to the wonderful % notation for url*/
76 static char * __cover_art_process_string(const gchar
*string
)
78 #define ACCEPTABLE(a) (((a) >= 'a' && (a) <= 'z') || ((a) >= 'A' && (a) <= 'Z') || ((a) >= '0' && (a) <= '9'))
80 const gchar hex
[16] = "0123456789ABCDEF";
85 gint unacceptable
= 0;
94 new_string
= g_malloc(len
+ 1);
96 /* Get count of chars that will need to be converted to %
97 and remove ([{}]) and everything between */
98 for (p
= string
; *p
!= '\0'; p
++) {
101 if(c
== '(' || c
== '[' || c
== '{') {
103 } else if(c
== ')' || c
== ']' || c
== '}') {
107 } else if(depth
== 0) {
117 new_string
[i
] = '\0';
119 len
= strlen(new_string
);
121 /* remove double spaces from the string because removing ([{}])
122 tends to create those */
123 for(p
= new_string
+ 1; *p
!= '\0'; p
++) {
128 len
= shrink_string(new_string
, p
- new_string
, len
);
134 /* make sure first char isn't a space */
135 if(new_string
[0] == ' ')
136 len
= shrink_string(new_string
, 0, len
);
138 /* make sure there isn't a trailing space*/
139 if(new_string
[len
- 1] == ' ')
142 new_string
[len
] = '\0';
144 result
= g_malloc (len
+ unacceptable
* 2 + 1);
146 /*time to create the escaped string*/
147 for (q
= result
, p
= new_string
; *p
!= '\0'; p
++)
151 if (!ACCEPTABLE (c
)) {
152 *q
++ = '%'; /* means hex coming */
170 static int amazon_get_enabled()
172 return cfg_get_single_value_as_int_with_default(config
, "cover-amazon", "enable", TRUE
);
174 static void amazon_set_enabled(int enabled
)
176 cfg_set_single_value_as_int(config
, "cover-amazon", "enable", enabled
);
181 char *file
= gmpc_get_covers_path(NULL
);
182 if(!g_file_test(file
,G_FILE_TEST_EXISTS
))
189 static int fetch_cover_priority(){
190 return cfg_get_single_value_as_int_with_default(config
, "cover-amazon", "priority", 80);
194 static int fetch_metadata(mpd_Song
*song
,MetaDataType type
, char **path
)
200 if(song
->artist
== NULL
|| song
->album
== NULL
)
202 return META_DATA_UNAVAILABLE
;
204 if(type
!= META_ALBUM_ART
&& type
!= META_ALBUM_TXT
) {
205 return META_DATA_UNAVAILABLE
;
207 /* Always fetch it. */
208 fetch_cover_art(song
,type
, &url
);
211 return META_DATA_AVAILABLE
;
215 return META_DATA_UNAVAILABLE
;
218 static amazon_song_info
* amazon_song_info_new()
220 amazon_song_info
*asi
= g_malloc(sizeof(amazon_song_info
));
221 asi
->image_big
= NULL
;
222 asi
->image_medium
= NULL
;
223 asi
->image_small
= NULL
;
224 asi
->album_info
= NULL
;
227 static void amazon_song_info_free(amazon_song_info
*asi
)
229 if(asi
->image_big
!= NULL
) g_free(asi
->image_big
);
230 if(asi
->image_medium
!= NULL
) g_free(asi
->image_medium
);
231 if(asi
->image_small
!= NULL
) g_free(asi
->image_small
);
232 if(asi
->album_info
!= NULL
) g_free(asi
->album_info
);
237 static xmlNodePtr
get_first_node_by_name(xmlNodePtr xml
, gchar
*name
) {
239 xmlNodePtr c
= xml
->xmlChildrenNode
;
241 if(xmlStrEqual(c
->name
, (xmlChar
*) name
))
248 static amazon_song_info
*__cover_art_xml_get_image(char *data
,int size
)
250 xmlDocPtr doc
= xmlParseMemory(data
,size
);
253 xmlNodePtr root
= xmlDocGetRootElement(doc
);
254 xmlNodePtr cur
= get_first_node_by_name(root
, "Items");
255 amazon_song_info
*asi
= NULL
;
257 cur
= get_first_node_by_name(cur
, "Item");
259 xmlNodePtr child
= NULL
;
260 asi
= amazon_song_info_new();
261 if(child
= get_first_node_by_name(cur
, "LargeImage")) {
262 xmlChar
*temp
= xmlNodeGetContent(get_first_node_by_name(child
, "URL"));
263 /* copy it, so we can free it, and don't need xmlFree */
264 asi
->image_big
= g_strdup((char *)temp
);
267 if(child
= get_first_node_by_name(cur
, "MediumImage")){
268 xmlChar
*temp
= xmlNodeGetContent(get_first_node_by_name(child
, "URL"));
269 asi
->image_medium
= g_strdup((char *)temp
);
272 if(child
= get_first_node_by_name(cur
, "SmallImage")){
273 xmlChar
*temp
= xmlNodeGetContent(get_first_node_by_name(child
, "URL"));
274 asi
->image_small
= g_strdup((char *)temp
);
278 if (child
= get_first_node_by_name(cur
, "EditorialReviews")) {
279 child
= get_first_node_by_name(child
, "EditorialReview");
281 xmlChar
*temp
= xmlNodeGetContent(get_first_node_by_name(child
, "Content")); /* ugy, lazy */
282 asi
->album_info
= g_strdup((char *)temp
);
296 static int __fetch_metadata_amazon(char *stype
, char *nartist
, char *nalbum
,int type
, char **url
)
299 gmpc_easy_download_struct data
= {NULL
, 0,-1, NULL
, NULL
};
302 int endpoint
= cfg_get_single_value_as_int_with_default(config
, "cover-amazon", "location", 0);
303 char *endp
= endpoints
[endpoint
][0];
305 debug_printf(DEBUG_INFO
, "search-type: %s\n", stype
);
306 snprintf(furl
,1024, host
,endp
, AMAZONKEY
, nartist
, stype
, nalbum
);
307 if(gmpc_easy_download(furl
, &data
))
309 amazon_song_info
*asi
= __cover_art_xml_get_image(data
.data
, data
.size
);
310 gmpc_easy_download_clean(&data
);
313 if(type
&META_ALBUM_ART
)
315 debug_printf(DEBUG_INFO
, "Trying to fetch album art");
316 gmpc_easy_download(asi
->image_big
, &data
);
317 if(data
.size
<= 900){
318 gmpc_easy_download_clean(&data
);
319 gmpc_easy_download(asi
->image_medium
, &data
);
320 if(data
.size
<= 900){
321 gmpc_easy_download_clean(&data
);
322 gmpc_easy_download(asi
->image_small
, &data
);
325 gmpc_easy_download_clean(&data
);
332 gchar
*imgpath
= NULL
;
333 gchar
*filename
= g_strdup_printf("%s-%s.jpg",nartist
,nalbum
);
334 imgpath
= gmpc_get_covers_path(filename
);
336 fp
= fopen(imgpath
, "wb");
339 fwrite(data
.data
, sizeof(char), data
.size
,fp
);
340 *url
= g_strdup(imgpath
);
346 gmpc_easy_download_clean(&data
);
350 else if(type
&META_ALBUM_TXT
)
352 debug_printf(DEBUG_INFO
, "Trying to fetch album txt");
356 gchar
*filename
, *imgpath
;
357 filename
= g_strdup_printf("%s-%s.albuminfo",nartist
,nalbum
);
358 imgpath
= gmpc_get_covers_path(filename
);
360 fp
= fopen(imgpath
, "w");
364 *url
= g_strdup(imgpath
);
366 * Quick 'n Dirty html stripper
368 for(j
=0; j
< strlen(asi
->album_info
);j
++)
370 if((asi
->album_info
)[j
] == '<') depth
++;
371 else if((asi
->album_info
)[j
] == '>' && depth
) depth
--;
374 fputc((asi
->album_info
)[j
],fp
);
384 amazon_song_info_free(asi
);
390 static int fetch_cover_art(mpd_Song
*song
,int type
, char **url
)
393 gchar
*artist
= __cover_art_process_string(song
->artist
);
394 gchar
*album
= __cover_art_process_string(song
->album
);
397 retval
= __fetch_metadata_amazon(search_types
[i
++], artist
, album
,type
,url
);
408 static void amazon_cover_art_enable_toggle(GtkWidget
*wid
)
410 int kk
= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid
));
411 cfg_set_single_value_as_int(config
, "cover-amazon", "enable", kk
);
413 static void amazon_cover_art_pref_destroy(GtkWidget
*container
)
415 gtk_container_remove(GTK_CONTAINER(container
), wp_pref_vbox
);
417 static void amazon_cover_art_pref_selection_changed(GtkWidget
*box
)
419 cfg_set_single_value_as_int(config
, "cover-amazon", "location", gtk_combo_box_get_active(GTK_COMBO_BOX(box
)));
422 static void amazon_cover_art_pref_construct(GtkWidget
*container
)
424 GtkWidget
*enable_cg
= gtk_check_button_new_with_mnemonic("_Enable amazon as cover art source");
425 GtkWidget
*label
= NULL
;
426 GtkWidget
*selection
= NULL
;
427 GtkWidget
*entry
= NULL
;
428 GtkWidget
*hbox
= NULL
;
430 wp_pref_vbox
= gtk_vbox_new(FALSE
,6);
432 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enable_cg
),
433 cfg_get_single_value_as_int_with_default(config
, "cover-amazon", "enable", TRUE
));
435 g_signal_connect(G_OBJECT(enable_cg
), "toggled", G_CALLBACK(amazon_cover_art_enable_toggle
), NULL
);
436 gtk_box_pack_start(GTK_BOX(wp_pref_vbox
), enable_cg
, FALSE
, FALSE
, 0);
439 hbox
= gtk_hbox_new(FALSE
, 6);
440 label
= gtk_label_new("Location:");
441 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
,0);
442 selection
= gtk_combo_box_new_text();
443 for(i
=0;i
<ENDPOINTS
;i
++)
444 gtk_combo_box_append_text(GTK_COMBO_BOX(selection
), endpoints
[i
][1]);
445 gtk_combo_box_set_active(GTK_COMBO_BOX(selection
),cfg_get_single_value_as_int_with_default(config
, "cover-amazon", "location", 0));
446 g_signal_connect(G_OBJECT(selection
), "changed",G_CALLBACK(amazon_cover_art_pref_selection_changed
), NULL
);
448 gtk_box_pack_start(GTK_BOX(hbox
), selection
, TRUE
,TRUE
,0);
449 gtk_box_pack_start(GTK_BOX(wp_pref_vbox
), hbox
, FALSE
, FALSE
, 0);
451 gtk_container_add(GTK_CONTAINER(container
), wp_pref_vbox
);
452 gtk_widget_show_all(container
);
455 static int shrink_string(gchar
*string
, int start
, int end
)
459 for( i
= start
; i
< end
; i
++)
460 string
[i
] = string
[i
+ 1];