mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-16 01:03:16 -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 \
|
conffile.c conffile.h \
|
||||||
cache.c cache.h \
|
cache.c cache.h \
|
||||||
filescanner.c filescanner.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 \
|
mdns_avahi.c mdns.h \
|
||||||
remote_pairing.c remote_pairing.h \
|
remote_pairing.c remote_pairing.h \
|
||||||
$(EVHTTP_SRC) \
|
$(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)
|
else if (type & F_SCAN_TYPE_URL)
|
||||||
{
|
{
|
||||||
mfi->data_kind = 1; /* url/stream */
|
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);
|
ret = scan_metadata_ffmpeg(path, mfi);
|
||||||
#else
|
|
||||||
ret = scan_metadata_icy(path, mfi);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
else if (type & F_SCAN_TYPE_SPOTIFY)
|
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);
|
icy_metadata = http_icy_metadata_get(ctx, 0);
|
||||||
if (icy_metadata && icy_metadata->name)
|
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)
|
if (mfi->title)
|
||||||
free(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)
|
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)
|
if (mfi->album)
|
||||||
free(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)
|
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)
|
if (mfi->genre)
|
||||||
free(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
|
// Number of seconds the client will wait for a response before aborting
|
||||||
#define HTTP_CLIENT_TIMEOUT 5
|
#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
|
static void
|
||||||
request_cb(struct evhttp_request *req, void *arg)
|
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;
|
ctx = (struct http_client_ctx *)arg;
|
||||||
|
|
||||||
response_code = evhttp_request_get_response_code(req);
|
if (ctx->headers_only)
|
||||||
response_code_line = evhttp_request_get_response_code_line(req);
|
{
|
||||||
|
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);
|
DPRINTF(E_LOG, L_HTTP, "Connection to %s failed: Connection timed out\n", ctx->url);
|
||||||
goto connection_error;
|
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);
|
DPRINTF(E_LOG, L_HTTP, "Connection to %s failed: Connection refused\n", ctx->url);
|
||||||
goto connection_error;
|
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 */
|
/* Async: we make a callback to caller, Sync: we move the body into the callers evbuf */
|
||||||
ctx->ret = 0;
|
ctx->ret = 0;
|
||||||
|
|
||||||
if (ctx->async)
|
if (!ctx->async)
|
||||||
ctx->cb(req, arg);
|
{
|
||||||
|
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
|
else
|
||||||
evbuffer_add_buffer(ctx->evbuf, evhttp_request_get_input_buffer(req));
|
ctx->cb(req, arg);
|
||||||
|
|
||||||
event_base_loopbreak(ctx->evbase);
|
event_base_loopbreak(ctx->evbase);
|
||||||
|
|
||||||
@ -101,6 +151,31 @@ request_cb(struct evhttp_request *req, void *arg)
|
|||||||
return;
|
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 *
|
static void *
|
||||||
request_make(void *arg)
|
request_make(void *arg)
|
||||||
{
|
{
|
||||||
@ -165,19 +240,25 @@ request_make(void *arg)
|
|||||||
return NULL;
|
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);
|
headers = evhttp_request_get_output_headers(req);
|
||||||
snprintf(s, PATH_MAX, "%s:%d", hostname, port);
|
snprintf(s, PATH_MAX, "%s:%d", hostname, port);
|
||||||
evhttp_add_header(headers, "Host", s);
|
evhttp_add_header(headers, "Host", s);
|
||||||
evhttp_add_header(headers, "Content-Length", "0");
|
evhttp_add_header(headers, "Content-Length", "0");
|
||||||
evhttp_add_header(headers, "User-Agent", "forked-daapd/" VERSION);
|
evhttp_add_header(headers, "User-Agent", "forked-daapd/" VERSION);
|
||||||
|
evhttp_add_header(headers, "Icy-MetaData", "1");
|
||||||
|
|
||||||
/* Make request */
|
/* 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);
|
ret = evhttp_make_request(evcon, req, EVHTTP_REQ_GET, path);
|
||||||
if (ret < 0)
|
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);
|
evhttp_connection_free(evcon);
|
||||||
event_base_free(ctx->evbase);
|
event_base_free(ctx->evbase);
|
||||||
@ -254,7 +335,7 @@ http_stream_setup(char **stream, const char *url)
|
|||||||
|
|
||||||
ctx.async = 0;
|
ctx.async = 0;
|
||||||
ctx.url = url;
|
ctx.url = url;
|
||||||
ctx.evbuf = evbuf;
|
ctx.body = evbuf;
|
||||||
|
|
||||||
ret = http_client_request(&ctx);
|
ret = http_client_request(&ctx);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
@ -269,7 +350,7 @@ http_stream_setup(char **stream, const char *url)
|
|||||||
* nothing is found in the first 10 lines
|
* nothing is found in the first 10 lines
|
||||||
*/
|
*/
|
||||||
n = 0;
|
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++;
|
n++;
|
||||||
if (strncasecmp(line, "http://", strlen("http://")) == 0)
|
if (strncasecmp(line, "http://", strlen("http://")) == 0)
|
||||||
@ -283,7 +364,7 @@ http_stream_setup(char **stream, const char *url)
|
|||||||
free(line);
|
free(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
evbuffer_free(ctx.evbuf);
|
evbuffer_free(ctx.body);
|
||||||
|
|
||||||
if (n != -1)
|
if (n != -1)
|
||||||
{
|
{
|
||||||
@ -300,6 +381,8 @@ http_stream_setup(char **stream, const char *url)
|
|||||||
|
|
||||||
/* ======================= ICY metadata handling =============================*/
|
/* ======================= ICY metadata handling =============================*/
|
||||||
|
|
||||||
|
|
||||||
|
#if LIBAVFORMAT_VERSION_MAJOR >= 56 || (LIBAVFORMAT_VERSION_MAJOR == 55 && LIBAVFORMAT_VERSION_MINOR >= 13)
|
||||||
static int
|
static int
|
||||||
metadata_packet_get(struct http_icy_metadata *metadata, AVFormatContext *fmtctx)
|
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;
|
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 *
|
struct http_icy_metadata *
|
||||||
http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only)
|
http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only)
|
||||||
{
|
{
|
||||||
@ -462,11 +520,121 @@ http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only)
|
|||||||
*/
|
*/
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
|
#elif defined(HAVE_LIBEVENT2_OLD)
|
||||||
struct http_icy_metadata *
|
struct http_icy_metadata *
|
||||||
http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only)
|
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;
|
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
|
#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/buffer.h>
|
||||||
#include <event2/http.h>
|
#include <event2/http.h>
|
||||||
|
#include "misc.h"
|
||||||
|
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
@ -13,8 +14,17 @@ struct http_client_ctx
|
|||||||
const char *url;
|
const char *url;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* For sync mode */
|
/* For sync mode, a keyval/evbuf to store response headers and body
|
||||||
struct evbuffer *evbuf;
|
* 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 */
|
/* For async mode */
|
||||||
void (*cb)(struct evhttp_request *, void *);
|
void (*cb)(struct evhttp_request *, void *);
|
||||||
@ -60,14 +70,6 @@ int
|
|||||||
http_stream_setup(char **stream, const char *url);
|
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)
|
/* Extracts ICY header and packet metadata (requires libav 10)
|
||||||
*
|
*
|
||||||
* example header metadata (standard http header format):
|
* example header metadata (standard http header format):
|
||||||
@ -86,4 +88,11 @@ struct http_icy_metadata *
|
|||||||
http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only);
|
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__ */
|
#endif /* !__HTTP_H__ */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user