2 * sj-metadata-musicbrainz.c
3 * Copyright (C) 2003 Ross Burton <ross@burtonini.com>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
23 #endif /* HAVE_CONFIG_H */
27 #include <glib-object.h>
28 #include <glib/gi18n.h>
29 #include <glib/gerror.h>
30 #include <glib/glist.h>
31 #include <glib/gstrfuncs.h>
32 #include <glib/gmessages.h>
33 #include <gconf/gconf-client.h>
34 #include <musicbrainz/queries.h>
35 #include <musicbrainz/mb_c.h>
39 #include <nautilus-burn-drive.h>
40 #ifndef NAUTILUS_BURN_CHECK_VERSION
41 #define NAUTILUS_BURN_CHECK_VERSION(a,b,c) FALSE
44 #if NAUTILUS_BURN_CHECK_VERSION(2,15,3)
45 #include <nautilus-burn.h>
48 #ifndef HAVE_BURN_DRIVE_UNREF
49 #define nautilus_burn_drive_unref nautilus_burn_drive_free
52 #include "sj-metadata-musicbrainz.h"
53 #include "sj-structures.h"
56 struct SjMetadataMusicbrainzPrivate
{
57 GError
*construct_error
;
62 /* TODO: remove and use an async queue or something l33t */
67 static GError
* mb_get_new_error (SjMetadata
*metadata
);
68 static void mb_set_cdrom (SjMetadata
*metadata
, const char* device
);
69 static void mb_set_proxy (SjMetadata
*metadata
, const char* proxy
);
70 static void mb_set_proxy_port (SjMetadata
*metadata
, const int port
);
71 static void mb_list_albums (SjMetadata
*metadata
, GError
**error
);
72 static char *mb_get_submit_url (SjMetadata
*metadata
);
74 #define GCONF_MUSICBRAINZ_SERVER "/apps/sound-juicer/musicbrainz_server"
75 #define GCONF_PROXY_USE_PROXY "/system/http_proxy/use_http_proxy"
76 #define GCONF_PROXY_HOST "/system/http_proxy/host"
77 #define GCONF_PROXY_PORT "/system/http_proxy/port"
78 #define GCONF_PROXY_USE_AUTHENTICATION "/system/http_proxy/use_authentication"
79 #define GCONF_PROXY_USERNAME "/system/http_proxy/authentication_user"
80 #define GCONF_PROXY_PASSWORD "/system/http_proxy/authentication_password"
86 static GObjectClass
*parent_class
= NULL
;
89 sj_metadata_musicbrainz_finalize (GObject
*object
)
91 SjMetadataMusicbrainzPrivate
*priv
;
92 g_return_if_fail (object
!= NULL
);
93 priv
= SJ_METADATA_MUSICBRAINZ (object
)->priv
;
95 g_free (priv
->http_proxy
);
102 sj_metadata_musicbrainz_instance_init (GTypeInstance
*instance
, gpointer g_class
)
104 GConfClient
*gconf_client
;
105 char *server_name
= NULL
;
106 SjMetadataMusicbrainz
*self
= (SjMetadataMusicbrainz
*)instance
;
107 self
->priv
= g_new0 (SjMetadataMusicbrainzPrivate
, 1);
108 self
->priv
->construct_error
= NULL
;
109 self
->priv
->mb
= mb_New ();
110 /* TODO: something. :/ */
111 if (!self
->priv
->mb
) {
112 g_set_error (&self
->priv
->construct_error
,
113 SJ_ERROR
, SJ_ERROR_CD_LOOKUP_ERROR
,
114 _("Cannot create MusicBrainz client"));
117 mb_UseUTF8 (self
->priv
->mb
, TRUE
);
119 gconf_client
= gconf_client_get_default ();
120 server_name
= gconf_client_get_string (gconf_client
, GCONF_MUSICBRAINZ_SERVER
, NULL
);
122 g_strstrip (server_name
);
124 if (server_name
&& strcmp (server_name
, "") != 0) {
125 mb_SetServer (self
->priv
->mb
, server_name
, 80);
126 g_free (server_name
);
129 /* Set the HTTP proxy */
130 if (gconf_client_get_bool (gconf_client
, GCONF_PROXY_USE_PROXY
, NULL
)) {
131 char *proxy_host
= gconf_client_get_string (gconf_client
, GCONF_PROXY_HOST
, NULL
);
132 mb_SetProxy (self
->priv
->mb
, proxy_host
,
133 gconf_client_get_int (gconf_client
, GCONF_PROXY_PORT
, NULL
));
135 if (gconf_client_get_bool (gconf_client
, GCONF_PROXY_USE_AUTHENTICATION
, NULL
)) {
136 #if HAVE_MB_SETPROXYCREDS
137 char *username
= gconf_client_get_string (gconf_client
, GCONF_PROXY_USERNAME
, NULL
);
138 char *password
= gconf_client_get_string (gconf_client
, GCONF_PROXY_PASSWORD
, NULL
);
139 mb_SetProxyCreds (self
->priv
->mb
, username
, password
);
143 g_warning ("mb_SetProxyCreds() not found, no proxy authorisation possible.");
148 g_object_unref (gconf_client
);
150 if (g_getenv("MUSICBRAINZ_DEBUG")) {
151 mb_SetDebug (self
->priv
->mb
, TRUE
);
156 metadata_interface_init (gpointer g_iface
, gpointer iface_data
)
158 SjMetadataClass
*klass
= (SjMetadataClass
*)g_iface
;
159 klass
->get_new_error
= mb_get_new_error
;
160 klass
->set_cdrom
= mb_set_cdrom
;
161 klass
->set_proxy
= mb_set_proxy
;
162 klass
->set_proxy_port
= mb_set_proxy_port
;
163 klass
->list_albums
= mb_list_albums
;
164 klass
->get_submit_url
= mb_get_submit_url
;
168 sj_metadata_musicbrainz_class_init (SjMetadataMusicbrainzClass
*class)
170 GObjectClass
*object_class
;
171 parent_class
= g_type_class_peek_parent (class);
172 object_class
= (GObjectClass
*) class;
173 object_class
->finalize
= sj_metadata_musicbrainz_finalize
;
177 sj_metadata_musicbrainz_get_type (void)
179 static GType type
= 0;
181 static const GTypeInfo info
= {
182 sizeof (SjMetadataMusicbrainzClass
),
185 (GClassInitFunc
)sj_metadata_musicbrainz_class_init
,
188 sizeof (SjMetadataMusicbrainz
),
190 sj_metadata_musicbrainz_instance_init
,
193 static const GInterfaceInfo metadata_i_info
= {
194 (GInterfaceInitFunc
) metadata_interface_init
,
197 type
= g_type_register_static (G_TYPE_OBJECT
, "SjMetadataMusicBrainzClass", &info
, 0);
198 g_type_add_interface_static (type
, SJ_TYPE_METADATA
, &metadata_i_info
);
204 sj_metadata_musicbrainz_new (void)
206 return g_object_new (sj_metadata_musicbrainz_get_type (), NULL
);
213 #define BYTES_PER_SECTOR 2352
214 #define BYTES_PER_SECOND (44100 / 8) / 16 / 2
217 get_duration_from_sectors (int sectors
)
219 return (sectors
* BYTES_PER_SECTOR
/ BYTES_PER_SECOND
);
223 get_offline_track_listing(SjMetadata
*metadata
, GError
**error
)
225 SjMetadataMusicbrainzPrivate
*priv
;
231 g_return_val_if_fail (metadata
!= NULL
, NULL
);
232 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
234 if (!mb_Query (priv
->mb
, MBQ_GetCDTOC
)) {
236 mb_GetQueryError (priv
->mb
, message
, 255);
238 SJ_ERROR
, SJ_ERROR_CD_LOOKUP_ERROR
,
239 _("Cannot read CD: %s"), message
);
243 num_tracks
= mb_GetResultInt (priv
->mb
, MBE_TOCGetLastTrack
);
245 album
= g_new0 (AlbumDetails
, 1);
246 album
->artist
= g_strdup (_("Unknown Artist"));
247 album
->title
= g_strdup (_("Unknown Title"));
249 for (i
= 1; i
<= num_tracks
; i
++) {
250 track
= g_new0 (TrackDetails
, 1);
251 track
->album
= album
;
253 track
->title
= g_strdup_printf (_("Track %d"), i
);
254 track
->artist
= g_strdup (album
->artist
);
255 track
->duration
= get_duration_from_sectors (mb_GetResultInt1 (priv
->mb
, MBE_TOCGetTrackNumSectors
, i
+1));
256 album
->tracks
= g_list_append (album
->tracks
, track
);
259 return g_list_append (list
, album
);
263 fire_signal_idle (SjMetadataMusicbrainz
*m
)
265 g_return_val_if_fail (SJ_IS_METADATA_MUSICBRAINZ (m
), FALSE
);
266 g_signal_emit_by_name (G_OBJECT (m
), "metadata", m
->priv
->albums
, m
->priv
->error
);
275 mb_get_new_error (SjMetadata
*metadata
)
277 GError
*error
= NULL
;
278 if (metadata
== NULL
|| SJ_METADATA_MUSICBRAINZ (metadata
)->priv
== NULL
) {
280 SJ_ERROR
, SJ_ERROR_INTERNAL_ERROR
,
281 _("MusicBrainz metadata object is not valid. This is bad, check your console for errors."));
284 return SJ_METADATA_MUSICBRAINZ (metadata
)->priv
->construct_error
;
288 mb_set_cdrom (SjMetadata
*metadata
, const char* device
)
290 SjMetadataMusicbrainzPrivate
*priv
;
291 g_return_if_fail (metadata
!= NULL
);
292 g_return_if_fail (device
!= NULL
);
293 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
296 g_free (priv
->cdrom
);
298 priv
->cdrom
= g_strdup (device
);
299 mb_SetDevice (priv
->mb
, priv
->cdrom
);
303 mb_set_proxy (SjMetadata
*metadata
, const char* proxy
)
305 SjMetadataMusicbrainzPrivate
*priv
;
306 g_return_if_fail (metadata
!= NULL
);
307 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
312 if (priv
->http_proxy
) {
313 g_free (priv
->http_proxy
);
315 priv
->http_proxy
= g_strdup (proxy
);
316 mb_SetProxy (priv
->mb
, priv
->http_proxy
, priv
->http_proxy_port
);
320 mb_set_proxy_port (SjMetadata
*metadata
, const int port
)
322 SjMetadataMusicbrainzPrivate
*priv
;
323 g_return_if_fail (metadata
!= NULL
);
324 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
326 priv
->http_proxy_port
= port
;
327 mb_SetProxy (priv
->mb
, priv
->http_proxy
, priv
->http_proxy_port
);
330 /* Data imported from FreeDB is horrendeous for compilations,
331 * Try to split the 'Various' artist */
333 artist_and_title_from_title (TrackDetails
*track
, gpointer data
)
335 char *slash
, **split
;
337 if (g_ascii_strncasecmp (MBI_VARIOUS_ARTIST_ID
, track
->album
->artist_id
, 64) != 0 && track
->album
->artist_id
[0] != '\0' && track
->artist_id
[0] != '\0') {
338 track
->title
= g_strdup (data
);
342 slash
= strstr (data
, " / ");
344 track
->title
= g_strdup (data
);
347 split
= g_strsplit (data
, " / ", 2);
348 track
->artist
= g_strdup (split
[0]);
349 track
->title
= g_strdup (split
[1]);
355 * Write the RDF in the MusicBrainz object to the file specified.
358 cache_rdf (musicbrainz_t mb
, const char *filename
)
360 GError
*error
= NULL
;
364 g_assert (mb
!= NULL
);
365 g_assert (filename
!= NULL
);
367 /* Create the folder for the file */
368 path
= g_path_get_dirname (filename
);
369 g_mkdir_with_parents (path
, 0755); /* Handle errors in set_contents() */
372 /* How much data is there to save? */
373 len
= mb_GetResultRDFLen (mb
);
374 rdf
= g_malloc0 (len
);
376 /* Get the RDF and save it */
377 mb_GetResultRDF (mb
, rdf
, len
);
378 if (!g_file_set_contents (filename
, rdf
, len
, &error
)) {
379 g_warning ("Cannot write cache file %s: %s", filename
, error
->message
);
380 g_error_free (error
);
387 * Load into the MusicBrainz object the RDF from the specified cache file if it
388 * exists and is valid then return TRUE, otherwise return FALSE.
391 get_cached_rdf (musicbrainz_t mb
, const char *cachepath
)
393 gboolean ret
= FALSE
;
394 GError
*error
= NULL
;
397 g_assert (mb
!= NULL
);
398 g_assert (cachepath
!= NULL
);
400 if (!g_file_test (cachepath
, G_FILE_TEST_EXISTS
| G_FILE_TEST_IS_REGULAR
))
403 /* Cache file exists, open it */
404 if (!g_file_get_contents (cachepath
, &rdf
, NULL
, &error
)) {
405 g_warning ("Cannot open cache file %s: %s", cachepath
, error
->message
);
406 g_error_free (error
);
411 if (mb_SetResultRDF (mb
, rdf
))
420 get_cached_rdf (musicbrainz_t mb
, const char *cachepath
)
425 cache_rdf (musicbrainz_t mb
, const char *filename
) {
430 * Fill the MusicBrainz object with RDF. Basically get the CD Index and check
431 * the local cache, if that fails then lookup the data online.
434 get_rdf (SjMetadata
*metadata
)
436 SjMetadataMusicbrainzPrivate
*priv
;
438 char *cdindex
= NULL
, *cachepath
= NULL
;
440 g_assert (metadata
!= NULL
);
442 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
445 /* Get the Table of Contents */
446 if (!mb_Query (priv
->mb
, MBQ_GetCDTOC
)) {
447 mb_GetQueryError (priv
->mb
, data
, sizeof (data
));
448 g_print (_("This CD could not be queried: %s\n"), data
);
452 /* Extract the CD Index */
453 if (!mb_GetResultData(priv
->mb
, MBE_TOCGetCDIndexId
, data
, sizeof (data
))) {
454 mb_GetQueryError (priv
->mb
, data
, sizeof (data
));
455 g_print (_("This CD could not be queried: %s\n"), data
);
458 cdindex
= g_strdup (data
);
460 cachepath
= g_build_filename (g_get_home_dir (), ".gnome2", "sound-juicer", "cache", cdindex
, NULL
);
463 if (!get_cached_rdf (priv
->mb
, cachepath
)) {
464 /* Don't re-use the CD Index as that doesn't send enough data to the server.
465 By doing this we also pass track lengths, which can be proxied to FreeDB
467 if (!mb_Query (priv
->mb
, MBQ_GetCDInfo
)) {
468 mb_GetQueryError (priv
->mb
, data
, sizeof (data
));
469 g_print (_("This CD could not be queried: %s\n"), data
);
472 cache_rdf (priv
->mb
, cachepath
);
480 static NautilusBurnMediaType
481 get_drive_media_type (SjMetadata
*metadata
)
483 SjMetadataMusicbrainzPrivate
*priv
;
484 NautilusBurnDrive
*drive
;
485 NautilusBurnMediaType type
;
486 #if NAUTILUS_BURN_CHECK_VERSION(2,15,3)
487 NautilusBurnDriveMonitor
*monitor
;
490 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
491 #if NAUTILUS_BURN_CHECK_VERSION(2,15,3)
492 if (! nautilus_burn_initialized ()) {
493 nautilus_burn_init ();
495 monitor
= nautilus_burn_get_drive_monitor ();
496 drive
= nautilus_burn_drive_monitor_get_drive_for_device (monitor
, priv
->cdrom
);
498 drive
= nautilus_burn_drive_new_from_path (priv
->cdrom
);
502 return NAUTILUS_BURN_MEDIA_TYPE_ERROR
;
504 type
= nautilus_burn_drive_get_media_type (drive
);
505 nautilus_burn_drive_unref (drive
);
510 lookup_cd (SjMetadata
*metadata
)
512 /** The size of the buffer used in MusicBrainz lookups */
513 SjMetadataMusicbrainzPrivate
*priv
;
514 GList
*albums
= NULL
;
517 int num_albums
, i
, j
;
518 NautilusBurnMediaType type
;
520 /* TODO: fire error signal */
521 g_return_val_if_fail (metadata
!= NULL
, NULL
);
522 g_return_val_if_fail (SJ_IS_METADATA_MUSICBRAINZ (metadata
), NULL
);
523 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
524 g_return_val_if_fail (priv
->cdrom
!= NULL
, NULL
);
525 priv
->error
= NULL
; /* TODO: hack */
527 type
= get_drive_media_type (metadata
);
529 if (type
== NAUTILUS_BURN_MEDIA_TYPE_ERROR
) {
533 if (access (priv
->cdrom
, W_OK
) == 0) {
534 msg
= g_strdup_printf (_("Device '%s' does not contain any media"), priv
->cdrom
);
535 err
= SJ_ERROR_CD_NO_MEDIA
;
537 msg
= g_strdup_printf (_("Device '%s' could not be opened. Check the access permissions on the device."), priv
->cdrom
);
538 err
= SJ_ERROR_CD_PERMISSION_ERROR
;
540 priv
->error
= g_error_new (SJ_ERROR
, err
, _("Cannot read CD: %s"), msg
);
544 g_idle_add ((GSourceFunc
)fire_signal_idle
, metadata
);
550 num_albums
= mb_GetResultInt(priv
->mb
, MBE_GetNumAlbums
);
551 if (num_albums
< 1) {
552 priv
->albums
= get_offline_track_listing (metadata
, &(priv
->error
));
553 g_idle_add ((GSourceFunc
)fire_signal_idle
, metadata
);
557 for (i
= 1; i
<= num_albums
; i
++) {
561 mb_Select1(priv
->mb
, MBS_SelectAlbum
, i
);
562 album
= g_new0 (AlbumDetails
, 1);
564 if (mb_GetResultData(priv
->mb
, MBE_AlbumGetAlbumId
, data
, sizeof (data
))) {
565 mb_GetIDFromURL (priv
->mb
, data
, data
, sizeof (data
));
566 album
->album_id
= g_strdup (data
);
569 if (mb_GetResultData (priv
->mb
, MBE_AlbumGetAlbumArtistId
, data
, sizeof (data
))) {
570 mb_GetIDFromURL (priv
->mb
, data
, data
, sizeof (data
));
571 album
->artist_id
= g_strdup (data
);
572 if (g_ascii_strncasecmp (MBI_VARIOUS_ARTIST_ID
, data
, 64) == 0) {
573 album
->artist
= g_strdup (_("Various"));
575 if (*data
&& mb_GetResultData1(priv
->mb
, MBE_AlbumGetArtistName
, data
, sizeof (data
), 1)) {
576 album
->artist
= g_strdup (data
);
578 album
->artist
= g_strdup (_("Unknown Artist"));
580 if (*data
&& mb_GetResultData1(priv
->mb
, MBE_AlbumGetArtistSortName
, data
, sizeof (data
), 1)) {
581 album
->artist_sortname
= g_strdup (data
);
586 if (mb_GetResultData(priv
->mb
, MBE_AlbumGetAlbumName
, data
, sizeof (data
))) {
587 album
->title
= g_strdup (data
);
589 album
->title
= g_strdup (_("Unknown Title"));
594 num_releases
= mb_GetResultInt (priv
->mb
, MBE_AlbumGetNumReleaseDates
);
595 if (num_releases
> 0) {
596 mb_Select1(priv
->mb
, MBS_SelectReleaseDate
, 1);
597 if (mb_GetResultData(priv
->mb
, MBE_ReleaseGetDate
, data
, sizeof (data
))) {
598 int matched
, year
=1, month
=1, day
=1;
599 matched
= sscanf(data
, "%u-%u-%u", &year
, &month
, &day
);
601 album
->release_date
= g_date_new_dmy ((day
== 0) ? 1 : day
, (month
== 0) ? 1 : month
, year
);
604 mb_Select(priv
->mb
, MBS_Back
);
608 num_tracks
= mb_GetResultInt(priv
->mb
, MBE_AlbumGetNumTracks
);
609 if (num_tracks
< 1) {
610 g_free (album
->artist
);
611 g_free (album
->artist_sortname
);
612 g_free (album
->title
);
614 g_warning (_("Incomplete metadata for this CD"));
615 priv
->albums
= get_offline_track_listing (metadata
, &(priv
->error
));
616 g_idle_add ((GSourceFunc
)fire_signal_idle
, metadata
);
620 for (j
= 1; j
<= num_tracks
; j
++) {
622 track
= g_new0 (TrackDetails
, 1);
624 track
->album
= album
;
626 track
->number
= j
; /* replace with number lookup? */
628 if (mb_GetResultData1(priv
->mb
, MBE_AlbumGetTrackId
, data
, sizeof (data
), j
)) {
629 mb_GetIDFromURL (priv
->mb
, data
, data
, sizeof (data
));
630 track
->track_id
= g_strdup (data
);
633 if (mb_GetResultData1(priv
->mb
, MBE_AlbumGetArtistId
, data
, sizeof (data
), j
)) {
634 mb_GetIDFromURL (priv
->mb
, data
, data
, sizeof (data
));
635 track
->artist_id
= g_strdup (data
);
638 if (mb_GetResultData1(priv
->mb
, MBE_AlbumGetTrackName
, data
, sizeof (data
), j
)) {
639 if (track
->artist_id
!= NULL
) {
640 artist_and_title_from_title (track
, data
);
642 track
->title
= g_strdup (data
);
646 if (track
->artist
== NULL
&& mb_GetResultData1(priv
->mb
, MBE_AlbumGetArtistName
, data
, sizeof (data
), j
)) {
647 track
->artist
= g_strdup (data
);
650 if (mb_GetResultData1(priv
->mb
, MBE_AlbumGetArtistSortName
, data
, sizeof (data
), j
)) {
651 track
->artist_sortname
= g_strdup (data
);
654 if (mb_GetResultData1(priv
->mb
, MBE_AlbumGetTrackDuration
, data
, sizeof (data
), j
)) {
655 track
->duration
= atoi (data
) / 1000;
658 album
->tracks
= g_list_append (album
->tracks
, track
);
662 albums
= g_list_append (albums
, album
);
664 mb_Select (priv
->mb
, MBS_Rewind
);
667 /* For each album, we need to insert the duration data if necessary
668 * We need to query this here because otherwise we would flush the
669 * data queried from the server */
670 /* TODO: scan for 0 duration before doing the query to avoid another lookup if
671 we don't need to do it */
672 mb_Query (priv
->mb
, MBQ_GetCDTOC
);
673 for (al
= albums
; al
; al
= al
->next
) {
674 AlbumDetails
*album
= al
->data
;
677 for (tl
= album
->tracks
; tl
; tl
= tl
->next
) {
678 TrackDetails
*track
= tl
->data
;
681 if (track
->duration
== 0) {
682 sectors
= mb_GetResultInt1 (priv
->mb
, MBE_TOCGetTrackNumSectors
, j
+1);
683 track
->duration
= get_duration_from_sectors (sectors
);
689 priv
->albums
= albums
;
690 g_idle_add ((GSourceFunc
)fire_signal_idle
, metadata
);
695 mb_list_albums (SjMetadata
*metadata
, GError
**error
)
699 g_return_if_fail (SJ_IS_METADATA_MUSICBRAINZ (metadata
));
701 thread
= g_thread_create ((GThreadFunc
)lookup_cd
, metadata
, TRUE
, error
);
702 if (thread
== NULL
) {
704 SJ_ERROR
, SJ_ERROR_INTERNAL_ERROR
,
705 _("Could not create CD lookup thread"));
711 mb_get_submit_url (SjMetadata
*metadata
)
713 SjMetadataMusicbrainzPrivate
*priv
;
716 g_return_val_if_fail (metadata
!= NULL
, NULL
);
718 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
720 if (mb_GetWebSubmitURL(priv
->mb
, url
, 1024)) {
721 return g_strdup(url
);