mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-15 16:53:18 -05:00
Remove filescanner_icy.c and consolidate in http.c.
Libav 9 does not support ICY metadata, so in that case we must be able to get it outselves.
This commit is contained in:
parent
b8d8df132b
commit
ea29a8d988
@ -101,7 +101,7 @@ forked_daapd_SOURCES = main.c \
|
||||
conffile.c conffile.h \
|
||||
cache.c cache.h \
|
||||
filescanner.c filescanner.h \
|
||||
filescanner_ffmpeg.c filescanner_playlist.c filescanner_icy.c $(ITUNES_SRC) \
|
||||
filescanner_ffmpeg.c filescanner_playlist.c $(ITUNES_SRC) \
|
||||
mdns_avahi.c mdns.h \
|
||||
remote_pairing.c remote_pairing.h \
|
||||
$(EVHTTP_SRC) \
|
||||
|
@ -693,11 +693,7 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct
|
||||
else if (type & F_SCAN_TYPE_URL)
|
||||
{
|
||||
mfi->data_kind = 1; /* url/stream */
|
||||
#if LIBAVFORMAT_VERSION_MAJOR >= 56 || (LIBAVFORMAT_VERSION_MAJOR == 55 && LIBAVFORMAT_VERSION_MINOR >= 13)
|
||||
ret = scan_metadata_ffmpeg(path, mfi);
|
||||
#else
|
||||
ret = scan_metadata_icy(path, mfi);
|
||||
#endif
|
||||
}
|
||||
else if (type & F_SCAN_TYPE_SPOTIFY)
|
||||
{
|
||||
|
@ -498,7 +498,7 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
|
||||
icy_metadata = http_icy_metadata_get(ctx, 0);
|
||||
if (icy_metadata && icy_metadata->name)
|
||||
{
|
||||
DPRINTF(E_DBG, L_SCAN, "libav/ffmpeg found ICY metadata, name is '%s'\n", icy_metadata->name);
|
||||
DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, name is '%s'\n", icy_metadata->name);
|
||||
|
||||
if (mfi->title)
|
||||
free(mfi->title);
|
||||
@ -513,7 +513,7 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
|
||||
}
|
||||
if (icy_metadata && icy_metadata->description)
|
||||
{
|
||||
DPRINTF(E_DBG, L_SCAN, "libav/ffmpeg found ICY metadata, description is '%s'\n", icy_metadata->description);
|
||||
DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, description is '%s'\n", icy_metadata->description);
|
||||
|
||||
if (mfi->album)
|
||||
free(mfi->album);
|
||||
@ -522,7 +522,7 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
|
||||
}
|
||||
if (icy_metadata && icy_metadata->genre)
|
||||
{
|
||||
DPRINTF(E_DBG, L_SCAN, "libav/ffmpeg found ICY metadata, genre is '%s'\n", icy_metadata->genre);
|
||||
DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, genre is '%s'\n", icy_metadata->genre);
|
||||
|
||||
if (mfi->genre)
|
||||
free(mfi->genre);
|
||||
|
@ -1,348 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2010 Julien BLACHE <jb@jblache.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
|
||||
#include <event.h>
|
||||
#if defined HAVE_LIBEVENT2
|
||||
# include <event2/http.h>
|
||||
#else
|
||||
# include "evhttp/evhttp_compat.h"
|
||||
#endif
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "logger.h"
|
||||
#include "filescanner.h"
|
||||
#include "misc.h"
|
||||
|
||||
#define ICY_TIMEOUT 3
|
||||
|
||||
enum icy_request_status { ICY_INIT, ICY_WAITING, ICY_DONE };
|
||||
|
||||
static enum icy_request_status status;
|
||||
|
||||
/* TODO why doesn't evbase_scan work... */
|
||||
extern struct event_base *evbase_main;
|
||||
|
||||
struct icy_ctx
|
||||
{
|
||||
char *url;
|
||||
char address[INET6_ADDRSTRLEN];
|
||||
char hostname[PATH_MAX];
|
||||
char path[PATH_MAX];
|
||||
int port;
|
||||
|
||||
char *icy_name;
|
||||
char *icy_description;
|
||||
char *icy_genre;
|
||||
|
||||
pthread_mutex_t lck;
|
||||
pthread_cond_t cond;
|
||||
};
|
||||
|
||||
#ifndef HAVE_LIBEVENT2
|
||||
static int
|
||||
resolve_address(char *hostname, char *s, size_t maxlen)
|
||||
{
|
||||
struct addrinfo *result;
|
||||
int ret;
|
||||
|
||||
ret = getaddrinfo(hostname, NULL, NULL, &result);
|
||||
if (ret != 0)
|
||||
return -1;
|
||||
|
||||
switch(result->ai_addr->sa_family)
|
||||
{
|
||||
case AF_INET:
|
||||
inet_ntop(AF_INET, &(((struct sockaddr_in *)result->ai_addr)->sin_addr), s, maxlen);
|
||||
break;
|
||||
|
||||
case AF_INET6:
|
||||
inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)result->ai_addr)->sin6_addr), s, maxlen);
|
||||
break;
|
||||
|
||||
default:
|
||||
strncpy(s, "Unknown AF", maxlen);
|
||||
freeaddrinfo(result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_LIBEVENT2_OLD
|
||||
static void
|
||||
scan_icy_request_cb(struct evhttp_request *req, void *arg)
|
||||
{
|
||||
struct icy_ctx *ctx;
|
||||
|
||||
ctx = (struct icy_ctx *)arg;
|
||||
|
||||
pthread_mutex_lock(&ctx->lck);
|
||||
|
||||
DPRINTF(E_DBG, L_SCAN, "ICY metadata request: Signal callback\n");
|
||||
|
||||
status = ICY_DONE;
|
||||
pthread_cond_signal(&ctx->cond);
|
||||
pthread_mutex_unlock(&ctx->lck);
|
||||
}
|
||||
|
||||
/* Will always return -1 to make evhttp close the connection - we only need the http headers */
|
||||
static int
|
||||
scan_icy_header_cb(struct evhttp_request *req, void *arg)
|
||||
{
|
||||
struct icy_ctx *ctx;
|
||||
struct evkeyvalq *headers;
|
||||
const char *ptr;
|
||||
|
||||
ctx = (struct icy_ctx *)arg;
|
||||
|
||||
DPRINTF(E_DBG, L_SCAN, "ICY metadata request: Headers received\n");
|
||||
|
||||
headers = evhttp_request_get_input_headers(req);
|
||||
if ( (ptr = evhttp_find_header(headers, "icy-name")) )
|
||||
{
|
||||
ctx->icy_name = strdup(ptr);
|
||||
DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, name is %s\n", ctx->icy_name);
|
||||
}
|
||||
if ( (ptr = evhttp_find_header(headers, "icy-description")) )
|
||||
{
|
||||
ctx->icy_description = strdup(ptr);
|
||||
DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, description is %s\n", ctx->icy_description);
|
||||
}
|
||||
if ( (ptr = evhttp_find_header(headers, "icy-genre")) )
|
||||
{
|
||||
ctx->icy_genre = strdup(ptr);
|
||||
DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, genre is %s\n", ctx->icy_genre);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int
|
||||
scan_metadata_icy(char *url, struct media_file_info *mfi)
|
||||
{
|
||||
struct icy_ctx *ctx;
|
||||
struct evhttp_connection *evcon;
|
||||
#ifndef HAVE_LIBEVENT2_OLD
|
||||
struct evhttp_request *req;
|
||||
struct evkeyvalq *headers;
|
||||
char s[PATH_MAX];
|
||||
#endif
|
||||
time_t start;
|
||||
time_t end;
|
||||
int ret;
|
||||
|
||||
status = ICY_INIT;
|
||||
start = time(NULL);
|
||||
|
||||
/* We can set this straight away */
|
||||
mfi->url = strdup(url);
|
||||
|
||||
ctx = (struct icy_ctx *)malloc(sizeof(struct icy_ctx));
|
||||
if (!ctx)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Out of memory for ICY metadata context\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
memset(ctx, 0, sizeof(struct icy_ctx));
|
||||
|
||||
pthread_mutex_init(&ctx->lck, NULL);
|
||||
pthread_cond_init(&ctx->cond, NULL);
|
||||
|
||||
ctx->url = url;
|
||||
|
||||
/* TODO https */
|
||||
av_url_split(NULL, 0, NULL, 0, ctx->hostname, sizeof(ctx->hostname), &ctx->port, ctx->path, sizeof(ctx->path), ctx->url);
|
||||
if ((!ctx->hostname) || (strlen(ctx->hostname) == 0))
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Error extracting hostname from playlist URL: %s\n", ctx->url);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ctx->port < 0)
|
||||
ctx->port = 80;
|
||||
|
||||
if (strlen(ctx->path) == 0)
|
||||
{
|
||||
ctx->path[0] = '/';
|
||||
ctx->path[1] = '\0';
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBEVENT2
|
||||
evcon = evhttp_connection_base_new(evbase_main, NULL, ctx->hostname, (unsigned short)ctx->port);
|
||||
if (!evcon)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Could not create connection to %s\n", ctx->hostname);
|
||||
|
||||
goto no_icy;
|
||||
}
|
||||
#else
|
||||
/* Resolve IP address */
|
||||
ret = resolve_address(ctx->hostname, ctx->address, sizeof(ctx->address));
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Could not find IP address of %s\n", ctx->hostname);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_SCAN, "URL %s converted to hostname %s, port %d, path %s, IP %s\n", ctx->url, ctx->hostname, ctx->port, ctx->path, ctx->address);
|
||||
|
||||
/* Set up connection */
|
||||
evcon = evhttp_connection_new(ctx->address, (unsigned short)ctx->port);
|
||||
if (!evcon)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Could not create connection to %s\n", ctx->hostname);
|
||||
|
||||
goto no_icy;
|
||||
}
|
||||
evhttp_connection_set_base(evcon, evbase_main);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBEVENT2_OLD
|
||||
DPRINTF(E_LOG, L_SCAN, "Skipping Shoutcast metadata request for %s (requires libevent>=2.1.4 or libav 10)\n", ctx->hostname);
|
||||
#else
|
||||
evhttp_connection_set_timeout(evcon, ICY_TIMEOUT);
|
||||
|
||||
/* Set up request */
|
||||
req = evhttp_request_new(scan_icy_request_cb, ctx);
|
||||
if (!req)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Could not create request to %s\n", ctx->hostname);
|
||||
|
||||
goto no_icy;
|
||||
}
|
||||
|
||||
evhttp_request_set_header_cb(req, scan_icy_header_cb);
|
||||
|
||||
headers = evhttp_request_get_output_headers(req);
|
||||
snprintf(s, PATH_MAX, "%s:%d", ctx->hostname, ctx->port);
|
||||
evhttp_add_header(headers, "Host", s);
|
||||
evhttp_add_header(headers, "Icy-MetaData", "1");
|
||||
|
||||
/* Make request */
|
||||
DPRINTF(E_INFO, L_SCAN, "Making request to %s asking for ICY (Shoutcast) metadata\n", ctx->hostname);
|
||||
|
||||
status = ICY_WAITING;
|
||||
ret = evhttp_make_request(evcon, req, EVHTTP_REQ_GET, ctx->path);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Error making request to %s\n", ctx->hostname);
|
||||
|
||||
status = ICY_DONE;
|
||||
goto no_icy;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Can't count on server support for ICY metadata, so
|
||||
* while waiting for a reply make a parallel call to scan_metadata_ffmpeg.
|
||||
*/
|
||||
no_icy:
|
||||
ret = scan_metadata_ffmpeg(url, mfi);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "Playlist URL is unavailable for probe/metadata, assuming MP3 encoding\n");
|
||||
mfi->type = strdup("mp3");
|
||||
mfi->codectype = strdup("mpeg");
|
||||
mfi->description = strdup("MPEG audio file");
|
||||
}
|
||||
|
||||
/* Wait for ICY request to complete or timeout */
|
||||
pthread_mutex_lock(&ctx->lck);
|
||||
|
||||
if (status == ICY_WAITING)
|
||||
pthread_cond_wait(&ctx->cond, &ctx->lck);
|
||||
|
||||
pthread_mutex_unlock(&ctx->lck);
|
||||
|
||||
/* Copy result to mfi */
|
||||
if (ctx->icy_name)
|
||||
{
|
||||
if (mfi->title)
|
||||
free(mfi->title);
|
||||
if (mfi->artist)
|
||||
free(mfi->artist);
|
||||
if (mfi->album_artist)
|
||||
free(mfi->album_artist);
|
||||
|
||||
mfi->title = strdup(ctx->icy_name);
|
||||
mfi->artist = strdup(ctx->icy_name);
|
||||
mfi->album_artist = strdup(ctx->icy_name);
|
||||
|
||||
free(ctx->icy_name);
|
||||
}
|
||||
|
||||
if (ctx->icy_description)
|
||||
{
|
||||
if (mfi->album)
|
||||
free(mfi->album);
|
||||
|
||||
mfi->album = ctx->icy_description;
|
||||
}
|
||||
|
||||
if (ctx->icy_genre)
|
||||
{
|
||||
if (mfi->genre)
|
||||
free(mfi->genre);
|
||||
|
||||
mfi->genre = ctx->icy_genre;
|
||||
}
|
||||
|
||||
/* Clean up */
|
||||
if (evcon)
|
||||
evhttp_connection_free(evcon);
|
||||
|
||||
pthread_cond_destroy(&ctx->cond);
|
||||
pthread_mutex_destroy(&ctx->lck);
|
||||
free(ctx);
|
||||
|
||||
end = time(NULL);
|
||||
|
||||
DPRINTF(E_DBG, L_SCAN, "ICY metadata scan of %s completed in %.f sec\n", url, difftime(end, start));
|
||||
|
||||
return 1;
|
||||
}
|
244
src/http.c
244
src/http.c
@ -46,6 +46,38 @@
|
||||
// Number of seconds the client will wait for a response before aborting
|
||||
#define HTTP_CLIENT_TIMEOUT 5
|
||||
|
||||
/* The strict libevent api does not permit walking through an evkeyvalq and saving
|
||||
* all the http headers, so we predefine what we are looking for. You can add
|
||||
* extra headers here that you would like to save.
|
||||
*/
|
||||
static char *header_list[] =
|
||||
{
|
||||
"icy-name",
|
||||
"icy-description",
|
||||
"icy-metaint",
|
||||
"icy-genre",
|
||||
};
|
||||
|
||||
/* Copies headers we are searching for from one keyval struct to another
|
||||
*
|
||||
*/
|
||||
static void
|
||||
headers_save(struct keyval *kv, struct evkeyvalq *headers)
|
||||
{
|
||||
const char *value;
|
||||
int i;
|
||||
|
||||
if (!kv || !headers)
|
||||
return;
|
||||
|
||||
for (i = 0; i < (sizeof(header_list) / sizeof(header_list[0])); i++)
|
||||
{
|
||||
if ( (value = evhttp_find_header(headers, header_list[i])) )
|
||||
keyval_add(kv, header_list[i], value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
request_cb(struct evhttp_request *req, void *arg)
|
||||
{
|
||||
@ -55,15 +87,28 @@ request_cb(struct evhttp_request *req, void *arg)
|
||||
|
||||
ctx = (struct http_client_ctx *)arg;
|
||||
|
||||
response_code = evhttp_request_get_response_code(req);
|
||||
response_code_line = evhttp_request_get_response_code_line(req);
|
||||
if (ctx->headers_only)
|
||||
{
|
||||
ctx->ret = 0;
|
||||
|
||||
if (req == NULL)
|
||||
event_base_loopbreak(ctx->evbase);
|
||||
|
||||
if (ctx->async)
|
||||
free(ctx);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!req)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTP, "Connection to %s failed: Connection timed out\n", ctx->url);
|
||||
goto connection_error;
|
||||
}
|
||||
else if (response_code == 0)
|
||||
|
||||
response_code = evhttp_request_get_response_code(req);
|
||||
response_code_line = evhttp_request_get_response_code_line(req);
|
||||
|
||||
if (response_code == 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTP, "Connection to %s failed: Connection refused\n", ctx->url);
|
||||
goto connection_error;
|
||||
@ -77,10 +122,15 @@ request_cb(struct evhttp_request *req, void *arg)
|
||||
/* Async: we make a callback to caller, Sync: we move the body into the callers evbuf */
|
||||
ctx->ret = 0;
|
||||
|
||||
if (ctx->async)
|
||||
ctx->cb(req, arg);
|
||||
if (!ctx->async)
|
||||
{
|
||||
if (ctx->headers)
|
||||
headers_save(ctx->headers, evhttp_request_get_input_headers(req));
|
||||
if (ctx->body)
|
||||
evbuffer_add_buffer(ctx->body, evhttp_request_get_input_buffer(req));
|
||||
}
|
||||
else
|
||||
evbuffer_add_buffer(ctx->evbuf, evhttp_request_get_input_buffer(req));
|
||||
ctx->cb(req, arg);
|
||||
|
||||
event_base_loopbreak(ctx->evbase);
|
||||
|
||||
@ -101,6 +151,31 @@ request_cb(struct evhttp_request *req, void *arg)
|
||||
return;
|
||||
}
|
||||
|
||||
/* This callback is only invoked if ctx->headers_only is set. Since that means
|
||||
* we only want headers, it will always return -1 to make evhttp close the
|
||||
* connection. The headers will be saved in a keyval struct in ctx, since we
|
||||
* cannot address the *evkeyvalq after the connection is free'd.
|
||||
*/
|
||||
#ifndef HAVE_LIBEVENT2_OLD
|
||||
static int
|
||||
request_header_cb(struct evhttp_request *req, void *arg)
|
||||
{
|
||||
struct http_client_ctx *ctx;
|
||||
|
||||
ctx = (struct http_client_ctx *)arg;
|
||||
|
||||
if (!ctx->headers)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTP, "BUG: Header callback invoked but caller did not say where to save the headers\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
headers_save(ctx->headers, evhttp_request_get_input_headers(req));
|
||||
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void *
|
||||
request_make(void *arg)
|
||||
{
|
||||
@ -165,19 +240,25 @@ request_make(void *arg)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifndef HAVE_LIBEVENT2_OLD
|
||||
if (ctx->headers_only)
|
||||
evhttp_request_set_header_cb(req, request_header_cb);
|
||||
#endif
|
||||
|
||||
headers = evhttp_request_get_output_headers(req);
|
||||
snprintf(s, PATH_MAX, "%s:%d", hostname, port);
|
||||
evhttp_add_header(headers, "Host", s);
|
||||
evhttp_add_header(headers, "Content-Length", "0");
|
||||
evhttp_add_header(headers, "User-Agent", "forked-daapd/" VERSION);
|
||||
evhttp_add_header(headers, "Icy-MetaData", "1");
|
||||
|
||||
/* Make request */
|
||||
DPRINTF(E_INFO, L_HTTP, "Making request to %s asking for playlist\n", hostname);
|
||||
DPRINTF(E_INFO, L_HTTP, "Making request to %s:%d\n", hostname, port);
|
||||
|
||||
ret = evhttp_make_request(evcon, req, EVHTTP_REQ_GET, path);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTP, "Error making http request to %s\n", hostname);
|
||||
DPRINTF(E_LOG, L_HTTP, "Error making http request to %s:%d\n", hostname, port);
|
||||
|
||||
evhttp_connection_free(evcon);
|
||||
event_base_free(ctx->evbase);
|
||||
@ -254,7 +335,7 @@ http_stream_setup(char **stream, const char *url)
|
||||
|
||||
ctx.async = 0;
|
||||
ctx.url = url;
|
||||
ctx.evbuf = evbuf;
|
||||
ctx.body = evbuf;
|
||||
|
||||
ret = http_client_request(&ctx);
|
||||
if (ret < 0)
|
||||
@ -269,7 +350,7 @@ http_stream_setup(char **stream, const char *url)
|
||||
* nothing is found in the first 10 lines
|
||||
*/
|
||||
n = 0;
|
||||
while ((line = evbuffer_readln(ctx.evbuf, NULL, EVBUFFER_EOL_ANY)) && (n < 10))
|
||||
while ((line = evbuffer_readln(ctx.body, NULL, EVBUFFER_EOL_ANY)) && (n < 10))
|
||||
{
|
||||
n++;
|
||||
if (strncasecmp(line, "http://", strlen("http://")) == 0)
|
||||
@ -283,7 +364,7 @@ http_stream_setup(char **stream, const char *url)
|
||||
free(line);
|
||||
}
|
||||
|
||||
evbuffer_free(ctx.evbuf);
|
||||
evbuffer_free(ctx.body);
|
||||
|
||||
if (n != -1)
|
||||
{
|
||||
@ -300,6 +381,8 @@ http_stream_setup(char **stream, const char *url)
|
||||
|
||||
/* ======================= ICY metadata handling =============================*/
|
||||
|
||||
|
||||
#if LIBAVFORMAT_VERSION_MAJOR >= 56 || (LIBAVFORMAT_VERSION_MAJOR == 55 && LIBAVFORMAT_VERSION_MINOR >= 13)
|
||||
static int
|
||||
metadata_packet_get(struct http_icy_metadata *metadata, AVFormatContext *fmtctx)
|
||||
{
|
||||
@ -404,31 +487,6 @@ metadata_header_get(struct http_icy_metadata *metadata, AVFormatContext *fmtctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
http_icy_metadata_free(struct http_icy_metadata *metadata)
|
||||
{
|
||||
if (metadata->name)
|
||||
free(metadata->name);
|
||||
|
||||
if (metadata->description)
|
||||
free(metadata->description);
|
||||
|
||||
if (metadata->genre)
|
||||
free(metadata->genre);
|
||||
|
||||
if (metadata->title)
|
||||
free(metadata->title);
|
||||
|
||||
if (metadata->artist)
|
||||
free(metadata->artist);
|
||||
|
||||
if (metadata->artwork_url)
|
||||
free(metadata->artwork_url);
|
||||
|
||||
free(metadata);
|
||||
}
|
||||
|
||||
#if LIBAVFORMAT_VERSION_MAJOR >= 56 || (LIBAVFORMAT_VERSION_MAJOR == 55 && LIBAVFORMAT_VERSION_MINOR >= 13)
|
||||
struct http_icy_metadata *
|
||||
http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only)
|
||||
{
|
||||
@ -462,11 +520,121 @@ http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only)
|
||||
*/
|
||||
return metadata;
|
||||
}
|
||||
#else
|
||||
|
||||
#elif defined(HAVE_LIBEVENT2_OLD)
|
||||
struct http_icy_metadata *
|
||||
http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only)
|
||||
{
|
||||
DPRINTF(E_INFO, L_HTTP, "Skipping Shoutcast metadata request for %s (requires libevent>=2.1.4 or libav 10)\n", fmtctx->filename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#else
|
||||
/* Earlier versions of ffmpeg/libav do not seem to allow access to the http
|
||||
* headers, so we must instead open the stream ourselves to get the metadata.
|
||||
* Sorry about the extra connections, you radio streaming people!
|
||||
*
|
||||
* TODO: Get packet metadata from fmtctx->packet_buffer
|
||||
*/
|
||||
struct http_icy_metadata *
|
||||
http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only)
|
||||
{
|
||||
struct http_icy_metadata *metadata;
|
||||
struct http_client_ctx ctx;
|
||||
struct keyval *kv;
|
||||
const char *value;
|
||||
int got_header;
|
||||
int ret;
|
||||
|
||||
/* Can only get header metadata at the moment */
|
||||
if (packet_only)
|
||||
return NULL;
|
||||
|
||||
kv = keyval_alloc();
|
||||
if (!kv)
|
||||
return NULL;
|
||||
|
||||
memset(&ctx, 0, sizeof(struct http_client_ctx));
|
||||
ctx.async = 0;
|
||||
ctx.url = fmtctx->filename;
|
||||
ctx.headers = kv;
|
||||
ctx.headers_only = 1;
|
||||
ctx.body = NULL;
|
||||
|
||||
ret = http_client_request(&ctx);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTP, "Error fetching %s\n", fmtctx->filename);
|
||||
|
||||
free(kv);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
metadata = malloc(sizeof(struct http_icy_metadata));
|
||||
if (!metadata)
|
||||
return NULL;
|
||||
memset(metadata, 0, sizeof(struct http_icy_metadata));
|
||||
|
||||
got_header = 0;
|
||||
if ( (value = keyval_get(ctx.headers, "icy-name")) )
|
||||
{
|
||||
metadata->name = strdup(value);
|
||||
got_header = 1;
|
||||
}
|
||||
if ( (value = keyval_get(ctx.headers, "icy-description")) )
|
||||
{
|
||||
metadata->description = strdup(value);
|
||||
got_header = 1;
|
||||
}
|
||||
if ( (value = keyval_get(ctx.headers, "icy-genre")) )
|
||||
{
|
||||
metadata->genre = strdup(value);
|
||||
got_header = 1;
|
||||
}
|
||||
|
||||
keyval_clear(kv);
|
||||
free(kv);
|
||||
|
||||
if (!got_header)
|
||||
{
|
||||
free(metadata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* DPRINTF(E_DBG, L_HTTP, "Found ICY: N %s, D %s, G %s, T %s, A %s, U %s, I %" PRIu32 "\n",
|
||||
metadata->name,
|
||||
metadata->description,
|
||||
metadata->genre,
|
||||
metadata->title,
|
||||
metadata->artist,
|
||||
metadata->artwork_url,
|
||||
metadata->hash
|
||||
);*/
|
||||
|
||||
return metadata;
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
http_icy_metadata_free(struct http_icy_metadata *metadata)
|
||||
{
|
||||
if (metadata->name)
|
||||
free(metadata->name);
|
||||
|
||||
if (metadata->description)
|
||||
free(metadata->description);
|
||||
|
||||
if (metadata->genre)
|
||||
free(metadata->genre);
|
||||
|
||||
if (metadata->title)
|
||||
free(metadata->title);
|
||||
|
||||
if (metadata->artist)
|
||||
free(metadata->artist);
|
||||
|
||||
if (metadata->artwork_url)
|
||||
free(metadata->artwork_url);
|
||||
|
||||
free(metadata);
|
||||
}
|
||||
|
29
src/http.h
29
src/http.h
@ -4,6 +4,7 @@
|
||||
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/http.h>
|
||||
#include "misc.h"
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
@ -13,8 +14,17 @@ struct http_client_ctx
|
||||
const char *url;
|
||||
int ret;
|
||||
|
||||
/* For sync mode */
|
||||
struct evbuffer *evbuf;
|
||||
/* For sync mode, a keyval/evbuf to store response headers and body
|
||||
* Can be set to NULL to ignore that part of the response
|
||||
*/
|
||||
struct keyval *headers;
|
||||
struct evbuffer *body;
|
||||
|
||||
/* Cut the connection after the headers have been received
|
||||
* Used for getting Shoutcast/ICY headers for old versions of libav/ffmpeg
|
||||
* (requires libevent 1 or 2.1.4+)
|
||||
*/
|
||||
int headers_only;
|
||||
|
||||
/* For async mode */
|
||||
void (*cb)(struct evhttp_request *, void *);
|
||||
@ -60,14 +70,6 @@ int
|
||||
http_stream_setup(char **stream, const char *url);
|
||||
|
||||
|
||||
/* Frees a ICY metadata struct
|
||||
*
|
||||
* @param metadata struct to free
|
||||
*/
|
||||
void
|
||||
http_icy_metadata_free(struct http_icy_metadata *metadata);
|
||||
|
||||
|
||||
/* Extracts ICY header and packet metadata (requires libav 10)
|
||||
*
|
||||
* example header metadata (standard http header format):
|
||||
@ -86,4 +88,11 @@ struct http_icy_metadata *
|
||||
http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only);
|
||||
|
||||
|
||||
/* Frees an ICY metadata struct
|
||||
*
|
||||
* @param metadata struct to free
|
||||
*/
|
||||
void
|
||||
http_icy_metadata_free(struct http_icy_metadata *metadata);
|
||||
|
||||
#endif /* !__HTTP_H__ */
|
||||
|
Loading…
x
Reference in New Issue
Block a user