[vlc-commits] [Git][videolan/vlc][3.0.x] 7 commits: access: cdrom: return TOC struct instead of only sectors

Hugo Beauzée-Luyssen (@chouquette) gitlab at videolan.org
Fri Aug 27 12:02:26 UTC 2021



Hugo Beauzée-Luyssen pushed to branch 3.0.x at VideoLAN / VLC


Commits:
3941c7b8 by Francois Cartegnie at 2021-08-27T11:10:13+00:00
access: cdrom: return TOC struct instead of only sectors

(cherry picked from commit 890db4f7e7b04dc1c28bc3ba5eeebf76b102b9d0)

- - - - -
7e656c3b by Francois Cartegnie at 2021-08-27T11:10:13+00:00
access: cdda: fix audio/data mixed mode

best way to kill your speakers

(cherry picked from commit 182133eab2c6cf3c9ae98dd6383a774d07b5762d)

- - - - -
c48778b2 by Francois Cartegnie at 2021-08-27T11:10:13+00:00
acoustid: rename mb structure

(cherry picked from commit 6065ecc4217102d73af6d2266d25b27811647dd8)

- - - - -
ebd88159 by Francois Cartegnie at 2021-08-27T11:10:13+00:00
acoustid: refactor

(cherry picked from commit cdb7b9d8b21815d6dbe06aed25d9bf3621634e9d)

- - - - -
f0598afd by Francois Cartegnie at 2021-08-27T11:10:13+00:00
acoustid: parametrize server options

(cherry picked from commit 06e1aafd0d7d39db236723fa394205d956404070)

- - - - -
f2ef3fee by Francois Cartegnie at 2021-08-27T11:10:13+00:00
misc: webservices: add musicbrainz

(cherry picked from commit e8f5c617dfcf6dc571701eaa8c5d41b5a23d57a5)

- - - - -
4c49c64e by Francois Cartegnie at 2021-08-27T11:10:13+00:00
access: cdda: add support for musicbrainz (fix #21796)

deprecates CDDB which has too low entropy for lookups

(cherry picked from commit 7fde19d9d23a7a075f1e532ef3144dab86d4306b)

- - - - -


13 changed files:

- modules/access/Makefile.am
- modules/access/cdda.c
- modules/access/vcd/cdrom.c
- modules/access/vcd/cdrom.h
- modules/access/vcd/cdrom_internals.h
- modules/access/vcd/vcd.c
- modules/misc/Makefile.am
- modules/misc/fingerprinter.c
- modules/misc/webservices/acoustid.c
- modules/misc/webservices/acoustid.h
- + modules/misc/webservices/json_helper.h
- + modules/misc/webservices/musicbrainz.c
- + modules/misc/webservices/musicbrainz.h


Changes:

=====================================
modules/access/Makefile.am
=====================================
@@ -205,8 +205,9 @@ EXTRA_LTLIBRARIES += libvnc_plugin.la
 
 ### Optical media ###
 
-libcdda_plugin_la_SOURCES = access/cdda.c access/disc_helper.h access/vcd/cdrom.c \
-                            access/vcd/cdrom.h access/vcd/cdrom_internals.h
+libcdda_plugin_la_SOURCES = access/cdda.c access/vcd/cdrom.c access/vcd/cdrom.h access/vcd/cdrom_internals.h \
+                            misc/webservices/json.c misc/webservices/json.h misc/webservices/json_helper.h \
+                            misc/webservices/musicbrainz.c misc/webservices/musicbrainz.h
 libcdda_plugin_la_CFLAGS = $(AM_CFLAGS) $(LIBCDDB_CFLAGS)
 libcdda_plugin_la_LIBADD = $(LIBCDDB_LIBS) $(LIBM)
 libcdda_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(accessdir)'
@@ -214,6 +215,10 @@ if HAVE_DARWIN
 libcdda_plugin_la_LIBADD += -liconv
 libcdda_plugin_la_LDFLAGS += -Wl,-framework,IOKit,-framework,CoreFoundation
 endif
+if HAVE_GCRYPT
+libcdda_plugin_la_CFLAGS += $(GCRYPT_CFLAGS)
+libcdda_plugin_la_LIBADD += $(GCRYPT_LIBS)
+endif
 EXTRA_LTLIBRARIES += libcdda_plugin.la
 access_LTLIBRARIES += $(LTLIBcdda)
 


=====================================
modules/access/cdda.c
=====================================
@@ -40,6 +40,7 @@
 #include <math.h>
 #include <stdlib.h>
 #include <string.h>
+#include <ctype.h>
 
 #include <vlc_common.h>
 #include <vlc_demux.h>
@@ -49,9 +50,16 @@
 #include <vlc_meta.h>
 #include <vlc_charset.h> /* ToLocaleDup */
 #include <vlc_url.h>
+#include <vlc_strings.h>
+#include <vlc_memstream.h>
+#ifdef HAVE_GCRYPT
+# include <gcrypt.h>
+# include <vlc_gcrypt.h>
+#endif
 
 #include "disc_helper.h"
 #include "vcd/cdrom.h"  /* For CDDA_DATA_SIZE */
+#include "../misc/webservices/musicbrainz.h"
 
 
 #ifdef HAVE_LIBCDDB
@@ -219,6 +227,67 @@ static int DemuxControl(demux_t *demux, int query, va_list args)
     return VLC_SUCCESS;
 }
 
+/*
+ * Check for Mixed Audio/CDROM data
+ *
+ * ioctl will return first track # and last track # from last TOC
+ * (this could start > 1..  but where's the case ?)
+ *
+ * vcdev_toc stores i_tracks as the number of STORED sectors info,
+ * this is last - first track, plus one (the LEADOUT one)
+ *
+ * tracks can be DATA in mixed data/audio mode (CDROM spec, not CDDA)
+ * and this is control flag (Subcode Channel Q)
+ *
+ * first: 1 last 13
+ * track:   1 lba:         0 (        0) 00:02:00 adr: 1 control: 0 mode: -1
+ * track:   2 lba:     16462 (    65848) 03:41:37 adr: 1 control: 0 mode: -1
+ * ...
+ * track:  12 lba:    212405 (   849620) 47:14:05 adr: 1 control: 0 mode: -1
+ * track:  13 lba:    250948 (  1003792) 55:47:73 adr: 1 control: 4 mode: -1
+ * track:lout lba:    266286 (  1065144) 59:12:36 adr: 1 control: 4 mode: -1
+*/
+static int TOC_GetAudioRange(vcddev_toc_t *p_toc,
+                             int *pi_first, int *pi_last)
+{
+    if( p_toc->i_tracks < 1 )
+        return 0;
+
+    int i_first = p_toc->i_first_track;
+    int i_last = p_toc->i_last_track;
+    for(int i=i_first; i<p_toc->i_tracks; i++)
+    {
+        if((p_toc->p_sectors[i - 1].i_control & CD_ROM_DATA_FLAG) == 0)
+            break;
+        i_first++;
+    }
+    for(int i=i_last; i > 0; i--)
+    {
+        if((p_toc->p_sectors[i - 1].i_control & CD_ROM_DATA_FLAG) == 0)
+            break;
+        i_last--;
+    }
+
+    /* FIX copy protection TOC
+     * https://github.com/metabrainz/libdiscid/blob/e46249415eb6d657ecc63667b03d670a4347712f/src/toc.c#L101 */
+    do
+    {
+        vcddev_sector_t *p_last = &p_toc->p_sectors[i_last - p_toc->i_first_track];
+        vcddev_sector_t *p_lout = &p_toc->p_sectors[p_toc->i_tracks];
+        if(p_lout->i_lba > p_last->i_lba || i_last <= i_first)
+            break;
+        /* last audio is invalid, bigger than lead out */
+        i_last = i_last - 1;
+        p_toc->i_last_track = i_last;
+        p_last->i_lba -= CD_ROM_XA_INTERVAL;
+        p_toc->i_tracks--; /* change lead out */
+    } while( i_last > i_first );
+
+    *pi_first = i_first;
+    *pi_last = i_last;
+    return (i_last >= i_first) ? i_last - i_first + 1 : 0;
+}
+
 static int DemuxOpen(vlc_object_t *obj)
 {
     demux_t *demux = (demux_t *)obj;
@@ -244,19 +313,29 @@ static int DemuxOpen(vlc_object_t *obj)
     /* Track number in input item */
     if (sys->start == (unsigned)-1 || sys->length == (unsigned)-1)
     {
-        int *sectors = NULL; /* Track sectors */
-        unsigned titles = ioctl_GetTracksMap(obj, dev, &sectors);
+        vcddev_toc_t *p_toc = ioctl_GetTOC(obj, dev, true);
+        if(p_toc == NULL)
+            goto error;
+
+        int i_cdda_tracks, i_cdda_first, i_cdda_last;
+        i_cdda_tracks = TOC_GetAudioRange(p_toc, &i_cdda_first, &i_cdda_last);
 
-        if (track > titles)
+        if (track == 0 || track > (unsigned) i_cdda_tracks)
         {
-            msg_Err(obj, "invalid track number: %u/%u", track, titles);
-            free(sectors);
+            msg_Err(obj, "invalid track number: %u/%i", track, i_cdda_tracks);
+            vcddev_toc_Free(p_toc);
             goto error;
         }
 
-        sys->start = sectors[track - 1];
-        sys->length = sectors[track] - sys->start;
-        free(sectors);
+        track--;
+        int i_first_sector = p_toc->p_sectors[track].i_lba;
+        int i_last_sector = p_toc->p_sectors[track + 1].i_lba;
+        if(i_cdda_first + track == (unsigned) i_cdda_last && p_toc->i_last_track > i_cdda_last)
+            i_last_sector -= CD_ROM_XA_INTERVAL;
+
+        sys->start = i_first_sector;
+        sys->length = i_last_sector - i_first_sector;
+        vcddev_toc_Free(p_toc);
     }
 
     es_format_t fmt;
@@ -293,24 +372,166 @@ static void DemuxClose(vlc_object_t *obj)
 struct access_sys_t
 {
     vcddev_t    *vcddev;                            /* vcd device descriptor */
-    int         *p_sectors;                                 /* Track sectors */
-    int          titles;
+    vcddev_toc_t *p_toc;                            /* Tracks TOC */
+    int          i_cdda_tracks;                     /* # of audio tracks in TOC */
+    int          i_cdda_first;                      /* First .. */
+    int          i_cdda_last;                       /* Last .. */
     int          cdtextc;
     vlc_meta_t **cdtextv;
 #ifdef HAVE_LIBCDDB
     cddb_disc_t *cddb;
 #endif
+    musicbrainz_recording_t *mbrecord;
 };
 
-#ifdef HAVE_LIBCDDB
-static cddb_disc_t *GetCDDBInfo( vlc_object_t *obj, int i_titles, int *p_sectors )
+static inline int LBAPregap( int i_sector )
+{
+    /* LBA to sector pregap fixup
+       see libdiscid/src/toc.c #L80 */
+    return i_sector + 150;
+}
+
+#ifdef HAVE_GCRYPT
+static char * BuildMusicbrainzDiscID( const vcddev_toc_t *p_toc,
+                                      int i_total, int i_first, int i_last )
 {
-    if( !var_InheritBool( obj, "metadata-network-access" ) )
+    gcry_md_hd_t hd;
+    gcry_error_t err = gcry_md_open( &hd, GCRY_MD_SHA1, 0 );
+    if(err)
+        return NULL;
+
+    if(gcry_md_enable( hd, GCRY_MD_SHA1 ))
     {
-        msg_Dbg( obj, "album art policy set to manual: not fetching" );
+        gcry_md_close( hd );
         return NULL;
     }
 
+    char buffer[16];
+
+    sprintf( buffer, "%02X", i_first );
+    gcry_md_write( hd, buffer, 2 );
+    sprintf( buffer, "%02X", i_last );
+    gcry_md_write( hd, buffer, 2 );
+    /* LEAD OUT sector info */
+
+    /* LEAD OUT sector info
+     * https://github.com/metabrainz/libdiscid/blob/e46249415eb6d657ecc63667b03d670a4347712f/src/toc.c#L90 */
+    int i_last_track_end;
+    if (p_toc->i_tracks > i_total)
+        i_last_track_end = LBAPregap(p_toc->p_sectors[i_total].i_lba) - CD_ROM_XA_INTERVAL;
+    else
+        i_last_track_end = LBAPregap(p_toc->p_sectors[p_toc->i_tracks].i_lba);
+
+    sprintf( buffer, "%08X", i_last_track_end );
+    gcry_md_write( hd, buffer, 8 );
+
+    for( int i = 0; i<i_total; i++ ) /* skip LEAD OUT */
+    {
+        sprintf( buffer, "%08X", LBAPregap(p_toc->p_sectors[i].i_lba) );
+        gcry_md_write( hd, buffer, 8 );
+    }
+
+    for( int i = i_total; i<100; i++ )
+    {
+        if( i == p_toc->i_tracks )
+            continue;
+        gcry_md_write( hd, "00000000", 8 );
+    }
+
+    gcry_md_final( hd );
+
+    size_t i_len = gcry_md_get_algo_dlen( GCRY_MD_SHA1 );
+    unsigned char *output = gcry_md_read( hd, GCRY_MD_SHA1 );
+    char *out = vlc_b64_encode_binary( output, i_len );
+    if( out )
+    {
+        i_len = strlen( out );
+        for( size_t i=0; i<i_len; i++ )
+        {
+            if( !isalpha(out[i]) )
+            {
+                if( out[i] == '+' )
+                    out[i] = '.';
+                else if( out[i] == '/' )
+                    out[i] = '_';
+                else if( out[i] == '=' )
+                    out[i] = '-';
+            }
+        }
+    }
+
+    gcry_md_close( hd );
+
+    return out;
+}
+#else
+# define BuildMusicbrainzDiscID(a, b, c, d) (NULL)
+#endif
+
+static musicbrainz_recording_t * GetMusicbrainzInfo( vlc_object_t *obj,
+                                                     const vcddev_toc_t *p_toc,
+                                                     int i_total, int i_first, int i_last )
+{
+    musicbrainz_recording_t *recording = NULL;
+
+    char *psz_mbserver = var_InheritString( obj, "musicbrainz-server" );
+    if( !psz_mbserver || !*psz_mbserver )
+        return NULL;
+
+    musicbrainz_config_t cfg = { .obj = obj,
+                                 .psz_mb_server = psz_mbserver,
+                                 .psz_coverart_server = NULL };
+
+    /* Build DISC ID based on SHA1 */
+    char *psz_disc_id = BuildMusicbrainzDiscID( p_toc,
+                                                i_total, i_first, i_last );
+    if( psz_disc_id )
+    {
+        recording = musicbrainz_lookup_recording_by_discid( &cfg, psz_disc_id );
+    }
+    else /* Fuzzy lookup using TOC */
+    {
+        struct vlc_memstream ms;
+        if( vlc_memstream_open(&ms) )
+        {
+            free( psz_mbserver );
+            return NULL;
+        }
+
+        vlc_memstream_printf(&ms, "toc=%u+%u", i_first, i_last );
+        /* LEAD OUT sector info
+         * https://github.com/metabrainz/libdiscid/blob/e46249415eb6d657ecc63667b03d670a4347712f/src/toc.c#L90 */
+        int i_last_track_end;
+        if (p_toc->i_tracks > i_total)
+            i_last_track_end = LBAPregap(p_toc->p_sectors[i_total].i_lba) - CD_ROM_XA_INTERVAL;
+        else
+            i_last_track_end = LBAPregap(p_toc->p_sectors[p_toc->i_tracks].i_lba);
+        vlc_memstream_printf(&ms, "+%u", i_last_track_end );
+        for( int i = 0; i<i_total; i++ ) /* skipped LEAD OUT, audio only */
+            vlc_memstream_printf(&ms, "+%u", LBAPregap(p_toc->p_sectors[i].i_lba) );
+        if( vlc_memstream_flush(&ms) )
+        {
+            if( vlc_memstream_close(&ms) )
+                free( ms.ptr );
+            free( psz_mbserver );
+            return NULL;
+        }
+
+        recording = musicbrainz_lookup_recording_by_toc( &cfg, ms.ptr );
+
+        if( vlc_memstream_close(&ms) == 0 )
+            free( ms.ptr );
+    }
+
+    free( psz_mbserver );
+    return recording;
+}
+
+#ifdef HAVE_LIBCDDB
+static cddb_disc_t *GetCDDBInfo( vlc_object_t *obj, const vcddev_toc_t *p_toc )
+{
+    msg_Dbg( obj, "retrieving metadata with CDDB" );
+
     /* */
     cddb_conn_t *p_cddb = cddb_new();
     if( !p_cddb )
@@ -359,17 +580,17 @@ static cddb_disc_t *GetCDDBInfo( vlc_object_t *obj, int i_titles, int *p_sectors
     }
 
     int64_t i_length = 2000000; /* PreGap */
-    for( int i = 0; i < i_titles; i++ )
+    for( int i = 0; i < p_toc->i_tracks; i++ )
     {
         cddb_track_t *t = cddb_track_new();
-        cddb_track_set_frame_offset( t, p_sectors[i] + 150 );  /* Pregap offset */
+        cddb_track_set_frame_offset( t, p_toc->p_sectors[i].i_lba + 150 );  /* Pregap offset */
 
         cddb_disc_add_track( p_disc, t );
-        const int64_t i_size = ( p_sectors[i+1] - p_sectors[i] ) *
+        const int64_t i_size = ( p_toc->p_sectors[i+1].i_lba - p_toc->p_sectors[i].i_lba ) *
                                (int64_t)CDDA_DATA_SIZE;
         i_length += INT64_C(1000000) * i_size / 44100 / 4  ;
 
-        msg_Dbg( obj, "Track %i offset: %i", i, p_sectors[i] + 150 );
+        msg_Dbg( obj, "Track %i offset: %i", i, p_toc->p_sectors[i].i_lba + 150 );
     }
 
     msg_Dbg( obj, "Total length: %i", (int)(i_length/1000000) );
@@ -397,6 +618,8 @@ static cddb_disc_t *GetCDDBInfo( vlc_object_t *obj, int i_titles, int *p_sectors
 
     cddb_read( p_cddb, p_disc );
 
+    msg_Dbg( obj, "disc ID: 0x%08x", cddb_disc_get_discid(p_disc) );
+
     cddb_destroy( p_cddb);
     return p_disc;
 
@@ -404,6 +627,7 @@ error:
     if( p_disc )
         cddb_disc_destroy( p_disc );
     cddb_destroy( p_cddb );
+    msg_Dbg( obj, "CDDB failure" );
     return NULL;
 }
 #endif /* HAVE_LIBCDDB */
@@ -448,7 +672,7 @@ static void AccessGetMeta(stream_t *access, vlc_meta_t *meta)
         str = cddb_disc_get_artist(sys->cddb);
         if (NONEMPTY(str))
         {
-            for (int i = 0; i < sys->titles; i++)
+            for (int i = 0; i < sys->p_toc->i_tracks; i++)
             {
                 cddb_track_t *t = cddb_disc_get_track(sys->cddb, i);
                 if (t == NULL)
@@ -475,21 +699,33 @@ static void AccessGetMeta(stream_t *access, vlc_meta_t *meta)
 static int ReadDir(stream_t *access, input_item_node_t *node)
 {
     access_sys_t *sys = access->p_sys;
+    const vcddev_toc_t *p_toc = sys->p_toc;
 
     /* Build title table */
-    for (int i = 0; i < sys->titles; i++)
+    const int i_start_track_offset = sys->i_cdda_first - sys->p_toc->i_first_track;
+    for (int i = 0; i < sys->i_cdda_tracks; i++)
     {
-        msg_Dbg(access, "track[%d] start=%d", i, sys->p_sectors[i]);
+        if(i < i_start_track_offset)
+            continue;
+
+        msg_Dbg(access, "track[%d] start=%d", i, p_toc->p_sectors[i].i_lba);
 
         /* Initial/default name */
         char *name;
 
-        if (unlikely(asprintf(&name, _("Audio CD - Track %02i"), i + 1) == -1))
+        if (unlikely(asprintf(&name, _("Audio CD - Track %02i"),
+                              i - i_start_track_offset + 1 ) == -1))
             name = NULL;
 
         /* Create playlist items */
+        int i_first_sector = p_toc->p_sectors[i].i_lba;
+        int i_last_sector = p_toc->p_sectors[i + 1].i_lba;
+        if(sys->i_cdda_first + i == sys->i_cdda_last &&
+           p_toc->i_last_track > sys->i_cdda_last)
+            i_last_sector -= CD_ROM_XA_INTERVAL;
+
         const mtime_t duration =
-            (mtime_t)(sys->p_sectors[i + 1] - sys->p_sectors[i])
+            (mtime_t)(i_last_sector - i_first_sector)
             * CDDA_DATA_SIZE * CLOCK_FREQ / 44100 / 2 / 2;
 
         input_item_t *item = input_item_NewDisc(access->psz_url,
@@ -508,14 +744,13 @@ static int ReadDir(stream_t *access, input_item_node_t *node)
         }
 
         if (likely(asprintf(&opt, "cdda-first-sector=%i",
-                            sys->p_sectors[i]) != -1))
+                            p_toc->p_sectors[i].i_lba) != -1))
         {
             input_item_AddOption(item, opt, VLC_INPUT_OPTION_TRUSTED);
             free(opt);
         }
 
-        if (likely(asprintf(&opt, "cdda-last-sector=%i",
-                            sys->p_sectors[i + 1]) != -1))
+        if (likely(asprintf(&opt, "cdda-last-sector=%i", i_last_sector) != -1))
         {
             input_item_AddOption(item, opt, VLC_INPUT_OPTION_TRUSTED);
             free(opt);
@@ -562,6 +797,37 @@ static int ReadDir(stream_t *access, input_item_node_t *node)
             ON_EMPTY(description, vlc_meta_Get(m, vlc_meta_Description));
         }
 
+        if(sys->mbrecord && sys->mbrecord->i_release)
+        {
+            musicbrainz_release_t *r = sys->mbrecord->p_releases;
+            for(size_t j=0; j<r->i_tracks; j++)
+            {
+                if(r->p_tracks[j].i_index == (unsigned)i + 1)
+                {
+                    if (r->p_tracks[j].psz_title)
+                    {
+                        ON_EMPTY(title, r->p_tracks[j].psz_title);
+                        ON_EMPTY(artist, r->p_tracks[j].psz_artist);
+                    }
+                    break;
+                }
+            }
+            ON_EMPTY(album, r->psz_title);
+            if(NONEMPTY(r->psz_artist))
+            {
+                artist = r->psz_artist;
+                input_item_SetAlbumArtist(item, r->psz_artist);
+            }
+            if(year == 0 && r->psz_date)
+            {
+                unsigned i_year;
+                if(sscanf(r->psz_date, "%4d", &i_year) == 1)
+                    year = i_year;
+            }
+            if(NONEMPTY(r->psz_coverart_url))
+                input_item_SetArtworkURL(item, r->psz_coverart_url);
+        }
+
         if (NONEMPTY(title))
         {
             input_item_SetName(item, title);
@@ -589,8 +855,10 @@ static int ReadDir(stream_t *access, input_item_node_t *node)
         }
 
         char num[4];
-        snprintf(num, sizeof (num), "%d", i + 1);
-        input_item_SetTrackNum(item, num);
+        if(snprintf(num, sizeof (num), "%u", i + 1) < 4)
+            input_item_SetTrackNum(item, num);
+        snprintf(num, sizeof (num), "%u", p_toc->i_tracks);
+        input_item_SetTrackTotal(item, num);
 
         input_item_node_AppendItem(node, item);
         input_item_Release(item);
@@ -634,31 +902,21 @@ static int AccessOpen(vlc_object_t *obj)
     }
 
     sys->vcddev = dev;
-    sys->p_sectors = NULL;
-
-    sys->titles = ioctl_GetTracksMap(obj, dev, &sys->p_sectors);
-    if (sys->titles < 0)
+    sys->p_toc = ioctl_GetTOC(obj, dev, true);
+    if (sys->p_toc == NULL)
     {
         msg_Err(obj, "cannot count tracks");
         goto error;
     }
 
-    if (sys->titles == 0)
+    sys->i_cdda_tracks = TOC_GetAudioRange(sys->p_toc, &sys->i_cdda_first, &sys->i_cdda_last);
+    if (sys->i_cdda_tracks == 0)
     {
         msg_Err(obj, "no audio tracks found");
+        vcddev_toc_Free(sys->p_toc);
         goto error;
     }
 
-#ifdef HAVE_LIBCDDB
-    msg_Dbg(obj, "retrieving metadata with CDDB");
-
-    sys->cddb = GetCDDBInfo(obj, sys->titles, sys->p_sectors);
-    if (sys->cddb != NULL)
-        msg_Dbg(obj, "disc ID: 0x%08x", cddb_disc_get_discid(sys->cddb));
-    else
-        msg_Dbg(obj, "CDDB failure");
-#endif
-
     if (ioctl_GetCdText(obj, dev, &sys->cdtextv, &sys->cdtextc))
     {
         msg_Dbg(obj, "CD-TEXT information missing");
@@ -666,6 +924,20 @@ static int AccessOpen(vlc_object_t *obj)
         sys->cdtextc = 0;
     }
 
+    sys->mbrecord = NULL;
+    sys->cddb = NULL;
+
+    if(var_InheritBool(obj, "metadata-network-access"))
+    {
+        sys->mbrecord = GetMusicbrainzInfo(obj, sys->p_toc, sys->i_cdda_tracks,
+                                           sys->i_cdda_first, sys->i_cdda_last );
+#ifdef HAVE_LIBCDDB
+        if(!sys->mbrecord)
+            sys->cddb = GetCDDBInfo(obj, sys->p_toc);
+#endif
+    }
+    else msg_Dbg(obj, "album art policy set to manual: not fetching");
+
     access->p_sys = sys;
     access->pf_read = NULL;
     access->pf_block = NULL;
@@ -675,7 +947,6 @@ static int AccessOpen(vlc_object_t *obj)
     return VLC_SUCCESS;
 
 error:
-    free(sys->p_sectors);
     ioctl_Close(obj, dev);
     return VLC_EGENERIC;
 }
@@ -698,8 +969,11 @@ static void AccessClose(vlc_object_t *obj)
         cddb_disc_destroy(sys->cddb);
 #endif
 
-    free(sys->p_sectors);
     ioctl_Close(obj, sys->vcddev);
+
+    if(sys->mbrecord)
+        musicbrainz_recording_release(sys->mbrecord);
+    vcddev_toc_Free(sys->p_toc);
 }
 
 /*****************************************************************************
@@ -742,6 +1016,9 @@ vlc_module_begin ()
     add_integer( "cdda-last-sector", -1, NULL, NULL, true )
         change_volatile ()
 
+    add_string( "musicbrainz-server", MUSICBRAINZ_DEFAULT_SERVER,
+                N_( "Musicbrainz Server" ),
+                N_( "Address of the musicbrainz server to use." ), true )
 #ifdef HAVE_LIBCDDB
     add_string( "cddb-server", "freedb.videolan.org", N_( "CDDB Server" ),
             N_( "Address of the CDDB server to use." ), true )


=====================================
modules/access/vcd/cdrom.c
=====================================
@@ -140,8 +140,8 @@ exit_free:
 #   error FIXME
 #endif
 
-#include "cdrom_internals.h"
 #include "cdrom.h"
+#include "cdrom_internals.h"
 
 /*****************************************************************************
  * ioctl_Open: Opens a VCD device or file and returns an opaque handle
@@ -165,6 +165,7 @@ vcddev_t *ioctl_Open( vlc_object_t *p_this, const char *psz_dev )
         return NULL;
     p_vcddev->i_vcdimage_handle = -1;
     p_vcddev->psz_dev = NULL;
+    memset( &p_vcddev->toc, 0, sizeof(p_vcddev->toc) );
     b_is_file = 1;
 
     /*
@@ -257,14 +258,15 @@ void ioctl_Close( vlc_object_t * p_this, vcddev_t *p_vcddev )
 }
 
 /*****************************************************************************
- * ioctl_GetTracksMap: Read the Table of Content, fill in the pp_sectors map
- *                     if pp_sectors is not null and return the number of
- *                     tracks available.
+ * ioctl_GetTOC: Read the Table of Content, fill in the p_sectors map
+ *               if b_fill_sector_info is true.
  *****************************************************************************/
-int ioctl_GetTracksMap( vlc_object_t *p_this, const vcddev_t *p_vcddev,
-                        int **pp_sectors )
+vcddev_toc_t * ioctl_GetTOC( vlc_object_t *p_this, const vcddev_t *p_vcddev,
+                             bool b_fill_sectorinfo )
 {
-    int i_tracks = 0;
+    vcddev_toc_t *p_toc = calloc(1, sizeof(*p_toc));
+    if(!p_toc)
+        return NULL;
 
     if( p_vcddev->i_vcdimage_handle != -1 )
     {
@@ -272,18 +274,22 @@ int ioctl_GetTracksMap( vlc_object_t *p_this, const vcddev_t *p_vcddev,
          *  vcd image mode
          */
 
-        i_tracks = p_vcddev->i_tracks;
+        *p_toc = p_vcddev->toc;
+        p_toc->p_sectors = NULL;
 
-        if( pp_sectors )
+        if( b_fill_sectorinfo )
         {
-            *pp_sectors = calloc( i_tracks + 1, sizeof(**pp_sectors) );
-            if( *pp_sectors == NULL )
-                return 0;
-            memcpy( *pp_sectors, p_vcddev->p_sectors,
-                    (i_tracks + 1) * sizeof(**pp_sectors) );
+            p_toc->p_sectors = calloc( p_toc->i_tracks + 1, sizeof(*p_toc->p_sectors) );
+            if( p_toc->p_sectors == NULL )
+            {
+                free( p_toc );
+                return NULL;
+            }
+            memcpy( p_toc->p_sectors, p_vcddev->toc.p_sectors,
+                    (p_toc->i_tracks + 1) * sizeof(*p_toc->p_sectors) );
         }
 
-        return i_tracks;
+        return p_toc;
     }
     else
     {
@@ -300,28 +306,33 @@ int ioctl_GetTracksMap( vlc_object_t *p_this, const vcddev_t *p_vcddev,
         if( ( pTOC = darwin_getTOC( p_this, p_vcddev ) ) == NULL )
         {
             msg_Err( p_this, "failed to get the TOC" );
-            return 0;
+            vcddev_toc_Free( p_toc );
+            return NULL;
         }
 
         i_descriptors = CDTOCGetDescriptorCount( pTOC );
-        i_tracks = darwin_getNumberOfTracks( pTOC, i_descriptors );
+        p_toc->i_tracks = darwin_getNumberOfTracks( pTOC, i_descriptors,
+                                                    &p_toc->i_first_track,
+                                                    &p_toc->i_last_track );
 
-        if( pp_sectors )
+        if( b_fill_sectorinfo )
         {
             int i, i_leadout = -1;
             CDTOCDescriptor *pTrackDescriptors;
             u_char track;
 
-            *pp_sectors = calloc( i_tracks + 1, sizeof(**pp_sectors) );
-            if( *pp_sectors == NULL )
+            p_toc->p_sectors = calloc( p_toc->i_tracks + 1,
+                                       sizeof(*p_toc->p_sectors) );
+            if( p_toc->p_sectors == NULL )
             {
+                vcddev_toc_Free( p_toc );
                 darwin_freeTOC( pTOC );
-                return 0;
+                return NULL;
             }
 
             pTrackDescriptors = pTOC->descriptors;
 
-            for( i_tracks = 0, i = 0; i < i_descriptors; i++ )
+            for( p_toc->i_tracks = 0, i = 0; i < i_descriptors; i++ )
             {
                 track = pTrackDescriptors[i].point;
 
@@ -331,20 +342,21 @@ int ioctl_GetTracksMap( vlc_object_t *p_this, const vcddev_t *p_vcddev,
                 if( track > CD_MAX_TRACK_NO || track < CD_MIN_TRACK_NO )
                     continue;
 
-                (*pp_sectors)[i_tracks++] =
+                p_toc->p_sectors[p_toc->i_tracks].i_control = pTrackDescriptors[i].control;
+                p_toc->p_sectors[p_toc->i_tracks++].i_lba =
                     CDConvertMSFToLBA( pTrackDescriptors[i].p );
             }
 
             if( i_leadout == -1 )
             {
                 msg_Err( p_this, "leadout not found" );
-                free( *pp_sectors );
+                vcddev_toc_Free( p_toc );
                 darwin_freeTOC( pTOC );
-                return 0;
+                return NULL;
             }
 
             /* set leadout sector */
-            (*pp_sectors)[i_tracks] =
+            p_toc->p_sectors[p_toc->i_tracks].i_lba =
                 CDConvertMSFToLBA( pTrackDescriptors[i_leadout].p );
         }
 
@@ -359,24 +371,31 @@ int ioctl_GetTracksMap( vlc_object_t *p_this, const vcddev_t *p_vcddev,
                              &dwBytesReturned, NULL ) == 0 )
         {
             msg_Err( p_this, "could not read TOCHDR" );
-            return 0;
+            vcddev_toc_Free( p_toc );
+            return NULL;
         }
 
-        i_tracks = cdrom_toc.LastTrack - cdrom_toc.FirstTrack + 1;
+        p_toc->i_tracks = cdrom_toc.LastTrack - cdrom_toc.FirstTrack + 1;
+        p_toc->i_first_track = cdrom_toc.FirstTrack;
+        p_toc->i_last_track = cdrom_toc.LastTrack;
 
-        if( pp_sectors )
+        if( b_fill_sectorinfo )
         {
-            *pp_sectors = calloc( i_tracks + 1, sizeof(**pp_sectors) );
-            if( *pp_sectors == NULL )
-                return 0;
+            p_toc->p_sectors = calloc( p_toc->i_tracks + 1, sizeof(p_toc->p_sectors) );
+            if( p_toc->p_sectors == NULL )
+            {
+                vcddev_toc_Free( p_toc );
+                return NULL;
+            }
 
-            for( int i = 0 ; i <= i_tracks ; i++ )
+            for( int i = 0 ; i <= p_toc->i_tracks ; i++ )
             {
-                (*pp_sectors)[ i ] = MSF_TO_LBA2(
+                p_toc->p_sectors[ i ].i_control = cdrom_toc.TrackData[i].Control;
+                p_toc->p_sectors[ i ].i_lba = MSF_TO_LBA2(
                                            cdrom_toc.TrackData[i].Address[1],
                                            cdrom_toc.TrackData[i].Address[2],
                                            cdrom_toc.TrackData[i].Address[3] );
-                msg_Dbg( p_this, "p_sectors: %i, %i", i, (*pp_sectors)[i]);
+                msg_Dbg( p_this, "p_sectors: %i, %i", i, p_toc->p_sectors[i].i_lba);
              }
         }
 
@@ -398,19 +417,24 @@ int ioctl_GetTracksMap( vlc_object_t *p_this, const vcddev_t *p_vcddev,
             return 0;
         }
 
-        i_tracks = tochdr.last_track - tochdr.first_track + 1;
+        p_toc->i_tracks = tochdr.last_track - tochdr.first_track + 1;
+        p_toc->i_first_track = tochdr.first_track;
+        p_toc->i_last_track = tochdr.last_track;
 
-        if( pp_sectors )
+        if( b_fill_sectorinfo )
         {
             cdrom_get_track_t get_track = {{'C', 'D', '0', '1'}, };
             cdrom_track_t track;
             int i;
 
-            *pp_sectors = calloc( i_tracks + 1, sizeof(**pp_sectors) );
-            if( *pp_sectors == NULL )
-                return 0;
+            p_toc->p_sectors = calloc( p_toc->i_tracks + 1, sizeof(*p_toc->p_sectors) );
+            if( *p_toc->p_sectors == NULL )
+            {
+                vcddev_toc_Free( p_toc );
+                return NULL;
+            }
 
-            for( i = 0 ; i < i_tracks ; i++ )
+            for( i = 0 ; i < p_toc->i_tracks ; i++ )
             {
                 get_track.track = tochdr.first_track + i;
                 rc = DosDevIOCtl( p_vcddev->hcd, IOCTL_CDROMAUDIO,
@@ -421,22 +445,23 @@ int ioctl_GetTracksMap( vlc_object_t *p_this, const vcddev_t *p_vcddev,
                 {
                     msg_Err( p_this, "could not read %d track",
                              get_track.track );
-                    return 0;
+                    vcddev_toc_Free( p_toc );
+                    return NULL;
                 }
 
-                (*pp_sectors)[ i ] = MSF_TO_LBA2(
+                p_toc->p_sectors[ i ].i_lba = MSF_TO_LBA2(
                                        track.start.minute,
                                        track.start.second,
                                        track.start.frame );
-                msg_Dbg( p_this, "p_sectors: %i, %i", i, (*pp_sectors)[i]);
+                msg_Dbg( p_this, "p_sectors: %i, %i", i, p_toc->p_sectors[i].i_lba);
             }
 
             /* for lead-out track */
-            (*pp_sectors)[ i ] = MSF_TO_LBA2(
+            p_toc->p_sectors[ i ].i_lba = MSF_TO_LBA2(
                                    tochdr.lead_out.minute,
                                    tochdr.lead_out.second,
                                    tochdr.lead_out.frame );
-            msg_Dbg( p_this, "p_sectors: %i, %i", i, (*pp_sectors)[i]);
+            msg_Dbg( p_this, "p_sectors: %i, %i", i, p_toc->p_sectors[i].i_lba);
         }
 
 #elif defined( HAVE_IOC_TOC_HEADER_IN_SYS_CDIO_H ) \
@@ -448,27 +473,33 @@ int ioctl_GetTracksMap( vlc_object_t *p_this, const vcddev_t *p_vcddev,
             == -1 )
         {
             msg_Err( p_this, "could not read TOCHDR" );
-            return 0;
+            vcddev_toc_Free( p_toc );
+            return NULL;
         }
 
-        i_tracks = tochdr.ending_track - tochdr.starting_track + 1;
+        p_toc->i_tracks = tochdr.ending_track - tochdr.starting_track + 1;
+        p_toc->i_first_track = tochdr.starting_track;
+        p_toc->i_last_track = tochdr.ending_track;
 
-        if( pp_sectors )
+        if( b_fill_sectorinfo )
         {
-             *pp_sectors = calloc( i_tracks + 1, sizeof(**pp_sectors) );
-             if( *pp_sectors == NULL )
-                 return 0;
+             p_toc->p_sectors = calloc( p_toc->i_tracks + 1, sizeof(*p_toc->p_sectors) );
+             if( p_toc->p_sectors == NULL )
+             {
+                 vcddev_toc_Free( p_toc );
+                 return NULL;
+             }
 
              toc_entries.address_format = CD_LBA_FORMAT;
              toc_entries.starting_track = 0;
-             toc_entries.data_len = ( i_tracks + 1 ) *
+             toc_entries.data_len = ( p_toc->i_tracks + 1 ) *
                                         sizeof( struct cd_toc_entry );
              toc_entries.data = (struct cd_toc_entry *)
                                     malloc( toc_entries.data_len );
              if( toc_entries.data == NULL )
              {
-                 free( *pp_sectors );
-                 return 0;
+                 vcddev_toc_Free( p_toc );
+                 return NULL;
              }
 
              /* Read the TOC */
@@ -476,19 +507,19 @@ int ioctl_GetTracksMap( vlc_object_t *p_this, const vcddev_t *p_vcddev,
                         &toc_entries ) == -1 )
              {
                  msg_Err( p_this, "could not read the TOC" );
-                 free( *pp_sectors );
                  free( toc_entries.data );
-                 return 0;
+                 vcddev_toc_Free( p_toc );
+                 return NULL;
              }
 
              /* Fill the p_sectors structure with the track/sector matches */
-             for( int i = 0 ; i <= i_tracks ; i++ )
+             for( int i = 0 ; i <= p_toc->i_tracks ; i++ )
              {
 #if defined( HAVE_SCSIREQ_IN_SYS_SCSIIO_H )
                  /* FIXME: is this ok? */
-                 (*pp_sectors)[ i ] = toc_entries.data[i].addr.lba;
+                 p_toc->p_sectors[ i ].i_lba = toc_entries.data[i].addr.lba;
 #else
-                 (*pp_sectors)[ i ] = ntohl( toc_entries.data[i].addr.lba );
+                 p_toc->p_sectors[ i ].i_lba = ntohl( toc_entries.data[i].addr.lba );
 #endif
              }
         }
@@ -501,38 +532,46 @@ int ioctl_GetTracksMap( vlc_object_t *p_this, const vcddev_t *p_vcddev,
             == -1 )
         {
             msg_Err( p_this, "could not read TOCHDR" );
-            return 0;
+            free( p_toc );
+            return NULL;
         }
 
-        i_tracks = tochdr.cdth_trk1 - tochdr.cdth_trk0 + 1;
+        p_toc->i_tracks = tochdr.cdth_trk1 - tochdr.cdth_trk0 + 1;
+        p_toc->i_first_track = tochdr.cdth_trk0;
+        p_toc->i_last_track = tochdr.cdth_trk1;
 
-        if( pp_sectors )
+        if( b_fill_sectorinfo )
         {
-            *pp_sectors = calloc( i_tracks + 1, sizeof(**pp_sectors) );
-            if( *pp_sectors == NULL )
-                return 0;
+            p_toc->p_sectors = calloc( p_toc->i_tracks + 1, sizeof(*p_toc->p_sectors) );
+            if( p_toc->p_sectors == NULL )
+            {
+                free( p_toc );
+                return NULL;
+            }
 
             /* Fill the p_sectors structure with the track/sector matches */
-            for( int i = 0 ; i <= i_tracks ; i++ )
+            for( int i = 0 ; i <= p_toc->i_tracks ; i++ )
             {
                 tocent.cdte_format = CDROM_LBA;
                 tocent.cdte_track =
-                    ( i == i_tracks ) ? CDROM_LEADOUT : tochdr.cdth_trk0 + i;
+                    ( i == p_toc->i_tracks ) ? CDROM_LEADOUT : tochdr.cdth_trk0 + i;
 
                 if( ioctl( p_vcddev->i_device_handle, CDROMREADTOCENTRY,
                            &tocent ) == -1 )
                 {
                     msg_Err( p_this, "could not read TOCENTRY" );
-                    free( *pp_sectors );
-                    return 0;
+                    free( p_toc->p_sectors );
+                    free( p_toc );
+                    return NULL;
                 }
 
-                (*pp_sectors)[ i ] = tocent.cdte_addr.lba;
+                p_toc->p_sectors[ i ].i_lba = tocent.cdte_addr.lba;
+                p_toc->p_sectors[ i ].i_control = tocent.cdte_ctrl;
             }
         }
 #endif
 
-        return i_tracks;
+        return p_toc;
     }
 }
 
@@ -767,7 +806,7 @@ static int OpenVCDImage( vlc_object_t * p_this, const char *psz_dev,
     char *psz_vcdfile = NULL;
     char *psz_cuefile = NULL;
     FILE *cuefile     = NULL;
-    int *p_sectors    = NULL;
+    vcddev_toc_t *p_toc = &p_vcddev->toc;
     char line[1024];
     bool b_found      = false;
 
@@ -857,9 +896,9 @@ static int OpenVCDImage( vlc_object_t * p_this, const char *psz_dev,
 
     /* Try to parse the i_tracks and p_sectors info so we can just forget
      * about the cuefile */
-    size_t i_tracks = 0;
+    p_toc->i_tracks = 0;
 
-    while( fgets( line, 1024, cuefile ) && i_tracks < INT_MAX-1 )
+    while( fgets( line, 1024, cuefile ) && p_toc->i_tracks < INT_MAX-1 )
     {
         /* look for a TRACK line */
         char psz_dummy[10];
@@ -875,35 +914,42 @@ static int OpenVCDImage( vlc_object_t * p_this, const char *psz_dev,
                          &i_min, &i_sec, &i_frame ) != 4) || (i_num != 1) )
                 continue;
 
-            int *buf = realloc (p_sectors, (i_tracks + 1) * sizeof (*buf));
+            vcddev_sector_t *buf = realloc (p_toc->p_sectors,
+                                            (p_toc->i_tracks + 1) * sizeof (*buf));
             if (buf == NULL)
                 goto error;
-            p_sectors = buf;
-            p_sectors[i_tracks] = MSF_TO_LBA(i_min, i_sec, i_frame);
+            p_toc->p_sectors = buf;
+            p_toc->p_sectors[p_toc->i_tracks].i_lba = MSF_TO_LBA(i_min, i_sec, i_frame);
+            p_toc->p_sectors[p_toc->i_tracks].i_control = 0x00;
             msg_Dbg( p_this, "vcd track %i begins at sector:%i",
-                     (int)i_tracks, (int)p_sectors[i_tracks] );
-            i_tracks++;
+                     p_toc->i_tracks, p_toc->p_sectors[p_toc->i_tracks].i_lba );
+            p_toc->i_tracks++;
             break;
         }
     }
 
     /* fill in the last entry */
-    int *buf = realloc (p_sectors, (i_tracks + 1) * sizeof (*buf));
+    vcddev_sector_t *buf = realloc (p_toc->p_sectors,
+                                    (p_toc->i_tracks + 1) * sizeof (*buf));
     if (buf == NULL)
         goto error;
-    p_sectors = buf;
-    p_sectors[i_tracks] = lseek(p_vcddev->i_vcdimage_handle, 0, SEEK_END)
-                                 / VCD_SECTOR_SIZE;
+    p_toc->p_sectors = buf;
+    p_toc->p_sectors[p_toc->i_tracks].i_lba =
+            lseek(p_vcddev->i_vcdimage_handle, 0, SEEK_END) / VCD_SECTOR_SIZE;
+    p_toc->p_sectors[p_toc->i_tracks].i_control = 0x00;
     msg_Dbg( p_this, "vcd track %i, begins at sector:%i",
-             (int)i_tracks, (int)p_sectors[i_tracks] );
-    p_vcddev->i_tracks = ++i_tracks;
-    p_vcddev->p_sectors = p_sectors;
-    p_sectors = NULL;
+             p_toc->i_tracks, p_toc->p_sectors[p_toc->i_tracks].i_lba );
+    p_toc->i_tracks++;
+    p_toc->i_first_track = 1;
+    p_toc->i_last_track = p_toc->i_tracks;
     i_ret = 0;
+    goto end;
 
 error:
+    free( p_toc->p_sectors );
+    memset( p_toc, 0, sizeof(*p_toc) );
+end:
     if( cuefile ) fclose( cuefile );
-    free( p_sectors );
     free( psz_cuefile );
     free( psz_vcdfile );
 
@@ -921,7 +967,7 @@ static void CloseVCDImage( vlc_object_t * p_this, vcddev_t *p_vcddev )
     else
         return;
 
-    free( p_vcddev->p_sectors );
+    free( p_vcddev->toc.p_sectors );
 }
 
 #if defined( __APPLE__ )
@@ -1028,11 +1074,16 @@ static CDTOC *darwin_getTOC( vlc_object_t * p_this, const vcddev_t *p_vcddev )
 
 /****************************************************************************
  * darwin_getNumberOfTracks: get number of tracks in TOC
+ *                           and first and last CDDA ones
  ****************************************************************************/
-static int darwin_getNumberOfTracks( CDTOC *pTOC, int i_descriptors )
+static int darwin_getNumberOfTracks( CDTOC *pTOC, int i_descriptors,
+                                     int *pi_first_track,
+                                     int *pi_last_track )
 {
     u_char track;
     int i, i_tracks = 0;
+    int i_min = CD_MAX_TRACK_NO;
+    int i_max = CD_MIN_TRACK_NO;
     CDTOCDescriptor *pTrackDescriptors = NULL;
 
     pTrackDescriptors = (CDTOCDescriptor *)pTOC->descriptors;
@@ -1044,9 +1095,23 @@ static int darwin_getNumberOfTracks( CDTOC *pTOC, int i_descriptors )
         if( track > CD_MAX_TRACK_NO || track < CD_MIN_TRACK_NO )
             continue;
 
+        if( pTrackDescriptors[i].adr == 0x01 /* kCDSectorTypeCDDA */ )
+        {
+            i_min = __MIN(i_min, track);
+            i_max = __MAX(i_max, track);
+        }
+
         i_tracks++;
     }
 
+    if( i_max < i_min )
+        *pi_first_track = *pi_last_track = 0;
+    else
+    {
+        *pi_first_track = i_min;
+        *pi_last_track = i_max;
+    }
+
     return( i_tracks );
 }
 #endif /* __APPLE__ */


=====================================
modules/access/vcd/cdrom.h
=====================================
@@ -38,6 +38,12 @@ enum {
 #define CD_ROM_XA_MODE2_F1_DATA_SIZE 2048
 #define CD_ROM_XA_MODE2_F2_DATA_SIZE 2324
 
+#define CD_ROM_XA_FRAMES   75
+#define CD_ROM_XA_INTERVAL ((60 + 90 + 2) * CD_ROM_XA_FRAMES)
+
+/* Subcode control flag */
+#define CD_ROM_DATA_FLAG    0x04
+
 /* size of a CD sector */
 #define CD_SECTOR_SIZE      CD_ROM_MODE1_DATA_SIZE
 
@@ -74,6 +80,36 @@ static inline int MSF_TO_LBA2(uint8_t min, uint8_t sec, uint8_t frame)
     (uint8_t)((uint8_t)(0xf & (uint8_t)i)+((uint8_t)10*((uint8_t)i >> 4)))
 
 typedef struct vcddev_s vcddev_t;
+typedef struct
+{
+    int i_lba;
+    int i_control;
+} vcddev_sector_t;
+
+typedef struct
+{
+    int i_tracks;
+    vcddev_sector_t *p_sectors;
+    int i_first_track;
+    int i_last_track;
+} vcddev_toc_t;
+
+static inline vcddev_toc_t * vcddev_toc_New( void )
+{
+    return calloc(1, sizeof(vcddev_toc_t));
+}
+
+static inline void vcddev_toc_Reset( vcddev_toc_t *toc )
+{
+    free(toc->p_sectors);
+    memset(toc, 0, sizeof(*toc));
+}
+
+static inline void vcddev_toc_Free( vcddev_toc_t *toc )
+{
+    free(toc->p_sectors);
+    free(toc);
+}
 
 /*****************************************************************************
  * structure to store minute/second/frame locations
@@ -111,7 +147,7 @@ typedef struct entries_sect_s
  *****************************************************************************/
 vcddev_t *ioctl_Open         ( vlc_object_t *, const char * );
 void      ioctl_Close        ( vlc_object_t *, vcddev_t * );
-int       ioctl_GetTracksMap ( vlc_object_t *, const vcddev_t *, int ** );
+vcddev_toc_t * ioctl_GetTOC  ( vlc_object_t *, const vcddev_t *, bool );
 int       ioctl_ReadSectors  ( vlc_object_t *, const vcddev_t *,
                                int, uint8_t *, int, int );
 


=====================================
modules/access/vcd/cdrom_internals.h
=====================================
@@ -31,8 +31,7 @@ struct vcddev_s
 
     /* Section used in vcd image mode */
     int    i_vcdimage_handle;                   /* vcd image file descriptor */
-    int    i_tracks;                          /* number of tracks of the vcd */
-    int    *p_sectors;                           /* tracks layout on the vcd */
+    vcddev_toc_t toc;                           /* tracks layout on the vcd */
 
     /* Section used in vcd device mode */
 
@@ -181,7 +180,7 @@ static void   CloseVCDImage( vlc_object_t *, struct vcddev_s * );
 
 #if defined( __APPLE__ )
 static CDTOC *darwin_getTOC( vlc_object_t *, const struct vcddev_s * );
-static int    darwin_getNumberOfTracks( CDTOC *, int );
+static int    darwin_getNumberOfTracks( CDTOC *, int, int *, int * );
 
 #elif defined( _WIN32 )
 static int    win32_vcd_open( vlc_object_t *, const char *, struct vcddev_s *);


=====================================
modules/access/vcd/vcd.c
=====================================
@@ -69,7 +69,8 @@ struct access_sys_t
     uint64_t    offset;
 
     /* Title infos */
-    int           i_titles;
+    vcddev_toc_t *p_toc;
+
     struct
     {
         uint64_t *seekpoints;
@@ -77,9 +78,7 @@ struct access_sys_t
     } titles[99];                        /* No more that 99 track in a vcd ? */
     int         i_current_title;
     unsigned    i_current_seekpoint;
-
     int         i_sector;                                  /* Current Sector */
-    int         *p_sectors;                                 /* Track sectors */
 };
 
 static block_t *Block( stream_t *, bool * );
@@ -152,26 +151,28 @@ static int Open( vlc_object_t *p_this )
         p_sys->titles[i].seekpoints = NULL;
 
     /* We read the Table Of Content information */
-    p_sys->i_titles = ioctl_GetTracksMap( VLC_OBJECT(p_access),
-                                          p_sys->vcddev, &p_sys->p_sectors );
-    if( p_sys->i_titles < 0 )
+    p_sys->p_toc = ioctl_GetTOC( VLC_OBJECT(p_access), p_sys->vcddev, true );
+    if( p_sys->p_toc == NULL )
     {
         msg_Err( p_access, "unable to count tracks" );
         goto error;
     }
-    else if( p_sys->i_titles <= 1 )
+    else if( p_sys->p_toc->i_tracks <= 1 )
     {
+        vcddev_toc_Free( p_sys->p_toc );
+        p_sys->p_toc = NULL;
         msg_Err( p_access, "no movie tracks found" );
         goto error;
     }
 
     /* The first title isn't usable */
-    p_sys->i_titles--;
+#define USABLE_TITLES(a) (a - 1)
+    //p_sys->i_titles--;
 
-    for( int i = 0; i < p_sys->i_titles; i++ )
+    for( int i = 0; i < USABLE_TITLES(p_sys->p_toc->i_tracks); i++ )
     {
-        msg_Dbg( p_access, "title[%d] start=%d", i, p_sys->p_sectors[1+i] );
-        msg_Dbg( p_access, "title[%d] end=%d", i, p_sys->p_sectors[i+2] );
+        msg_Dbg( p_access, "title[%d] start=%d", i, p_sys->p_toc->p_sectors[1+i].i_lba );
+        msg_Dbg( p_access, "title[%d] end=%d", i, p_sys->p_toc->p_sectors[i+2].i_lba );
     }
 
     /* Map entry points into chapters */
@@ -181,12 +182,12 @@ static int Open( vlc_object_t *p_this )
     }
 
     /* Starting title/chapter and sector */
-    if( i_title >= p_sys->i_titles )
+    if( i_title > USABLE_TITLES(p_sys->p_toc->i_tracks) )
         i_title = 0;
     if( (unsigned)i_chapter >= p_sys->titles[i_title].count )
         i_chapter = 0;
 
-    p_sys->i_sector = p_sys->p_sectors[1+i_title];
+    p_sys->i_sector = p_sys->p_toc->p_sectors[1+i_title].i_lba;
     if( i_chapter > 0 )
         p_sys->i_sector += p_sys->titles[i_title].seekpoints[i_chapter]
                            / VCD_DATA_SIZE;
@@ -199,7 +200,7 @@ static int Open( vlc_object_t *p_this )
 
     p_sys->i_current_title = i_title;
     p_sys->i_current_seekpoint = i_chapter;
-    p_sys->offset = (uint64_t)(p_sys->i_sector - p_sys->p_sectors[1+i_title]) *
+    p_sys->offset = (uint64_t)(p_sys->i_sector - p_sys->p_toc->p_sectors[1+i_title].i_lba) *
                                VCD_DATA_SIZE;
 
     return VLC_SUCCESS;
@@ -221,6 +222,8 @@ static void Close( vlc_object_t *p_this )
     for( size_t i = 0; i < ARRAY_SIZE(p_sys->titles); i++ )
         free( p_sys->titles[i].seekpoints );
 
+    vcddev_toc_Free( p_sys->p_toc );
+
     ioctl_Close( p_this, p_sys->vcddev );
     free( p_sys );
 }
@@ -248,8 +251,8 @@ static int Control( stream_t *p_access, int i_query, va_list args )
             int i = p_sys->i_current_title;
 
             *va_arg( args, uint64_t * ) =
-                (p_sys->p_sectors[i + 2] - p_sys->p_sectors[i + 1])
-                               * (uint64_t)VCD_DATA_SIZE;
+                (p_sys->p_toc->p_sectors[i + 2].i_lba -
+                 p_sys->p_toc->p_sectors[i + 1].i_lba) * (uint64_t)VCD_DATA_SIZE;
             break;
         }
 
@@ -266,11 +269,12 @@ static int Control( stream_t *p_access, int i_query, va_list args )
         case STREAM_GET_TITLE_INFO:
             ppp_title = va_arg( args, input_title_t*** );
             /* Duplicate title infos */
-            *ppp_title = vlc_alloc( p_sys->i_titles, sizeof(input_title_t *) );
+            *ppp_title = vlc_alloc( USABLE_TITLES(p_sys->p_toc->i_tracks),
+                                    sizeof(input_title_t *) );
             if (!*ppp_title)
                 return VLC_ENOMEM;
-            *va_arg( args, int* ) = p_sys->i_titles;
-            for( int i = 0; i < p_sys->i_titles; i++ )
+            *va_arg( args, int* ) = USABLE_TITLES(p_sys->p_toc->i_tracks);
+            for( int i = 0; i < USABLE_TITLES(p_sys->p_toc->i_tracks); i++ )
                 (*ppp_title)[i] = vlc_input_title_New();
             break;
 
@@ -297,7 +301,7 @@ static int Control( stream_t *p_access, int i_query, va_list args )
                 p_sys->i_current_seekpoint = 0;
 
                 /* Next sector to read */
-                p_sys->i_sector = p_sys->p_sectors[1+i];
+                p_sys->i_sector = p_sys->p_toc->p_sectors[1+i].i_lba;
             }
             break;
         }
@@ -311,11 +315,11 @@ static int Control( stream_t *p_access, int i_query, va_list args )
             {
                 p_sys->i_current_seekpoint = i;
 
-                p_sys->i_sector = p_sys->p_sectors[1 + i_title] +
+                p_sys->i_sector = p_sys->p_toc->p_sectors[1 + i_title].i_lba +
                     p_sys->titles[i_title].seekpoints[i] / VCD_DATA_SIZE;
 
                 p_sys->offset = (uint64_t)(p_sys->i_sector -
-                    p_sys->p_sectors[1 + i_title]) * VCD_DATA_SIZE;
+                    p_sys->p_toc->p_sectors[1 + i_title].i_lba) * VCD_DATA_SIZE;
             }
             break;
         }
@@ -332,13 +336,14 @@ static int Control( stream_t *p_access, int i_query, va_list args )
 static block_t *Block( stream_t *p_access, bool *restrict eof )
 {
     access_sys_t *p_sys = p_access->p_sys;
+    const vcddev_toc_t *p_toc = p_sys->p_toc;
     int i_blocks = VCD_BLOCKS_ONCE;
     block_t *p_block;
 
     /* Check end of title */
-    while( p_sys->i_sector >= p_sys->p_sectors[p_sys->i_current_title + 2] )
+    while( p_sys->i_sector >= p_toc->p_sectors[p_sys->i_current_title + 2].i_lba )
     {
-        if( p_sys->i_current_title + 2 >= p_sys->i_titles )
+        if( p_sys->i_current_title + 2 >= USABLE_TITLES(p_toc->i_tracks) )
         {
             *eof = true;
             return NULL;
@@ -351,9 +356,9 @@ static block_t *Block( stream_t *p_access, bool *restrict eof )
 
     /* Don't read after the end of a title */
     if( p_sys->i_sector + i_blocks >=
-        p_sys->p_sectors[p_sys->i_current_title + 2] )
+        p_toc->p_sectors[p_sys->i_current_title + 2].i_lba )
     {
-        i_blocks = p_sys->p_sectors[p_sys->i_current_title + 2 ] - p_sys->i_sector;
+        i_blocks = p_toc->p_sectors[p_sys->i_current_title + 2 ].i_lba - p_sys->i_sector;
     }
 
     /* Do the actual reading */
@@ -404,12 +409,13 @@ static block_t *Block( stream_t *p_access, bool *restrict eof )
 static int Seek( stream_t *p_access, uint64_t i_pos )
 {
     access_sys_t *p_sys = p_access->p_sys;
+    const vcddev_toc_t *p_toc = p_sys->p_toc;
     int i_title = p_sys->i_current_title;
     unsigned i_seekpoint;
 
     /* Next sector to read */
     p_sys->offset = i_pos;
-    p_sys->i_sector = i_pos / VCD_DATA_SIZE + p_sys->p_sectors[i_title + 1];
+    p_sys->i_sector = i_pos / VCD_DATA_SIZE + p_toc->p_sectors[i_title + 1].i_lba;
 
     /* Update current seekpoint */
     for( i_seekpoint = 0; i_seekpoint < p_sys->titles[i_title].count; i_seekpoint++ )
@@ -434,6 +440,7 @@ static int Seek( stream_t *p_access, uint64_t i_pos )
 static int EntryPoints( stream_t *p_access )
 {
     access_sys_t *p_sys = p_access->p_sys;
+    const vcddev_toc_t *p_toc = p_sys->p_toc;
     uint8_t      sector[VCD_DATA_SIZE];
 
     entries_sect_t entries;
@@ -470,7 +477,7 @@ static int EntryPoints( stream_t *p_access )
                           BCD_TO_BIN( entries.entry[i].msf.second ),
                           BCD_TO_BIN( entries.entry[i].msf.frame  ) ));
         if( i_title < 0 ) continue;   /* Should not occur */
-        if( i_title >= p_sys->i_titles ) continue;
+        if( i_title >= USABLE_TITLES(p_toc->i_tracks) ) continue;
 
         msg_Dbg( p_access, "Entry[%d] title=%d sector=%d",
                  i, i_title, i_sector );
@@ -479,7 +486,7 @@ static int EntryPoints( stream_t *p_access )
             p_sys->titles[i_title].seekpoints,
             sizeof( uint64_t ) * (p_sys->titles[i_title].count + 1) );
         p_sys->titles[i_title].seekpoints[p_sys->titles[i_title].count++] =
-            (i_sector - p_sys->p_sectors[i_title+1]) * VCD_DATA_SIZE;
+            (i_sector - p_toc->p_sectors[i_title+1].i_lba) * VCD_DATA_SIZE;
     }
 
     return VLC_SUCCESS;


=====================================
modules/misc/Makefile.am
=====================================
@@ -19,6 +19,7 @@ misc_LTLIBRARIES += libexport_plugin.la
 libfingerprinter_plugin_la_SOURCES =  \
 	misc/webservices/acoustid.c misc/webservices/acoustid.h \
 	misc/webservices/json.c misc/webservices/json.h \
+        misc/webservices/json_helper.h \
 	misc/fingerprinter.c
 libfingerprinter_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/misc
 libfingerprinter_plugin_la_LIBADD = $(LIBM) $(LIBPTHREAD)


=====================================
modules/misc/fingerprinter.c
=====================================
@@ -309,7 +309,7 @@ static void fill_metas_with_results( fingerprint_request_t *p_r, acoustid_finger
         acoustid_result_t *p_result = & p_f->results.p_results[ i ];
         for ( unsigned int j=0 ; j < p_result->recordings.count; j++ )
         {
-            musicbrainz_recording_t *p_record = & p_result->recordings.p_recordings[ j ];
+            acoustid_mb_result_t *p_record = & p_result->recordings.p_recordings[ j ];
             vlc_meta_t *p_meta = vlc_meta_New();
             if ( p_meta )
             {
@@ -362,11 +362,13 @@ static void *Run( void *opaque )
                 DoFingerprint( p_fingerprinter, &acoustid_print, psz_uri );
                 free( psz_uri );
 
-                DoAcoustIdWebRequest( VLC_OBJECT(p_fingerprinter), &acoustid_print );
+                acoustid_config_t cfg = { .p_obj = VLC_OBJECT(p_fingerprinter),
+                                          .psz_server = NULL, .psz_apikey = NULL };
+                acoustid_lookup_fingerprint( &cfg, &acoustid_print );
                 fill_metas_with_results( p_data, &acoustid_print );
 
                 for( unsigned j = 0; j < acoustid_print.results.count; j++ )
-                     free_acoustid_result_t( &acoustid_print.results.p_results[j] );
+                     acoustid_result_release( &acoustid_print.results.p_results[j] );
                 if( acoustid_print.results.count )
                     free( acoustid_print.results.p_results );
                 free( acoustid_print.psz_fingerprint );


=====================================
modules/misc/webservices/acoustid.c
=====================================
@@ -22,19 +22,13 @@
 # include "config.h"
 #endif
 
-#include <vlc_common.h>
-#include <vlc_stream.h>
-#include <limits.h>
-#include <vlc_memory.h>
-
-#include <vlc/vlc.h>
+#include "json_helper.h"
 #include "acoustid.h"
-#include "json.h"
 
 /*****************************************************************************
  * Requests lifecycle
  *****************************************************************************/
-void free_acoustid_result_t( acoustid_result_t * r )
+void acoustid_result_release( acoustid_result_t * r )
 {
     free( r->psz_id );
     for ( unsigned int i=0; i<r->recordings.count; i++ )
@@ -45,69 +39,48 @@ void free_acoustid_result_t( acoustid_result_t * r )
     free( r->recordings.p_recordings );
 }
 
-static json_value * jsongetbyname( json_value *object, const char *psz_name )
-{
-    if ( object->type != json_object ) return NULL;
-    for ( unsigned int i=0; i < object->u.object.length; i++ )
-        if ( strcmp( object->u.object.values[i].name, psz_name ) == 0 )
-            return object->u.object.values[i].value;
-    return NULL;
-}
-
-static void parse_artists( json_value *node, musicbrainz_recording_t *record )
+static void parse_artists( const json_value *node, acoustid_mb_result_t *record )
 {
     /* take only main */
-    if ( !node || node->type != json_array || node->u.array.length < 1 ) return;
-    json_value *artistnode = node->u.array.values[ 0 ];
-    json_value *value = jsongetbyname( artistnode, "name" );
-    if ( value && value->type == json_string )
-        record->psz_artist = strdup( value->u.string.ptr );
+    if ( !node || node->type != json_array || node->u.array.length < 1 )
+        return;
+    record->psz_artist = json_dupstring( node->u.array.values[ 0 ], "name" );
 }
 
-static void parse_recordings( vlc_object_t *p_obj, json_value *node, acoustid_result_t *p_result )
+static void parse_recordings( vlc_object_t *p_obj, const json_value *node, acoustid_result_t *p_result )
 {
     if ( !node || node->type != json_array ) return;
-    p_result->recordings.p_recordings = calloc( node->u.array.length, sizeof(musicbrainz_recording_t) );
+    p_result->recordings.p_recordings = calloc( node->u.array.length, sizeof(acoustid_mb_result_t) );
     if ( ! p_result->recordings.p_recordings ) return;
     p_result->recordings.count = node->u.array.length;
 
     for( unsigned int i=0; i<node->u.array.length; i++ )
     {
-        musicbrainz_recording_t *record = & p_result->recordings.p_recordings[ i ];
-        json_value *recordnode = node->u.array.values[ i ];
-        if ( !recordnode || recordnode->type != json_object ) break;
-        json_value *value = jsongetbyname( recordnode, "title" );
-        if ( value && value->type == json_string )
-            record->psz_title = strdup( value->u.string.ptr );
-        value = jsongetbyname( recordnode, "id" );
+        acoustid_mb_result_t *record = & p_result->recordings.p_recordings[ i ];
+        const json_value *recordnode = node->u.array.values[ i ];
+        if ( !recordnode || recordnode->type != json_object )
+            break;
+        record->psz_title = json_dupstring( recordnode, "title" );
+        const json_value *value = json_getbyname( recordnode, "id" );
         if ( value && value->type == json_string )
         {
             size_t i_len = strlen( value->u.string.ptr );
             i_len = __MIN( i_len, MB_ID_SIZE );
             memcpy( record->s_musicbrainz_id, value->u.string.ptr, i_len );
         }
-        parse_artists( jsongetbyname( recordnode, "artists" ), record );
-        msg_Dbg( p_obj, "recording %d title %s %36s %s", i, record->psz_title, record->s_musicbrainz_id, record->psz_artist );
+        parse_artists( json_getbyname( recordnode, "artists" ), record );
+        msg_Dbg( p_obj, "recording %d title %s %36s %s", i, record->psz_title,
+                 record->s_musicbrainz_id, record->psz_artist );
     }
 }
 
-static bool ParseJson( vlc_object_t *p_obj, const char *psz_buffer, acoustid_results_t *p_results )
+static bool ParseJson( vlc_object_t *p_obj, const void *p_buffer, acoustid_results_t *p_results )
 {
-    json_settings settings;
-    char psz_error[128];
-    memset (&settings, 0, sizeof (json_settings));
-    json_value *root = json_parse_ex( &settings, psz_buffer, psz_error );
-    if ( root == NULL )
-    {
-        msg_Warn( p_obj, "Can't parse json data: %s", psz_error );
-        goto error;
-    }
-    if ( root->type != json_object )
-    {
-        msg_Warn( p_obj, "wrong json root node" );
-        goto error;
-    }
-    json_value *node = jsongetbyname( root, "status" );
+    json_value *root = json_parse_document( p_obj, p_buffer );
+    if( !root )
+        return false;
+
+    const json_value *node = json_getbyname( root, "status" );
     if ( !node || node->type != json_string )
     {
         msg_Warn( p_obj, "status node not found or invalid" );
@@ -118,7 +91,7 @@ static bool ParseJson( vlc_object_t *p_obj, const char *psz_buffer, acoustid_res
         msg_Warn( p_obj, "Bad request status" );
         goto error;
     }
-    node = jsongetbyname( root, "results" );
+    node = json_getbyname( root, "results" );
     if ( !node || node->type != json_array )
     {
         msg_Warn( p_obj, "Bad results array or no results" );
@@ -129,17 +102,15 @@ static bool ParseJson( vlc_object_t *p_obj, const char *psz_buffer, acoustid_res
     p_results->count = node->u.array.length;
     for( unsigned int i=0; i<node->u.array.length; i++ )
     {
-        json_value *resultnode = node->u.array.values[i];
+        const json_value *resultnode = node->u.array.values[i];
         if ( resultnode && resultnode->type == json_object )
         {
             acoustid_result_t *p_result = & p_results->p_results[i];
-            json_value *value = jsongetbyname( resultnode, "score" );
+            const json_value *value = json_getbyname( resultnode, "score" );
             if ( value && value->type == json_double )
                 p_result->d_score = value->u.dbl;
-            value = jsongetbyname( resultnode, "id" );
-            if ( value && value->type == json_string )
-                p_result->psz_id = strdup( value->u.string.ptr );
-            parse_recordings( p_obj, jsongetbyname( resultnode, "recordings" ), p_result );
+            p_result->psz_id = json_dupstring( resultnode, "id" );
+            parse_recordings( p_obj, json_getbyname( resultnode, "recordings" ), p_result );
         }
     }
     json_value_free( root );
@@ -150,62 +121,47 @@ error:
     return false;
 }
 
-int DoAcoustIdWebRequest( vlc_object_t *p_obj, acoustid_fingerprint_t *p_data )
+int acoustid_lookup_fingerprint( const acoustid_config_t *p_cfg, acoustid_fingerprint_t *p_data )
 {
-    if ( !p_data->psz_fingerprint ) return VLC_SUCCESS;
+    if ( !p_data->psz_fingerprint )
+        return VLC_SUCCESS;
 
     char *psz_url;
-    if( unlikely(asprintf( &psz_url, "https://fingerprint.videolan.org/"
-                           "acoustid.php?meta=recordings+tracks+usermeta+"
-                           "releases&duration=%d&fingerprint=%s",
-                           p_data->i_duration, p_data->psz_fingerprint ) < 1 ) )
-         return VLC_EGENERIC;
-
-    msg_Dbg( p_obj, "Querying AcoustID from %s", psz_url );
-    int i_saved_flags = p_obj->obj.flags;
-    p_obj->obj.flags |= OBJECT_FLAGS_NOINTERACT;
-
-    stream_t *p_stream = vlc_stream_NewURL( p_obj, psz_url );
+    if( p_cfg->psz_server )
+    {
+        if( unlikely(asprintf( &psz_url, "https://%s/v2/lookup"
+                               "?client=%s"
+                               "&meta=recordings+tracks+usermeta+releases"
+                               "&duration=%d"
+                               "&fingerprint=%s",
+                               p_cfg->psz_server,
+                               p_cfg->psz_apikey ? p_cfg->psz_apikey : "",
+                               p_data->i_duration,
+                               p_data->psz_fingerprint ) < 1 ) )
+             return VLC_EGENERIC;
+    }
+    else /* Use VideoLAN anonymized requests proxy */
+    {
+        if( unlikely(asprintf( &psz_url, "https://" ACOUSTID_ANON_SERVER
+                               ACOUSTID_ANON_SERVER_PATH
+                               "?meta=recordings+tracks+usermeta+releases"
+                               "&duration=%d"
+                               "&fingerprint=%s",
+                               p_data->i_duration,
+                               p_data->psz_fingerprint ) < 1 ) )
+             return VLC_EGENERIC;
+    }
 
+    msg_Dbg( p_cfg->p_obj, "Querying AcoustID from %s", psz_url );
+    void *p_buffer = json_retrieve_document( p_cfg->p_obj, psz_url );
     free( psz_url );
-    p_obj->obj.flags = i_saved_flags;
-    if ( p_stream == NULL )
+    if( !p_buffer )
         return VLC_EGENERIC;
 
-    stream_t *p_chain = vlc_stream_FilterNew( p_stream, "inflate" );
-    if( p_chain )
-        p_stream = p_chain;
-
-    /* read answer */
-    char *p_buffer = NULL;
-    int i_ret = 0;
-    for( ;; )
-    {
-        int i_read = 65536;
-
-        if( i_ret >= INT_MAX - i_read )
-            break;
-
-        p_buffer = realloc_or_free( p_buffer, 1 + i_ret + i_read );
-        if( unlikely(p_buffer == NULL) )
-        {
-            vlc_stream_Delete( p_stream );
-            return VLC_ENOMEM;
-        }
-
-        i_read = vlc_stream_Read( p_stream, &p_buffer[i_ret], i_read );
-        if( i_read <= 0 )
-            break;
-
-        i_ret += i_read;
-    }
-    vlc_stream_Delete( p_stream );
-    p_buffer[i_ret] = 0;
-
-    if ( ParseJson( p_obj, p_buffer, & p_data->results ) )
-        msg_Dbg( p_obj, "results count == %d", p_data->results.count );
+    if ( ParseJson( p_cfg->p_obj, p_buffer, & p_data->results ) )
+        msg_Dbg( p_cfg->p_obj, "results count == %d", p_data->results.count );
     else
-        msg_Dbg( p_obj, "No results" );
+        msg_Dbg( p_cfg->p_obj, "No results" );
     free( p_buffer );
 
     return VLC_SUCCESS;


=====================================
modules/misc/webservices/acoustid.h
=====================================
@@ -17,16 +17,17 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  *****************************************************************************/
-
+#define ACOUSTID_ANON_SERVER "fingerprint.videolan.org"
+#define ACOUSTID_ANON_SERVER_PATH   "/acoustid.php"
 #define MB_ID_SIZE 36
 
-struct musicbrainz_recording_t
+struct acoustid_mb_result_t
 {
     char *psz_artist;
     char *psz_title;
     char s_musicbrainz_id[MB_ID_SIZE];
 };
-typedef struct musicbrainz_recording_t musicbrainz_recording_t;
+typedef struct acoustid_mb_result_t acoustid_mb_result_t;
 
 struct acoustid_result_t
 {
@@ -35,7 +36,7 @@ struct acoustid_result_t
     struct
     {
         unsigned int count;
-        musicbrainz_recording_t *p_recordings;
+        acoustid_mb_result_t *p_recordings;
     } recordings;
 };
 typedef struct acoustid_result_t acoustid_result_t;
@@ -55,5 +56,12 @@ struct acoustid_fingerprint_t
 };
 typedef struct acoustid_fingerprint_t acoustid_fingerprint_t;
 
-int DoAcoustIdWebRequest( vlc_object_t *p_obj, acoustid_fingerprint_t *p_data );
-void free_acoustid_result_t( acoustid_result_t * r );
+typedef struct
+{
+    vlc_object_t *p_obj;
+    char *psz_server;
+    char *psz_apikey;
+} acoustid_config_t;
+
+int acoustid_lookup_fingerprint( const acoustid_config_t *, acoustid_fingerprint_t * );
+void acoustid_result_release( acoustid_result_t * );


=====================================
modules/misc/webservices/json_helper.h
=====================================
@@ -0,0 +1,127 @@
+/*****************************************************************************
+ * json_helper.h:
+ *****************************************************************************
+ * Copyright (C) 2012-2019 VLC authors, VideoLabs and VideoLAN
+ *
+ * This program 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 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef JSON_HELPER_H
+#define JSON_HELPER_H
+
+#include <vlc_common.h>
+#include <vlc_stream.h>
+
+#include <limits.h>
+
+#include "json.h"
+
+static inline
+const json_value * json_getbyname(const json_value *object, const char *psz_name)
+{
+    if (object->type != json_object) return NULL;
+    for (unsigned int i=0; i < object->u.object.length; i++)
+        if (strcmp(object->u.object.values[i].name, psz_name) == 0)
+            return object->u.object.values[i].value;
+    return NULL;
+}
+
+static inline
+char * jsongetstring(const json_value *node, const char *key)
+{
+    node = json_getbyname(node, key);
+    if (node && node->type == json_string)
+        return node->u.string.ptr;
+    return NULL;
+}
+
+static inline
+char * json_dupstring(const json_value *node, const char *key)
+{
+    const char *str = jsongetstring(node, key);
+    return (str) ? strdup(str) : NULL;
+}
+
+static inline
+json_value * json_parse_document(vlc_object_t *p_obj, const char *psz_buffer)
+{
+    json_settings settings;
+    char psz_error[128];
+    memset (&settings, 0, sizeof (json_settings));
+    json_value *root = json_parse_ex(&settings, psz_buffer, psz_error);
+    if (root == NULL)
+    {
+        msg_Warn(p_obj, "Can't parse json data: %s", psz_error);
+        goto error;
+    }
+    if (root->type != json_object)
+    {
+        msg_Warn(p_obj, "wrong json root node");
+        goto error;
+    }
+
+    return root;
+
+error:
+    if (root) json_value_free(root);
+    return NULL;
+}
+
+static inline
+void * json_retrieve_document(vlc_object_t *p_obj, const char *psz_url)
+{
+    int i_saved_flags = p_obj->obj.flags;
+    p_obj->obj.flags |= OBJECT_FLAGS_NOINTERACT;
+    stream_t *p_stream = vlc_stream_NewURL(p_obj, psz_url);
+
+    p_obj->obj.flags = i_saved_flags;
+    if (p_stream == NULL)
+        return NULL;
+
+    stream_t *p_chain = vlc_stream_FilterNew(p_stream, "inflate");
+    if(p_chain)
+        p_stream = p_chain;
+
+    /* read answer */
+    char *p_buffer = NULL;
+    int i_ret = 0;
+    for(;;)
+    {
+        int i_read = 65536;
+
+        if(i_ret >= INT_MAX - i_read)
+            break;
+
+        char *p_realloc = realloc(p_buffer, 1 + i_ret + i_read);
+        if(unlikely(p_realloc == NULL))
+        {
+            free(p_buffer);
+            vlc_stream_Delete(p_stream);
+            return NULL;
+        }
+        p_buffer = p_realloc;
+
+        i_read = vlc_stream_Read(p_stream, &p_buffer[i_ret], i_read);
+        if(i_read <= 0)
+            break;
+
+        i_ret += i_read;
+    }
+    vlc_stream_Delete(p_stream);
+    p_buffer[i_ret] = 0;
+
+    return p_buffer;
+}
+
+#endif


=====================================
modules/misc/webservices/musicbrainz.c
=====================================
@@ -0,0 +1,349 @@
+/*****************************************************************************
+ * musicbrainz.c : Musicbrainz API lookup
+ *****************************************************************************
+ * Copyright (C) 2019 VideoLabs, VLC authors and VideoLAN
+ *
+ * This program 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 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+#include <limits.h>
+
+#include "json_helper.h"
+#include "musicbrainz.h"
+
+typedef struct
+{
+    json_value *root;
+} musicbrainz_lookup_t;
+
+static void musicbrainz_lookup_release(musicbrainz_lookup_t *p)
+{
+    if(p && p->root)
+        json_value_free(p->root);
+    free(p);
+}
+
+static musicbrainz_lookup_t * musicbrainz_lookup_new(void)
+{
+    return calloc(1, sizeof(musicbrainz_lookup_t));
+}
+
+static musicbrainz_lookup_t * musicbrainz_lookup(vlc_object_t *p_obj, const char *psz_url)
+{
+    msg_Dbg(p_obj, "Querying MB for %s", psz_url);
+    void *p_buffer = json_retrieve_document(p_obj, psz_url);
+    if(!p_buffer)
+        return NULL;
+
+    musicbrainz_lookup_t *p_lookup = musicbrainz_lookup_new();
+    if(p_lookup)
+    {
+        p_lookup->root = json_parse_document(p_obj, p_buffer);
+        if (!p_lookup->root)
+            msg_Dbg(p_obj, "No results");
+    }
+    free(p_buffer);
+    return p_lookup;
+}
+
+static bool musicbrainz_fill_track(const json_value *tracknode, musicbrainz_track_t *t)
+{
+    t->psz_title = json_dupstring(tracknode, "title");
+
+    const json_value *node = json_getbyname(tracknode, "artist-credit");
+    if (node && node->type == json_array && node->u.array.length)
+        t->psz_artist = json_dupstring(node->u.array.values[0], "name");
+
+    node = json_getbyname(tracknode, "position");
+    if (node && node->type == json_integer)
+        t->i_index = node->u.integer;
+
+    return true;
+}
+
+static bool musicbrainz_has_cover_in_releasegroup(json_value ** const p_nodes,
+                                                  size_t i_nodes,
+                                                  const char *psz_group_id)
+{
+    for(size_t i=0; i<i_nodes; i++)
+    {
+        const json_value *rgnode = json_getbyname(p_nodes[i], "release-group");
+        if(rgnode)
+        {
+            const char *psz_id = jsongetstring(rgnode, "id");
+            if(!psz_id || strcmp(psz_id, psz_group_id))
+                continue;
+
+            const json_value *node = json_getbyname(p_nodes[i], "cover-art-archive");
+            if(!node)
+                continue;
+
+            node = json_getbyname(node, "front");
+            if(!node || node->type != json_boolean || !node->u.boolean)
+                continue;
+
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static char *musicbrainz_fill_artists(const json_value *arraynode)
+{
+    char *psz = NULL;
+    if(arraynode->type != json_array || arraynode->u.array.length < 1)
+        return psz;
+
+    size_t i_total = 1;
+    for(size_t i=0; i<arraynode->u.array.length; i++)
+    {
+        const json_value *name = json_getbyname(arraynode->u.array.values[i], "name");
+        if(name->type != json_string)
+            continue;
+
+        if(psz == NULL)
+        {
+            psz = strdup(name->u.string.ptr);
+            i_total = name->u.string.length + 1;
+        }
+        else
+        {
+            char *p = realloc(psz, i_total + name->u.string.length + 2);
+            if(p)
+            {
+                psz = p;
+                psz = strcat(psz, ", ");
+                psz = strncat(psz, name->u.string.ptr, name->u.string.length);
+                i_total += name->u.string.length + 2;
+            }
+        }
+    }
+
+    return psz;
+}
+
+static bool musicbrainz_fill_release(const json_value *releasenode, musicbrainz_release_t *r)
+{
+    const json_value *media = json_getbyname(releasenode, "media");
+    if(!media || media->type != json_array ||
+       media->u.array.length == 0)
+        return false;
+    /* we always use first media */
+    media = media->u.array.values[0];
+
+    const json_value *tracks = json_getbyname(media, "tracks");
+    if(!tracks || tracks->type != json_array ||
+       tracks->u.array.length == 0)
+        return false;
+
+    r->p_tracks = calloc(tracks->u.array.length, sizeof(*r->p_tracks));
+    if(!r->p_tracks)
+        return false;
+
+    for(size_t i=0; i<tracks->u.array.length; i++)
+    {
+        if(musicbrainz_fill_track(tracks->u.array.values[i], &r->p_tracks[r->i_tracks]))
+            r->i_tracks++;
+    }
+
+    r->psz_title = json_dupstring(releasenode, "title");
+    r->psz_id = json_dupstring(releasenode, "id");
+
+    const json_value *rgnode = json_getbyname(releasenode, "release-group");
+    if(rgnode)
+    {
+        r->psz_date = json_dupstring(rgnode, "first-release-date");
+        r->psz_group_id = json_dupstring(rgnode, "id");
+
+        const json_value *node = json_getbyname(rgnode, "artist-credit");
+        if(node)
+            r->psz_artist = musicbrainz_fill_artists(node);
+    }
+    else
+    {
+        const json_value *node = json_getbyname(releasenode, "artist-credit");
+        if(node)
+            r->psz_artist = musicbrainz_fill_artists(node);
+
+        node = json_getbyname(releasenode, "release-events");
+        if(node && node->type == json_array && node->u.array.length)
+            r->psz_date = json_dupstring(node->u.array.values[0], "date");
+    }
+
+
+    return true;
+}
+
+void musicbrainz_recording_release(musicbrainz_recording_t *mbr)
+{
+    for(size_t i=0; i<mbr->i_release; i++)
+    {
+        free(mbr->p_releases[i].psz_id);
+        free(mbr->p_releases[i].psz_group_id);
+        free(mbr->p_releases[i].psz_artist);
+        free(mbr->p_releases[i].psz_title);
+        free(mbr->p_releases[i].psz_date);
+        free(mbr->p_releases[i].psz_coverart_url);
+        for(size_t j=0; j<mbr->p_releases[i].i_tracks; j++)
+        {
+            free(mbr->p_releases[i].p_tracks[j].psz_title);
+            free(mbr->p_releases[i].p_tracks[j].psz_artist);
+        }
+        free(mbr->p_releases[i].p_tracks);
+    }
+    free(mbr->p_releases);
+    free(mbr);
+}
+
+static musicbrainz_recording_t *musicbrainz_lookup_recording_by_apiurl(vlc_object_t *obj,
+                                                                       const char *psz_url)
+{
+    musicbrainz_recording_t *r = calloc(1, sizeof(*r));
+    if(!r)
+        return NULL;
+
+    musicbrainz_lookup_t *lookup = musicbrainz_lookup(obj, psz_url);
+    if(!lookup)
+    {
+        free(r);
+        return NULL;
+    }
+
+    const json_value *releases = json_getbyname(lookup->root, "releases");
+    if (releases && releases->type == json_array &&
+        releases->u.array.length)
+    {
+        r->p_releases = calloc(releases->u.array.length, sizeof(*r->p_releases));
+        if(r->p_releases)
+        {
+            for(unsigned i=0; i<releases->u.array.length; i++)
+            {
+                json_value *node = releases->u.array.values[i];
+                musicbrainz_release_t *p_mbrel = &r->p_releases[r->i_release];
+                if (!node || node->type != json_object ||
+                    !musicbrainz_fill_release(node, p_mbrel))
+                    continue;
+
+                /* Try to find cover from other releases from the same group */
+                if(p_mbrel->psz_group_id && !p_mbrel->psz_coverart_url &&
+                   musicbrainz_has_cover_in_releasegroup(releases->u.array.values,
+                                                         releases->u.array.length,
+                                                         p_mbrel->psz_group_id))
+                {
+                    char *psz_art = coverartarchive_make_releasegroup_arturl(
+                                        COVERARTARCHIVE_DEFAULT_SERVER,
+                                        p_mbrel->psz_group_id );
+                    if(psz_art)
+                        p_mbrel->psz_coverart_url = psz_art;
+                }
+
+                r->i_release++;
+            }
+        }
+    }
+
+    musicbrainz_lookup_release(lookup);
+
+    return r;
+}
+
+static char *musicbrainz_build_discid_json_url(const char *psz_server,
+                                               const char *psz_disc_id,
+                                               const char *psz_tail)
+{
+    char *psz_url;
+    if(asprintf(&psz_url,
+                "https://%s/ws/2/discid/%s?"
+                "fmt=json"
+                "&inc=artist-credits+recordings+release-groups"
+                "&cdstubs=no"
+                "%s%s",
+                psz_server ? psz_server : MUSICBRAINZ_DEFAULT_SERVER,
+                psz_disc_id,
+                psz_tail ? "&" : "",
+                psz_tail ? psz_tail : "" ) > -1 )
+    {
+        return psz_url;
+    }
+    return NULL;
+}
+
+musicbrainz_recording_t *musicbrainz_lookup_recording_by_toc(musicbrainz_config_t *cfg,
+                                                             const char *psz_toc)
+{
+    char *psz_url = musicbrainz_build_discid_json_url(cfg->psz_mb_server, "-", psz_toc);
+    if(!psz_url)
+        return NULL;
+
+    musicbrainz_recording_t *r = musicbrainz_lookup_recording_by_apiurl(cfg->obj, psz_url);
+    free(psz_url);
+    return r;
+}
+
+musicbrainz_recording_t *musicbrainz_lookup_recording_by_discid(musicbrainz_config_t *cfg,
+                                                                const char *psz_disc_id)
+{
+    char *psz_url = musicbrainz_build_discid_json_url(cfg->psz_mb_server, psz_disc_id, NULL);
+    if(!psz_url)
+        return NULL;
+
+    musicbrainz_recording_t *r = musicbrainz_lookup_recording_by_apiurl(cfg->obj, psz_url);
+    free(psz_url);
+    return r;
+}
+
+char * coverartarchive_make_releasegroup_arturl(const char *psz_server, const char *psz_group_id)
+{
+    char *psz_art;
+    if(-1 < asprintf(&psz_art, "https://%s/release-group/%s/front",
+                     psz_server ? psz_server : COVERARTARCHIVE_DEFAULT_SERVER,
+                     psz_group_id))
+        return psz_art;
+    return NULL;
+}
+
+void musicbrainz_release_covert_art(coverartarchive_t *c)
+{
+    free(c);
+}
+
+coverartarchive_t * coverartarchive_lookup_releasegroup(musicbrainz_config_t *cfg, const char *psz_id)
+{
+    coverartarchive_t *c = calloc(1, sizeof(*c));
+    if(!c)
+        return NULL;
+
+    char *psz_url;
+    if(0 < asprintf(&psz_url, "https://%s/releasegroup/%s", cfg->psz_coverart_server, psz_id ))
+    {
+        return NULL;
+    }
+
+     musicbrainz_lookup_t *p_lookup = musicbrainz_lookup(cfg->obj, psz_url);
+     free(psz_url);
+
+     if(!p_lookup)
+     {
+         free(c);
+         return NULL;
+     }
+
+    return c;
+}


=====================================
modules/misc/webservices/musicbrainz.h
=====================================
@@ -0,0 +1,73 @@
+/*****************************************************************************
+ * musicbrainz.h : Musicbrainz API lookup
+ *****************************************************************************
+ * Copyright (C) 2019 VideoLabs, VLC authors and VideoLAN
+ *
+ * This program 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 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef VLC_MUSICBRAINZ_H_
+#define VLC_MUSICBRAINZ_H_
+
+#define MUSICBRAINZ_DEFAULT_SERVER "musicbrainz.org"
+#define COVERARTARCHIVE_DEFAULT_SERVER "coverartarchive.org"
+
+typedef struct
+{
+    unsigned i_index;
+    char *psz_title;
+    char *psz_artist;
+} musicbrainz_track_t;
+
+typedef struct
+{
+    char *psz_id;
+    char *psz_group_id;
+    char *psz_title;
+    char *psz_artist;
+    /* https://github.com/metabrainz/mmd-schema/blob/master/schema/musicbrainz_mmd-2.0.rng
+       "def_incomplete-date" [0-9]{4}(-[0-9]{2})?(-[0-9]{2})? */
+    char *psz_date;
+    char *psz_coverart_url;
+    size_t i_tracks;
+    musicbrainz_track_t *p_tracks;
+} musicbrainz_release_t;
+
+typedef struct
+{
+    size_t i_release;
+    musicbrainz_release_t *p_releases;
+} musicbrainz_recording_t;
+
+typedef struct
+{
+    vlc_object_t *obj;
+    char *psz_mb_server;
+    char *psz_coverart_server;
+} musicbrainz_config_t;
+
+void musicbrainz_recording_release(musicbrainz_recording_t *);
+musicbrainz_recording_t * musicbrainz_lookup_recording_by_toc(musicbrainz_config_t *, const char *);
+musicbrainz_recording_t * musicbrainz_lookup_recording_by_discid(musicbrainz_config_t *, const char *);
+
+typedef struct
+{
+    char *psz_url;
+} coverartarchive_t;
+
+void musicbrainz_release_covert_art(coverartarchive_t *c);
+coverartarchive_t * coverartarchive_lookup_releasegroup(musicbrainz_config_t *, const char *);
+char * coverartarchive_make_releasegroup_arturl(const char *, const char *);
+
+#endif



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/e496d3776da5f5b84e09996c31f2089d1299a8a3...4c49c64e813be16b9b98e2e96717fab1f7912ee2

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/e496d3776da5f5b84e09996c31f2089d1299a8a3...4c49c64e813be16b9b98e2e96717fab1f7912ee2
You're receiving this email because of your account on code.videolan.org.




More information about the vlc-commits mailing list