1) Taglib does not support MP4 or WMA/ASF without hacking it in there. 2) Taglib is C++, so it's nice to remove that dependency. * Use embedded album art where available.
359 lines
12 KiB
C
359 lines
12 KiB
C
#include "config.h"
|
|
#ifdef ENABLE_TIVO
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "tivo_utils.h"
|
|
#include "upnpglobalvars.h"
|
|
#include "upnphttp.h"
|
|
#include "log.h"
|
|
|
|
void
|
|
SendRootContainer(struct upnphttp * h)
|
|
{
|
|
char * resp;
|
|
int len;
|
|
|
|
len = asprintf(&resp, "<?xml version='1.0' encoding='UTF-8' ?>\n"
|
|
"<TiVoContainer>"
|
|
"<Details>"
|
|
"<ContentType>x-container/tivo-server</ContentType>"
|
|
"<SourceFormat>x-container/folder</SourceFormat>"
|
|
"<TotalDuration>0</TotalDuration>"
|
|
"<TotalItems>2</TotalItems>"
|
|
"<Title>%s</Title>"
|
|
"</Details>"
|
|
"<ItemStart>0</ItemStart>"
|
|
"<ItemCount>2</ItemCount>"
|
|
"<Item>"
|
|
"<Details>"
|
|
"<ContentType>x-container/tivo-photos</ContentType>"
|
|
"<SourceFormat>x-container/folder</SourceFormat>"
|
|
"<Title>Pictures on %s</Title>"
|
|
"</Details>"
|
|
"<Links>"
|
|
"<Content>"
|
|
"<Url>/TiVoConnect?Command=QueryContainer&Container=3/Pictures on %s</Url>"
|
|
"</Content>"
|
|
"</Links>"
|
|
"</Item>"
|
|
"<Item>"
|
|
"<Details>"
|
|
"<ContentType>x-container/tivo-music</ContentType>"
|
|
"<SourceFormat>x-container/folder</SourceFormat>"
|
|
"<Title>Music on %s</Title>"
|
|
"</Details>"
|
|
"<Links>"
|
|
"<Content>"
|
|
"<Url>/TiVoConnect?Command=QueryContainer&Container=1</Url>"
|
|
"</Content>"
|
|
"</Links>"
|
|
"</Item>"
|
|
"</TiVoContainer>", friendly_name, friendly_name, friendly_name, friendly_name);
|
|
BuildResp_upnphttp(h, resp, len);
|
|
SendResp_upnphttp(h);
|
|
}
|
|
|
|
#if 0
|
|
int callback(void *args, int argc, char **argv, char **azColName)
|
|
{
|
|
struct Response *passed_args = (struct Response *)args;
|
|
char *id = argv[1], *parent = argv[2], *refID = argv[3], *class = argv[4], *detailID = argv[5], *size = argv[9], *title = argv[10],
|
|
*duration = argv[11], *bitrate = argv[12], *sampleFrequency = argv[13], *artist = argv[14], *album = argv[15],
|
|
*genre = argv[16], *comment = argv[17], *nrAudioChannels = argv[18], *track = argv[19], *date = argv[20], *resolution = argv[21],
|
|
*tn = argv[22], *creator = argv[23], *dlna_pn = argv[24], *mime = argv[25], *album_art = argv[26], *art_dlna_pn = argv[27];
|
|
char dlna_buf[64];
|
|
char str_buf[4096];
|
|
char **result;
|
|
int ret;
|
|
|
|
passed_args->total++;
|
|
|
|
if( passed_args->requested && (passed_args->returned >= passed_args->requested) )
|
|
return 0;
|
|
passed_args->returned++;
|
|
|
|
if( dlna_pn )
|
|
sprintf(dlna_buf, "DLNA.ORG_PN=%s", dlna_pn);
|
|
else
|
|
strcpy(dlna_buf, "*");
|
|
|
|
if( strncmp(class, "item", 4) == 0 )
|
|
{
|
|
if( passed_args->client == EXbox )
|
|
{
|
|
if( strcmp(mime, "video/divx") == 0 )
|
|
{
|
|
mime[6] = 'a';
|
|
mime[7] = 'v';
|
|
mime[8] = 'i';
|
|
mime[9] = '\0';
|
|
}
|
|
}
|
|
sprintf(str_buf, "<item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id, parent);
|
|
strcat(passed_args->resp, str_buf);
|
|
if( refID && (!passed_args->filter || strstr(passed_args->filter, "@refID")) ) {
|
|
sprintf(str_buf, " refID=\"%s\"", refID);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
sprintf(str_buf, ">"
|
|
"<dc:title>%s</dc:title>"
|
|
"<upnp:class>object.%s</upnp:class>",
|
|
title, class);
|
|
strcat(passed_args->resp, str_buf);
|
|
if( comment && (!passed_args->filter || strstr(passed_args->filter, "dc:description")) ) {
|
|
sprintf(str_buf, "<dc:description>%s</dc:description>", comment);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( creator && (!passed_args->filter || strstr(passed_args->filter, "dc:creator")) ) {
|
|
sprintf(str_buf, "<dc:creator>%s</dc:creator>", creator);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( date && (!passed_args->filter || strstr(passed_args->filter, "dc:date")) ) {
|
|
sprintf(str_buf, "<dc:date>%s</dc:date>", date);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( artist && (!passed_args->filter || strstr(passed_args->filter, "upnp:artist")) ) {
|
|
sprintf(str_buf, "<upnp:artist>%s</upnp:artist>", artist);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( album && (!passed_args->filter || strstr(passed_args->filter, "upnp:album")) ) {
|
|
sprintf(str_buf, "<upnp:album>%s</upnp:album>", album);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( genre && (!passed_args->filter || strstr(passed_args->filter, "upnp:genre")) ) {
|
|
sprintf(str_buf, "<upnp:genre>%s</upnp:genre>", genre);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( track && atoi(track) && (!passed_args->filter || strstr(passed_args->filter, "upnp:originalTrackNumber")) ) {
|
|
sprintf(str_buf, "<upnp:originalTrackNumber>%s</upnp:originalTrackNumber>", track);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( album_art && atoi(album_art) && (!passed_args->filter || strstr(passed_args->filter, "upnp:albumArtURI")) ) {
|
|
strcat(passed_args->resp, "<upnp:albumArtURI ");
|
|
if( !passed_args->filter || strstr(passed_args->filter, "upnp:albumArtURI@dlna:profileID") ) {
|
|
sprintf(str_buf, "dlna:profileID=\"%s\" xmlns:dlna=\"urn:schemas-dlnaorg:metadata-1-0/\"", art_dlna_pn);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
sprintf(str_buf, ">http://%s:%d/AlbumArt/%s.jpg</upnp:albumArtURI>",
|
|
lan_addr[0].str, runtime_vars.port, album_art);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( !passed_args->filter || strstr(passed_args->filter, "res") ) {
|
|
strcat(passed_args->resp, "<res ");
|
|
if( size && (!passed_args->filter || strstr(passed_args->filter, "res@size")) ) {
|
|
sprintf(str_buf, "size=\"%s\" ", size);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( duration && (!passed_args->filter || strstr(passed_args->filter, "res@duration")) ) {
|
|
sprintf(str_buf, "duration=\"%s\" ", duration);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( bitrate && (!passed_args->filter || strstr(passed_args->filter, "res@bitrate")) ) {
|
|
sprintf(str_buf, "bitrate=\"%s\" ", bitrate);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( sampleFrequency && (!passed_args->filter || strstr(passed_args->filter, "res@sampleFrequency")) ) {
|
|
sprintf(str_buf, "sampleFrequency=\"%s\" ", sampleFrequency);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( nrAudioChannels && (!passed_args->filter || strstr(passed_args->filter, "res@nrAudioChannels")) ) {
|
|
sprintf(str_buf, "nrAudioChannels=\"%s\" ", nrAudioChannels);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( resolution && (!passed_args->filter || strstr(passed_args->filter, "res@resolution")) ) {
|
|
sprintf(str_buf, "resolution=\"%s\" ", resolution);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\">"
|
|
"http://%s:%d/MediaItems/%s.dat"
|
|
"</res>",
|
|
mime, dlna_buf, lan_addr[0].str, runtime_vars.port, detailID);
|
|
#if 0 //JPEG_RESIZE
|
|
if( dlna_pn && (strncmp(dlna_pn, "JPEG_LRG", 8) == 0) ) {
|
|
strcat(passed_args->resp, str_buf);
|
|
sprintf(str_buf, "<res "
|
|
"protocolInfo=\"http-get:*:%s:%s\">"
|
|
"http://%s:%d/Resized/%s"
|
|
"</res>",
|
|
mime, "DLNA.ORG_PN=JPEG_SM", lan_addr[0].str, runtime_vars.port, id);
|
|
}
|
|
#endif
|
|
if( tn && atoi(tn) && dlna_pn ) {
|
|
strcat(passed_args->resp, str_buf);
|
|
strcat(passed_args->resp, "<res ");
|
|
sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\">"
|
|
"http://%s:%d/Thumbnails/%s.dat"
|
|
"</res>",
|
|
mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, runtime_vars.port, detailID);
|
|
}
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
strcpy(str_buf, "</item>");
|
|
}
|
|
else if( strncmp(class, "container", 9) == 0 )
|
|
{
|
|
sprintf(str_buf, "SELECT count(ID) from OBJECTS where PARENT_ID = '%s';", id);
|
|
ret = sql_get_table(db, str_buf, &result, NULL, NULL);
|
|
sprintf(str_buf, "<container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent);
|
|
strcat(passed_args->resp, str_buf);
|
|
if( !passed_args->filter || strstr(passed_args->filter, "@childCount"))
|
|
{
|
|
sprintf(str_buf, "childCount=\"%s\"", result[1]);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
/* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */
|
|
if( (passed_args->requested == 1) && (strcmp(id, "0") == 0) )
|
|
{
|
|
if( !passed_args->filter || strstr(passed_args->filter, "upnp:searchClass") )
|
|
{
|
|
strcat(passed_args->resp, ">"
|
|
"<upnp:searchClass includeDerived=\"1\">object.item.audioItem</upnp:searchClass>"
|
|
"<upnp:searchClass includeDerived=\"1\">object.item.imageItem</upnp:searchClass>"
|
|
"<upnp:searchClass includeDerived=\"1\">object.item.videoItem</upnp:searchClass");
|
|
}
|
|
}
|
|
sprintf(str_buf, ">"
|
|
"<dc:title>%s</dc:title>"
|
|
"<upnp:class>object.%s</upnp:class>",
|
|
title, class);
|
|
strcat(passed_args->resp, str_buf);
|
|
if( creator && (!passed_args->filter || strstr(passed_args->filter, "dc:creator")) ) {
|
|
sprintf(str_buf, "<dc:creator>%s</dc:creator>", creator);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( genre && (!passed_args->filter || strstr(passed_args->filter, "upnp:genre")) ) {
|
|
sprintf(str_buf, "<upnp:genre>%s</upnp:genre>", genre);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( artist && (!passed_args->filter || strstr(passed_args->filter, "upnp:artist")) ) {
|
|
sprintf(str_buf, "<upnp:artist>%s</upnp:artist>", artist);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
if( album_art && atoi(album_art) && (!passed_args->filter || strstr(passed_args->filter, "upnp:albumArtURI")) ) {
|
|
strcat(passed_args->resp, "<upnp:albumArtURI ");
|
|
if( !passed_args->filter || strstr(passed_args->filter, "upnp:albumArtURI@dlna:profileID") ) {
|
|
sprintf(str_buf, "dlna:profileID=\"%s\" xmlns:dlna=\"urn:schemas-dlnaorg:metadata-1-0/\"", art_dlna_pn);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
sprintf(str_buf, ">http://%s:%d/AlbumArt/%s.jpg</upnp:albumArtURI>",
|
|
lan_addr[0].str, runtime_vars.port, album_art);
|
|
strcat(passed_args->resp, str_buf);
|
|
}
|
|
sprintf(str_buf, "</container>");
|
|
sqlite3_free_table(result);
|
|
}
|
|
strcat(passed_args->resp, str_buf);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
void
|
|
SendContainer(struct upnphttp * h, const char * objectID, const char * title, int itemStart, int itemCount)
|
|
{
|
|
char * resp;
|
|
int len;
|
|
|
|
len = asprintf(&resp, "<?xml version='1.0' encoding='UTF-8' ?>\n"
|
|
"<TiVoContainer>"
|
|
"<Details>"
|
|
"<ContentType>x-container/tivo-%s</ContentType>"
|
|
"<SourceFormat>x-container/folder</SourceFormat>"
|
|
"<TotalDuration>0</TotalDuration>"
|
|
"<TotalItems>2</TotalItems>"
|
|
"<Title>%s</Title>"
|
|
"</Details>"
|
|
"<ItemStart>%d</ItemStart>"
|
|
"<ItemCount>%d</ItemCount>"
|
|
"<Item>"
|
|
"<Details>"
|
|
"<ContentType>x-container/tivo-photos</ContentType>"
|
|
"<SourceFormat>x-container/folder</SourceFormat>"
|
|
"<Title>Pictures on %s</Title>"
|
|
"</Details>"
|
|
"<Links>"
|
|
"<Content>"
|
|
"<Url>/TiVoConnect?Command=QueryContainer&Container=3</Url>"
|
|
"</Content>"
|
|
"</Links>"
|
|
"</Item>"
|
|
"<Item>"
|
|
"<Details>"
|
|
"<ContentType>x-container/tivo-music</ContentType>"
|
|
"<SourceFormat>x-container/folder</SourceFormat>"
|
|
"<Title>Music on %s</Title>"
|
|
"</Details>"
|
|
"<Links>"
|
|
"<Content>"
|
|
"<Url>/TiVoConnect?Command=QueryContainer&Container=1</Url>"
|
|
"</Content>"
|
|
"</Links>"
|
|
"</Item>"
|
|
"</TiVoContainer>",
|
|
(objectID[0]=='1') ? "music":"photos",
|
|
title, itemStart, itemCount, title, title);
|
|
BuildResp_upnphttp(h, resp, len);
|
|
SendResp_upnphttp(h);
|
|
}
|
|
|
|
void
|
|
ProcessTiVoCommand(struct upnphttp * h, const char * orig_path)
|
|
{
|
|
char *path;
|
|
char *key, *val;
|
|
char *saveptr, *item;
|
|
char *command = NULL, *container = NULL;
|
|
int itemStart=0, itemCount=0;
|
|
|
|
path = decodeString(orig_path);
|
|
DPRINTF(E_DEBUG, L_GENERAL, "Processing TiVo command %s\n", path);
|
|
|
|
item = strtok_r( path, "&", &saveptr );
|
|
while( item != NULL )
|
|
{
|
|
if( strlen( item ) == 0 )
|
|
{
|
|
item = strtok_r( NULL, "&", &saveptr );
|
|
continue;
|
|
}
|
|
val = item;
|
|
key = strsep(&val, "=");
|
|
DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val);
|
|
if( strcasecmp(key, "command") == 0 )
|
|
{
|
|
command = val;
|
|
}
|
|
else if( strcasecmp(key, "container") == 0 )
|
|
{
|
|
container = val;
|
|
}
|
|
else if( strcasecmp(key, "itemstart") == 0 )
|
|
{
|
|
itemStart = atoi(val);
|
|
}
|
|
else if( strcasecmp(key, "itemcount") == 0 )
|
|
{
|
|
itemCount = atoi(val);
|
|
}
|
|
item = strtok_r( NULL, "&", &saveptr );
|
|
}
|
|
|
|
if( strcmp(command, "QueryContainer") == 0 )
|
|
{
|
|
if( !container || (strcmp(container, "/") == 0) )
|
|
{
|
|
SendRootContainer(h);
|
|
}
|
|
else
|
|
{
|
|
val = container;
|
|
key = strsep(&val, "/");
|
|
SendContainer(h, container, val, itemStart, itemCount);
|
|
}
|
|
}
|
|
CloseSocket_upnphttp(h);
|
|
}
|
|
#endif // ENABLE_TIVO
|