[vlc-devel] [PATCH 1/3] Use fetcher to download playback content from http/ftp

Mario Speiß 1034-135 at online.de
Sat Jan 5 22:12:46 CET 2013


The playlist fetcher (used to update the cover art) can be used to download
from various internet sources.
To start a download, the input_item_t has a new member i_download to flag this
item for download if the fetcher thread is fed with this item.

Also, DownloadArt now checks the content type to verify an image will be
downloaded.

Regards,
Mario
---
 include/vlc_input_item.h |    2 +
 src/playlist/fetcher.c   |  406 ++++++++++++++++++++++++++++++++++++++++------
 src/playlist/fetcher.h   |    2 +-
 3 files changed, 361 insertions(+), 49 deletions(-)

diff --git a/include/vlc_input_item.h b/include/vlc_input_item.h
index 0f9800d..5c02ab2 100644
--- a/include/vlc_input_item.h
+++ b/include/vlc_input_item.h
@@ -66,6 +66,8 @@ struct input_item_t
 
     mtime_t    i_duration;           /**< Duration in microseconds */
 
+    int        i_download;           /**< Flag: <0 marked for download, >0 download finished */
+
 
     int        i_categories;         /**< Number of info categories */
     info_category_t **pp_categories; /**< Pointer to the first info category */
diff --git a/src/playlist/fetcher.c b/src/playlist/fetcher.c
index f682c8e..9826348 100644
--- a/src/playlist/fetcher.c
+++ b/src/playlist/fetcher.c
@@ -35,6 +35,8 @@
 #include <vlc_memory.h>
 #include <vlc_demux.h>
 #include <vlc_modules.h>
+#include <vlc_fs.h>
+#include <vlc_strings.h>
 
 #include "art.h"
 #include "fetcher.h"
@@ -45,7 +47,8 @@
  *****************************************************************************/
 struct playlist_fetcher_t
 {
-    vlc_object_t   *object;
+    playlist_t      *p_playlist;
+
     vlc_mutex_t     lock;
     vlc_cond_t      wait;
     bool            b_live;
@@ -62,19 +65,19 @@ static void *Thread( void * );
 /*****************************************************************************
  * Public functions
  *****************************************************************************/
-playlist_fetcher_t *playlist_fetcher_New( vlc_object_t *parent )
+playlist_fetcher_t *playlist_fetcher_New( playlist_t *p_playlist )
 {
     playlist_fetcher_t *p_fetcher = malloc( sizeof(*p_fetcher) );
     if( !p_fetcher )
         return NULL;
 
-    p_fetcher->object = parent;
+    p_fetcher->p_playlist = p_playlist;
     vlc_mutex_init( &p_fetcher->lock );
     vlc_cond_init( &p_fetcher->wait );
     p_fetcher->b_live = false;
     p_fetcher->i_waiting = 0;
     p_fetcher->pp_waiting = NULL;
-    p_fetcher->i_art_policy = var_GetInteger( parent, "album-art" );
+    p_fetcher->i_art_policy = var_GetInteger( p_playlist, "album-art" );
     ARRAY_INIT( p_fetcher->albums );
 
     return p_fetcher;
@@ -92,7 +95,7 @@ void playlist_fetcher_Push( playlist_fetcher_t *p_fetcher,
     {
         if( vlc_clone_detach( NULL, Thread, p_fetcher,
                               VLC_THREAD_PRIORITY_LOW ) )
-            msg_Err( p_fetcher->object,
+            msg_Err( p_fetcher->p_playlist,
                      "cannot spawn secondary preparse thread" );
         else
             p_fetcher->b_live = true;
@@ -152,7 +155,7 @@ static int FindArt( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
             if( !strcmp( album.psz_artist, psz_artist ) &&
                 !strcmp( album.psz_album, psz_album ) )
             {
-                msg_Dbg( p_fetcher->object,
+                msg_Dbg( p_fetcher->p_playlist,
                          " %s - %s has already been searched",
                          psz_artist, psz_album );
                 /* TODO-fenrir if we cache art filename too, we can go faster */
@@ -179,7 +182,7 @@ static int FindArt( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
     if ( playlist_FindArtInCacheUsingItemUID( p_item ) != VLC_SUCCESS )
         playlist_FindArtInCache( p_item );
     else
-        msg_Dbg( p_fetcher->object, "successfully retrieved arturl by uid" );
+        msg_Dbg( p_fetcher->p_playlist, "successfully retrieved arturl by uid" );
 
     char *psz_arturl = input_item_GetArtURL( p_item );
     if( psz_arturl )
@@ -202,8 +205,8 @@ static int FindArt( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
     psz_artist = input_item_GetArtist( p_item );
     if( psz_album && psz_artist )
     {
-        msg_Dbg( p_fetcher->object, "searching art for %s - %s",
-                 psz_artist, psz_album );
+        msg_Dbg( p_fetcher->p_playlist, "searching art for %s - %s",
+             psz_artist, psz_album );
     }
     else
     {
@@ -211,14 +214,14 @@ static int FindArt( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
         if( !psz_title )
             psz_title = input_item_GetName( p_item );
 
-        msg_Dbg( p_fetcher->object, "searching art for %s", psz_title );
+        msg_Dbg( p_fetcher->p_playlist, "searching art for %s", psz_title );
         free( psz_title );
     }
 
     /* Fetch the art url */
     i_ret = VLC_EGENERIC;
 
-    vlc_object_t *p_parent = p_fetcher->object;
+    vlc_object_t *p_parent = VLC_OBJECT(p_fetcher->p_playlist);
     art_finder_t *p_finder =
         vlc_custom_create( p_parent, sizeof( *p_finder ), "art finder" );
     if( p_finder != NULL)
@@ -270,7 +273,7 @@ static int DownloadArt( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
 
     if( !strncmp( psz_arturl , "file://", 7 ) )
     {
-        msg_Dbg( p_fetcher->object,
+        msg_Dbg( p_fetcher->p_playlist,
                  "Album art is local file, no need to cache" );
         free( psz_arturl );
         return VLC_SUCCESS;
@@ -278,16 +281,241 @@ static int DownloadArt( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
 
     if( !strncmp( psz_arturl , "APIC", 4 ) )
     {
-        msg_Warn( p_fetcher->object, "APIC fetch not supported yet" );
+        msg_Warn( p_fetcher->p_playlist, "APIC fetch not supported yet" );
         goto error;
     }
 
-    stream_t *p_stream = stream_UrlNew( p_fetcher->object, psz_arturl );
+    stream_t *p_stream = stream_UrlNew( p_fetcher->p_playlist, psz_arturl );
     if( !p_stream )
         goto error;
 
-    uint8_t *p_data = NULL;
+    /* while parsing the url no filename extension was found.
+     * try the content-type now */
+    char *contenttype = stream_ContentType( p_stream );
+    if( contenttype )
+    {
+        if( strstr( contenttype, "image") == NULL )
+        {
+            msg_Warn( p_fetcher->p_playlist, "content type is '%s', not saving the art.", contenttype );
+            free( contenttype );
+            contenttype = NULL;
+        }
+        else
+        {
+            free( contenttype );
+        }
+    }
+    else
+    {
+        contenttype = "image";
+    }
+
     int i_data = 0;
+    uint8_t *p_data = NULL;
+    if( contenttype )
+    {
+        for( ;; )
+        {
+            int i_read = 65536;
+
+            if( i_data >= INT_MAX - i_read )
+                break;
+
+            p_data = realloc_or_free( p_data, i_data + i_read );
+            if( !p_data )
+                break;
+
+            i_read = stream_Read( p_stream, &p_data[i_data], i_read );
+            if( i_read <= 0 )
+                break;
+
+            i_data += i_read;
+        }
+    }
+    stream_Delete( p_stream );
+
+    if( p_data && i_data > 0 && contenttype )
+    {
+        char *psz_type = strrchr( psz_arturl, '.' );
+        if( psz_type && strlen( psz_type ) > 5 )
+            psz_type = NULL; /* remove extension if it's > to 4 characters */
+
+        playlist_SaveArt( p_fetcher->p_playlist, p_item, p_data, i_data, psz_type );
+    }
+
+    free( p_data );
+
+    free( psz_arturl );
+    return VLC_SUCCESS;
+
+error:
+    free( psz_arturl );
+    return VLC_EGENERIC;
+}
+
+/**
+ * Download the art using the URL or an art downloaded
+ * This function should be called only if data is not already in cache
+ */
+static int Download( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
+{
+    stream_t *p_stream = NULL;
+    FILE            *f = NULL;
+
+    char *psz_url = input_item_GetURI( p_item );
+    assert( *psz_url );
+    /* get optional URL to make filename prettier
+     * Usually the original URL (to the web page containing this file) gets
+     * overwritten (by lua parser for example) but can be stored to meta-url.
+    */
+    char *psz_orgurl = input_item_GetURL( p_item );
+
+    if( strncmp( psz_url , "http://", 7 ) && strncmp( psz_url , "https://", 8 ) && strncmp( psz_url , "ftp://", 5 ) )
+    {
+        msg_Dbg( p_fetcher->p_playlist, "download not possible for %s", psz_url );
+        free( psz_url );
+        free( psz_orgurl );
+        /* set i_download positive to not retry the download */
+        p_item->i_download = 1;
+        return VLC_SUCCESS;
+    }
+
+    /* make filename */
+    char filename[256] = "fetcher.dump";
+    char *psz_title = input_item_GetTitle( p_item );
+    if( !psz_title )
+        psz_title = input_item_GetName( p_item );
+    if( psz_title )
+        strncpy( filename, psz_title, sizeof( filename )/2 );
+    free( psz_title );
+
+    char *url = psz_url;
+    if( psz_orgurl &&
+            (
+                strlen( psz_url ) > 256 || strrchr( psz_url ,'.' ) < psz_url + strlen( psz_url ) - 7 )
+            )
+        url = psz_orgurl;
+
+    /* parse the url to find the filename part */
+    char *remotefilename    = strrchr( url, '/' );
+    char *remotefilenameext = NULL;
+    if( remotefilename )
+    {
+        /* skip the '/' */
+        remotefilename ++;
+        remotefilenameext = strrchr( remotefilename, '.' );
+        /* check this part of the url for a possible file extension */
+        if( remotefilenameext - remotefilename <= 10 && remotefilenameext - remotefilename >= 0 )
+        {
+            /* yes, seems to be a filename extension */
+            sprintf( filename + strlen( filename )," - %.100s", remotefilename );
+        }
+        else
+        {
+            /* no filename extension found */
+            sprintf( filename + strlen( filename )," - %.100s", remotefilename );
+            remotefilenameext = NULL;
+        }
+    }
+
+    filename_sanitize( filename );
+    char *psz_path = config_GetUserDir( VLC_DOWNLOAD_DIR );
+    if( !psz_path )
+        goto error;
+
+    /* Create file name
+     * TODO allow prefix configuration */
+    char filepath[1024];
+    sprintf( filepath, "%s"DIR_SEP"%s", psz_path, filename);
+    free( psz_path );
+
+    msg_Dbg( p_fetcher->p_playlist, "downloading to %s", filepath );
+    msg_Dbg( p_fetcher->p_playlist, "starting to download from %s to %s", psz_url, filepath );
+
+    if( !strncmp( psz_url , "APIC", 4 ) )
+    {
+        msg_Warn( p_fetcher->p_playlist, "APIC fetch not supported yet" );
+        /* set i_download positive to not retry the download */
+        p_item->i_download = 1;
+        goto error;
+    }
+
+    p_stream = stream_UrlNew( p_fetcher->p_playlist, psz_url );
+    if( !p_stream )
+        goto error;
+
+    stream_Control( p_stream, STREAM_SET_POSITION, 0 );
+    uint64_t pos  = -1;
+    uint64_t size = -1;
+    uint64_t len  = 0;
+    bool seekable = false;
+    stream_Control( p_stream, STREAM_CAN_SEEK, &seekable );
+    if( seekable )
+        pos = stream_Tell( p_stream );
+    stream_Control( p_stream, STREAM_GET_SIZE, &size );
+    if( size > 0 )
+        msg_Dbg( p_fetcher->p_playlist, "expected length: %d kB", size / 1024 );
+
+
+    if( remotefilenameext == NULL )
+    {
+        /* while parsing the url no filename extension was found.
+         * try the content-type now */
+        remotefilenameext = stream_ContentType( p_stream );
+        if( remotefilenameext )
+        {
+            msg_Dbg( p_fetcher->p_playlist, "content type %s", remotefilenameext );
+            strcat( filepath, "." );
+            if( strchr( remotefilenameext, '/' ) )
+                strcat( filepath, strchr( remotefilenameext, '/' ) + 1 );
+            else
+                strcat( filepath, remotefilenameext );
+            msg_Dbg(p_fetcher->p_playlist, "downloading to file %s", filepath );
+        }
+        free( remotefilenameext );
+    }
+
+    /* check if file already exists */
+    f = vlc_fopen( filepath, "r+b" );
+    if( f )
+    {
+        /* get length of local file */
+        fseek( f, 0, SEEK_END );
+        len = ftell( f );
+        msg_Dbg( p_fetcher->p_playlist, "file already exists. size %d", len );
+        if( len > 0 && size > 0 )
+        {
+            if( len == size )
+            {
+                msg_Dbg( p_fetcher->p_playlist, "file sizes equal, nothing to do!" );
+                stream_Delete( p_stream );
+                fclose( f );
+                /* mark download as finished */
+                p_item->i_download = len;
+                return VLC_SUCCESS;
+            }
+            else if( seekable )
+            {
+                /* if remote stream supports seeking, resume download
+                 * at the current local file length */
+                msg_Dbg( p_fetcher->p_playlist, "resuming download at the end..." );
+                stream_Control( p_stream, STREAM_SET_POSITION, len );
+            }
+        }
+    }
+    else
+    {
+        /* file did not exist - create it! */
+        f = vlc_fopen( filepath, "wb");
+    }
+    if( !f )
+        goto error;
+
+    uint8_t *p_data = NULL;
+    p_data = realloc_or_free( p_data, 65536 );
+    if( !p_data )
+        goto error;
+    int i_data = len;
     for( ;; )
     {
         int i_read = 65536;
@@ -295,35 +523,48 @@ static int DownloadArt( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
         if( i_data >= INT_MAX - i_read )
             break;
 
-        p_data = realloc_or_free( p_data, i_data + i_read );
-        if( !p_data )
-            break;
+        pos = stream_Tell( p_stream );
+        msg_Dbg( p_fetcher->p_playlist, "stream_Tell: %d, done: %d", pos/1024, i_data/1024 );
+        if( pos >= 0 )
+            fseek( f, pos, SEEK_SET );
 
-        i_read = stream_Read( p_stream, &p_data[i_data], i_read );
+        i_read = stream_Read( p_stream, p_data, i_read );
         if( i_read <= 0 )
             break;
 
+        const size_t i_written = fwrite( p_data, 1, i_read, f );
+
         i_data += i_read;
+        p_item->i_download = -i_data;
     }
     stream_Delete( p_stream );
 
-    if( p_data && i_data > 0 )
+    msg_Dbg( p_fetcher->p_playlist, "download finished (%d kB)", i_data/1024 );
+    if( p_item->i_download != size )
     {
-        char *psz_type = strrchr( psz_arturl, '.' );
-        if( psz_type && strlen( psz_type ) > 5 )
-            psz_type = NULL; /* remove extension if it's > to 4 characters */
-
-        playlist_SaveArt( p_fetcher->object, p_item,
-                          p_data, i_data, psz_type );
+        /* not all data was retrieved, set item->i_download negative to
+         * be able to resume/retry downloading it */
+        p_item->i_download =- size;
     }
 
+    fclose( f );
+
     free( p_data );
 
-    free( psz_arturl );
+    free( psz_url );
+    free( psz_orgurl );
     return VLC_SUCCESS;
 
 error:
-    free( psz_arturl );
+    if( f )
+        fclose( f );
+    if( p_stream )
+        stream_Delete( p_stream );
+    msg_Err( p_fetcher->p_playlist, "error downloading from %s", psz_url );
+    free( psz_url );
+    free( psz_orgurl );
+    if( p_item->i_download < 0)
+        p_item->i_download = 0;
     return VLC_EGENERIC;
 }
 
@@ -334,7 +575,7 @@ error:
  */
 static void FetchMeta( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
 {
-    demux_meta_t *p_demux_meta = vlc_custom_create(p_fetcher->object,
+    demux_meta_t *p_demux_meta = vlc_custom_create(p_fetcher->p_playlist,
                                          sizeof(*p_demux_meta), "demux meta" );
     if( !p_demux_meta )
         return;
@@ -348,10 +589,66 @@ static void FetchMeta( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
     vlc_object_release( p_demux_meta );
 }
 
+static int InputEvent( vlc_object_t *p_this, char const *psz_cmd,
+                       vlc_value_t oldval, vlc_value_t newval, void *p_data )
+{
+    VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
+    vlc_cond_t *p_cond = p_data;
+
+    if( newval.i_int == INPUT_EVENT_ITEM_META ||
+        newval.i_int == INPUT_EVENT_DEAD )
+        vlc_cond_signal( p_cond );
+
+    return VLC_SUCCESS;
+}
+
+
+/* Check if it is not yet preparsed and if so wait for it
+ * (at most 0.5s)
+ * (This can happen if we fetch art on play)
+ * FIXME this doesn't work if we need to fetch meta before art...
+ */
+static void WaitPreparsed( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
+{
+    if( input_item_IsPreparsed( p_item ) )
+        return;
+
+    input_thread_t *p_input = playlist_CurrentInput( p_fetcher->p_playlist );
+    if( !p_input )
+        return;
+
+    if( input_GetItem( p_input ) != p_item )
+        goto exit;
+
+    vlc_cond_t cond;
+    vlc_cond_init( &cond );
+    var_AddCallback( p_input, "intf-event", InputEvent, &cond );
+
+    const mtime_t i_deadline = mdate() + 500*1000;
+    bool b_timeout = false;
+
+    while( !p_input->b_eof && !p_input->b_error
+        && !input_item_IsPreparsed( p_item ) && !b_timeout )
+    {
+        /* A bit weird, but input_item_IsPreparsed holds the protected value */
+        /* FIXME: locking looks wrong here */
+        vlc_mutex_lock( &p_fetcher->lock );
+        if( vlc_cond_timedwait( &cond, &p_fetcher->lock, i_deadline ) )
+            b_timeout = true;
+        vlc_mutex_unlock( &p_fetcher->lock );
+    }
+
+    var_DelCallback( p_input, "intf-event", InputEvent, &cond );
+    vlc_cond_destroy( &cond );
+
+exit:
+    vlc_object_release( p_input );
+}
+
 static void *Thread( void *p_data )
 {
     playlist_fetcher_t *p_fetcher = p_data;
-    vlc_object_t *obj = p_fetcher->object;
+    playlist_t *p_playlist = p_fetcher->p_playlist;
 
     for( ;; )
     {
@@ -373,31 +670,44 @@ static void *Thread( void *p_data )
         if( !p_item )
             break;
 
-        /* Triggers "meta fetcher", eventually fetch meta on the network.
-         * They are identical to "meta reader" expect that may actually
-         * takes time. That's why they are running here.
-         * The result of this fetch is not cached. */
-        FetchMeta( p_fetcher, p_item );
+        /* */
 
-        /* Find art, and download it if needed */
-        int i_ret = FindArt( p_fetcher, p_item );
-        if( i_ret == 1 )
-            i_ret = DownloadArt( p_fetcher, p_item );
+        /* Wait that the input item is preparsed if it is being played */
+        WaitPreparsed( p_fetcher, p_item );
 
-        /* */
-        char *psz_name = input_item_GetName( p_item );
-        if( !i_ret ) /* Art is now in cache */
+        /* process download request. */
+        if( p_item->i_download < 0 )
         {
-            msg_Dbg( obj, "found art for %s in cache", psz_name );
-            input_item_SetArtFetched( p_item, true );
-            var_SetAddress( obj, "item-change", p_item );
+            Download( p_fetcher, p_item );
         }
         else
         {
-            msg_Dbg( obj, "art not found for %s", psz_name );
-            input_item_SetArtNotFound( p_item, true );
+            /* Triggers "meta fetcher", eventually fetch meta on the network.
+             * They are identical to "meta reader" expect that may actually
+             * takes time. That's why they are running here.
+             * The result of this fetch is not cached. */
+            FetchMeta( p_fetcher, p_item );
+
+            /* Find art, and download it if needed */
+            int i_ret = FindArt( p_fetcher, p_item );
+            if( i_ret == 1 )
+                i_ret = DownloadArt( p_fetcher, p_item );
+
+            /* */
+            char *psz_name = input_item_GetName( p_item );
+            if( !i_ret ) /* Art is now in cache */
+            {
+                PL_DEBUG( "found art for %s in cache", psz_name );
+                input_item_SetArtFetched( p_item, true );
+                var_SetAddress( p_playlist, "item-change", p_item );
+            }
+            else
+            {
+                PL_DEBUG( "art not found for %s", psz_name );
+                input_item_SetArtNotFound( p_item, true );
+            }
+            free( psz_name );
         }
-        free( psz_name );
         vlc_gc_decref( p_item );
     }
     return NULL;
diff --git a/src/playlist/fetcher.h b/src/playlist/fetcher.h
index 9d7cae1..348ebc2 100644
--- a/src/playlist/fetcher.h
+++ b/src/playlist/fetcher.h
@@ -36,7 +36,7 @@ typedef struct playlist_fetcher_t playlist_fetcher_t;
 /**
  * This function creates the fetcher object and thread.
  */
-playlist_fetcher_t *playlist_fetcher_New( vlc_object_t * );
+playlist_fetcher_t *playlist_fetcher_New( playlist_t * );
 
 /**
  * This function enqueues the provided item to be art fetched.
-- 
1.7.5.4




More information about the vlc-devel mailing list