* Many changes for TiVo support. It actually [kinda] works. :)
This commit is contained in:
parent
3ae378cdc9
commit
7a2e3ae67a
126
albumart.c
126
albumart.c
@ -61,130 +61,6 @@ check_res(int width, int height, char * dlna_pn)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Use our own boxfilter resizer, because gdCopyImageResampled is slow,
|
|
||||||
* and gdCopyImageResized looks horrible when you downscale this much. */
|
|
||||||
#define N_FRAC 8
|
|
||||||
#define MASK_FRAC ((1 << N_FRAC) - 1)
|
|
||||||
#define ROUND2(v) (((v) + (1 << (N_FRAC - 1))) >> N_FRAC)
|
|
||||||
#define DIV(x, y) ( ((x) << (N_FRAC - 3)) / ((y) >> 3) )
|
|
||||||
static void
|
|
||||||
boxfilter_resize(gdImagePtr dst, gdImagePtr src,
|
|
||||||
int dstX, int dstY, int srcX, int srcY,
|
|
||||||
int dstW, int dstH, int srcW, int srcH)
|
|
||||||
{
|
|
||||||
int x, y;
|
|
||||||
int sy1, sy2, sx1, sx2;
|
|
||||||
|
|
||||||
if(!dst->trueColor)
|
|
||||||
{
|
|
||||||
gdImageCopyResized(dst, src, dstX, dstY, srcX, srcY, dstW, dstH,
|
|
||||||
srcW, srcH);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for(y = dstY; y < (dstY + dstH); y++)
|
|
||||||
{
|
|
||||||
sy1 = (((y - dstY) * srcH) << N_FRAC) / dstH;
|
|
||||||
sy2 = (((y - dstY + 1) * srcH) << N_FRAC) / dstH;
|
|
||||||
for(x = dstX; x < (dstX + dstW); x++)
|
|
||||||
{
|
|
||||||
int sx, sy;
|
|
||||||
int spixels = 0;
|
|
||||||
int red = 0, green = 0, blue = 0, alpha = 0;
|
|
||||||
sx1 = (((x - dstX) * srcW) << N_FRAC) / dstW;
|
|
||||||
sx2 = (((x - dstX + 1) * srcW) << N_FRAC) / dstW;
|
|
||||||
sy = sy1;
|
|
||||||
do {
|
|
||||||
int yportion;
|
|
||||||
if((sy >> N_FRAC) == (sy1 >> N_FRAC))
|
|
||||||
{
|
|
||||||
yportion = (1 << N_FRAC) - (sy & MASK_FRAC);
|
|
||||||
if(yportion > sy2 - sy1)
|
|
||||||
{
|
|
||||||
yportion = sy2 - sy1;
|
|
||||||
}
|
|
||||||
sy = sy & ~MASK_FRAC;
|
|
||||||
}
|
|
||||||
else if(sy == (sy2 & ~MASK_FRAC))
|
|
||||||
{
|
|
||||||
yportion = sy2 & MASK_FRAC;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
yportion = (1 << N_FRAC);
|
|
||||||
}
|
|
||||||
sx = sx1;
|
|
||||||
do {
|
|
||||||
int xportion;
|
|
||||||
int pcontribution;
|
|
||||||
int p;
|
|
||||||
if((sx >> N_FRAC) == (sx1 >> N_FRAC))
|
|
||||||
{
|
|
||||||
xportion = (1 << N_FRAC) - (sx & MASK_FRAC);
|
|
||||||
if(xportion > sx2 - sx1)
|
|
||||||
{
|
|
||||||
xportion = sx2 - sx1;
|
|
||||||
}
|
|
||||||
sx = sx & ~MASK_FRAC;
|
|
||||||
}
|
|
||||||
else if(sx == (sx2 & ~MASK_FRAC))
|
|
||||||
{
|
|
||||||
xportion = sx2 & MASK_FRAC;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
xportion = (1 << N_FRAC);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(xportion && yportion)
|
|
||||||
{
|
|
||||||
pcontribution = (xportion * yportion) >> N_FRAC;
|
|
||||||
p = gdImageGetTrueColorPixel(src, ROUND2(sx) + srcX, ROUND2(sy) + srcY);
|
|
||||||
if(pcontribution == (1 << N_FRAC))
|
|
||||||
{
|
|
||||||
// optimization for down-scaler, which many pixel has pcontribution=1
|
|
||||||
red += gdTrueColorGetRed(p) << N_FRAC;
|
|
||||||
green += gdTrueColorGetGreen(p) << N_FRAC;
|
|
||||||
blue += gdTrueColorGetBlue(p) << N_FRAC;
|
|
||||||
alpha += gdTrueColorGetAlpha(p) << N_FRAC;
|
|
||||||
spixels += (1 << N_FRAC);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
red += gdTrueColorGetRed(p) * pcontribution;
|
|
||||||
green += gdTrueColorGetGreen(p) * pcontribution;
|
|
||||||
blue += gdTrueColorGetBlue(p) * pcontribution;
|
|
||||||
alpha += gdTrueColorGetAlpha(p) * pcontribution;
|
|
||||||
spixels += pcontribution;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sx += (1 << N_FRAC);
|
|
||||||
}
|
|
||||||
while(sx < sx2);
|
|
||||||
sy += (1 << N_FRAC);
|
|
||||||
}
|
|
||||||
while(sy < sy2);
|
|
||||||
if(spixels != 0)
|
|
||||||
{
|
|
||||||
red = DIV(red, spixels);
|
|
||||||
green = DIV(green, spixels);
|
|
||||||
blue = DIV(blue, spixels);
|
|
||||||
alpha = DIV(alpha, spixels);
|
|
||||||
}
|
|
||||||
/* Clamping to allow for rounding errors above */
|
|
||||||
if(red > (255 << N_FRAC))
|
|
||||||
red = (255 << N_FRAC);
|
|
||||||
if(green > (255 << N_FRAC))
|
|
||||||
green = (255 << N_FRAC);
|
|
||||||
if(blue > (255 << N_FRAC))
|
|
||||||
blue = (255 << N_FRAC);
|
|
||||||
if(alpha > (gdAlphaMax << N_FRAC))
|
|
||||||
alpha = (gdAlphaMax << N_FRAC);
|
|
||||||
gdImageSetPixel(dst, x, y,
|
|
||||||
gdTrueColorAlpha(ROUND2(red), ROUND2(green), ROUND2(blue), ROUND2(alpha)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *
|
char *
|
||||||
save_resized_album_art(void * ptr, const char * path, int srcw, int srch, int file, int size)
|
save_resized_album_art(void * ptr, const char * path, int srcw, int srch, int file, int size)
|
||||||
{
|
{
|
||||||
@ -230,7 +106,7 @@ save_resized_album_art(void * ptr, const char * path, int srcw, int srch, int fi
|
|||||||
fclose(dstfile);
|
fclose(dstfile);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
#if 0 // Try our box filter resizer for now
|
#if 0 // Try our box filter resizer instead
|
||||||
#ifdef __sparc__
|
#ifdef __sparc__
|
||||||
gdImageCopyResized(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy);
|
gdImageCopyResized(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy);
|
||||||
#else
|
#else
|
||||||
|
58
minidlna.c
58
minidlna.c
@ -46,7 +46,7 @@
|
|||||||
#include "inotify.h"
|
#include "inotify.h"
|
||||||
#include "commonrdr.h"
|
#include "commonrdr.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef TIVO_SUPPORT
|
||||||
#include "tivo_beacon.h"
|
#include "tivo_beacon.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -386,6 +386,10 @@ init(int argc, char * * argv)
|
|||||||
if( (strcmp(ary_options[i].value, "yes") != 0) && !atoi(ary_options[i].value) )
|
if( (strcmp(ary_options[i].value, "yes") != 0) && !atoi(ary_options[i].value) )
|
||||||
CLEARFLAG(INOTIFYMASK);
|
CLEARFLAG(INOTIFYMASK);
|
||||||
break;
|
break;
|
||||||
|
case ENABLE_TIVO:
|
||||||
|
if( (strcmp(ary_options[i].value, "yes") == 0) || atoi(ary_options[i].value) )
|
||||||
|
SETFLAG(TIVOMASK);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
fprintf(stderr, "Unknown option in file %s\n",
|
fprintf(stderr, "Unknown option in file %s\n",
|
||||||
optionsfile);
|
optionsfile);
|
||||||
@ -589,6 +593,9 @@ main(int argc, char * * argv)
|
|||||||
struct timeval timeout, timeofday, lasttimeofday = {0, 0}, lastupdatetime = {0, 0};
|
struct timeval timeout, timeofday, lasttimeofday = {0, 0}, lastupdatetime = {0, 0};
|
||||||
int max_fd = -1;
|
int max_fd = -1;
|
||||||
int last_changecnt = 0;
|
int last_changecnt = 0;
|
||||||
|
unsigned short int loop_cnt = 0;
|
||||||
|
int sbeacon;
|
||||||
|
struct sockaddr_in tivo_bcast;
|
||||||
char * sql;
|
char * sql;
|
||||||
pthread_t thread[2];
|
pthread_t thread[2];
|
||||||
|
|
||||||
@ -662,19 +669,21 @@ main(int argc, char * * argv)
|
|||||||
"messages. EXITING\n");
|
"messages. EXITING\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef TIVO_SUPPORT
|
||||||
/* open socket for sending Tivo notifications */
|
if( GETFLAG(TIVOMASK) )
|
||||||
unsigned short int loop_cnt = 0;
|
|
||||||
int sbeacon = OpenAndConfTivoBeaconSocket();
|
|
||||||
struct sockaddr_in bcast;
|
|
||||||
if(sbeacon < 0)
|
|
||||||
{
|
{
|
||||||
DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending Tivo beacon notify "
|
DPRINTF(E_WARN, L_GENERAL, "TiVo support is enabled.\n");
|
||||||
"messages. EXITING\n");
|
/* open socket for sending Tivo notifications */
|
||||||
|
sbeacon = OpenAndConfTivoBeaconSocket();
|
||||||
|
if(sbeacon < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending Tivo beacon notify "
|
||||||
|
"messages. EXITING\n");
|
||||||
|
}
|
||||||
|
tivo_bcast.sin_family = AF_INET;
|
||||||
|
tivo_bcast.sin_addr.s_addr = htonl(getBcastAddress());
|
||||||
|
tivo_bcast.sin_port = htons( 2190 );
|
||||||
}
|
}
|
||||||
bcast.sin_family = AF_INET;
|
|
||||||
bcast.sin_addr.s_addr = htonl(getBcastAddress());
|
|
||||||
bcast.sin_port = htons( 2190 );
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SendSSDPGoodbye(snotify, n_lan_addr);
|
SendSSDPGoodbye(snotify, n_lan_addr);
|
||||||
@ -727,20 +736,23 @@ main(int argc, char * * argv)
|
|||||||
last_changecnt = sqlite3_total_changes(db);
|
last_changecnt = sqlite3_total_changes(db);
|
||||||
upnp_event_var_change_notify(EContentDirectory);
|
upnp_event_var_change_notify(EContentDirectory);
|
||||||
}
|
}
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef TIVO_SUPPORT
|
||||||
if( loop_cnt < 10 )
|
if( GETFLAG(TIVOMASK) )
|
||||||
{
|
{
|
||||||
sendBeaconMessage(sbeacon, &bcast, sizeof(struct sockaddr_in), 1);
|
if( loop_cnt < 10 )
|
||||||
loop_cnt++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if( loop_cnt == 30 )
|
|
||||||
{
|
{
|
||||||
sendBeaconMessage(sbeacon, &bcast, sizeof(struct sockaddr_in), 1);
|
sendBeaconMessage(sbeacon, &tivo_bcast, sizeof(struct sockaddr_in), 1);
|
||||||
loop_cnt = 10;
|
loop_cnt++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if( loop_cnt == 30 )
|
||||||
|
{
|
||||||
|
sendBeaconMessage(sbeacon, &tivo_bcast, sizeof(struct sockaddr_in), 1);
|
||||||
|
loop_cnt = 10;
|
||||||
|
}
|
||||||
|
loop_cnt++;
|
||||||
}
|
}
|
||||||
loop_cnt++;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
memcpy(&lastupdatetime, &timeofday, sizeof(struct timeval));
|
memcpy(&lastupdatetime, &timeofday, sizeof(struct timeval));
|
||||||
|
@ -21,6 +21,9 @@ album_art_names=Cover.jpg/cover.jpg/AlbumArtSmall.jpg/albumartsmall.jpg/AlbumArt
|
|||||||
# note: the default is yes
|
# note: the default is yes
|
||||||
inotify=yes
|
inotify=yes
|
||||||
|
|
||||||
|
# set this to yes to enable support for streaming .jpg and .mp3 files to a TiVo supporting HMO
|
||||||
|
enable_tivo=no
|
||||||
|
|
||||||
# default presentation url is http address on port 80
|
# default presentation url is http address on port 80
|
||||||
#presentation_url=http://www.mylan/index.php
|
#presentation_url=http://www.mylan/index.php
|
||||||
|
|
||||||
|
@ -33,7 +33,8 @@ static const struct {
|
|||||||
{ UPNPFRIENDLYNAME, "friendly_name"},
|
{ UPNPFRIENDLYNAME, "friendly_name"},
|
||||||
{ UPNPMEDIADIR, "media_dir"},
|
{ UPNPMEDIADIR, "media_dir"},
|
||||||
{ UPNPALBUMART_NAMES, "album_art_names"},
|
{ UPNPALBUMART_NAMES, "album_art_names"},
|
||||||
{ UPNPINOTIFY, "inotify" }
|
{ UPNPINOTIFY, "inotify" },
|
||||||
|
{ ENABLE_TIVO, "enable_tivo" }
|
||||||
};
|
};
|
||||||
|
|
||||||
int
|
int
|
||||||
|
17
options.h
17
options.h
@ -14,19 +14,20 @@
|
|||||||
enum upnpconfigoptions {
|
enum upnpconfigoptions {
|
||||||
UPNP_INVALID = 0,
|
UPNP_INVALID = 0,
|
||||||
UPNPEXT_IFNAME = 1, /* ext_ifname */
|
UPNPEXT_IFNAME = 1, /* ext_ifname */
|
||||||
UPNPEXT_IP, /* ext_ip */
|
UPNPEXT_IP, /* ext_ip */
|
||||||
UPNPLISTENING_IP, /* listening_ip */
|
UPNPLISTENING_IP, /* listening_ip */
|
||||||
UPNPPORT, /* "port" */
|
UPNPPORT, /* "port" */
|
||||||
UPNPPRESENTATIONURL, /* presentation_url */
|
UPNPPRESENTATIONURL, /* presentation_url */
|
||||||
UPNPNOTIFY_INTERVAL, /* notify_interval */
|
UPNPNOTIFY_INTERVAL, /* notify_interval */
|
||||||
UPNPSYSTEM_UPTIME, /* "system_uptime" */
|
UPNPSYSTEM_UPTIME, /* "system_uptime" */
|
||||||
UPNPUUID, /* uuid */
|
UPNPUUID, /* uuid */
|
||||||
UPNPSERIAL, /* serial */
|
UPNPSERIAL, /* serial */
|
||||||
UPNPMODEL_NUMBER, /* model_number */
|
UPNPMODEL_NUMBER, /* model_number */
|
||||||
UPNPFRIENDLYNAME, /* how the system should show up to DLNA clients */
|
UPNPFRIENDLYNAME, /* how the system should show up to DLNA clients */
|
||||||
UPNPMEDIADIR, /* directory to search for UPnP-A/V content */
|
UPNPMEDIADIR, /* directory to search for UPnP-A/V content */
|
||||||
UPNPALBUMART_NAMES, /* list of '/'-delimited file names to check for album art */
|
UPNPALBUMART_NAMES, /* list of '/'-delimited file names to check for album art */
|
||||||
UPNPINOTIFY /* enable inotify on the media directories */
|
UPNPINOTIFY, /* enable inotify on the media directories */
|
||||||
|
ENABLE_TIVO /* enable support for streaming images and music to TiVo */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* readoptionsfile()
|
/* readoptionsfile()
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
* See the file "COPYING" for more details.
|
* See the file "COPYING" for more details.
|
||||||
*/
|
*/
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef TIVO_SUPPORT
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@ -284,4 +284,4 @@ rcvBeaconMessage(char * beacon)
|
|||||||
return 1;
|
return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif // ENABLE_TIVO
|
#endif // TIVO_SUPPORT
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef TIVO_SUPPORT
|
||||||
/*
|
/*
|
||||||
* * A saved copy of a beacon from another tivo or another server
|
* * A saved copy of a beacon from another tivo or another server
|
||||||
* */
|
* */
|
||||||
|
599
tivo_commands.c
599
tivo_commands.c
@ -1,12 +1,16 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef TIVO_SUPPORT
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
#include "tivo_utils.h"
|
#include "tivo_utils.h"
|
||||||
#include "upnpglobalvars.h"
|
#include "upnpglobalvars.h"
|
||||||
#include "upnphttp.h"
|
#include "upnphttp.h"
|
||||||
|
#include "upnpsoap.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "sql.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -26,247 +30,6 @@ SendRootContainer(struct upnphttp * h)
|
|||||||
"</Details>"
|
"</Details>"
|
||||||
"<ItemStart>0</ItemStart>"
|
"<ItemStart>0</ItemStart>"
|
||||||
"<ItemCount>2</ItemCount>"
|
"<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>"
|
"<Item>"
|
||||||
"<Details>"
|
"<Details>"
|
||||||
"<ContentType>x-container/tivo-photos</ContentType>"
|
"<ContentType>x-container/tivo-photos</ContentType>"
|
||||||
@ -291,15 +54,334 @@ SendContainer(struct upnphttp * h, const char * objectID, const char * title, in
|
|||||||
"</Content>"
|
"</Content>"
|
||||||
"</Links>"
|
"</Links>"
|
||||||
"</Item>"
|
"</Item>"
|
||||||
"</TiVoContainer>",
|
"</TiVoContainer>", friendly_name, friendly_name, friendly_name);
|
||||||
(objectID[0]=='1') ? "music":"photos",
|
|
||||||
title, itemStart, itemCount, title, title);
|
|
||||||
BuildResp_upnphttp(h, resp, len);
|
BuildResp_upnphttp(h, resp, len);
|
||||||
SendResp_upnphttp(h);
|
SendResp_upnphttp(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int callback(void *args, int argc, char **argv, char **azColName)
|
||||||
|
{
|
||||||
|
struct Response *passed_args = (struct Response *)args;
|
||||||
|
char *id = argv[1], *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], *date = argv[20], *resolution = argv[21], *mime = argv[25];
|
||||||
|
char str_buf[4096];
|
||||||
|
char **result;
|
||||||
|
int is_audio = 0;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
//passed_args->total++;
|
||||||
|
//if( (passed_args->requested > -100) && (passed_args->returned >= passed_args->requested) )
|
||||||
|
// return 0;
|
||||||
|
|
||||||
|
if( strncmp(class, "item", 4) == 0 )
|
||||||
|
{
|
||||||
|
if( strcmp(mime, "audio/mpeg") == 0 )
|
||||||
|
{
|
||||||
|
if( passed_args->start > passed_args->total )
|
||||||
|
return 0;
|
||||||
|
passed_args->total++;
|
||||||
|
if( (passed_args->requested > -100) && (passed_args->returned >= passed_args->requested) )
|
||||||
|
return 0;
|
||||||
|
passed_args->returned++;
|
||||||
|
sprintf(str_buf, "<Item><Details>"
|
||||||
|
"<ContentType>audio/*</ContentType>"
|
||||||
|
"<SourceFormat>audio/mpeg</SourceFormat>"
|
||||||
|
"<SourceSize>%s</SourceSize>"
|
||||||
|
"<SongTitle>%s</SongTitle>", size, title);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
if( date )
|
||||||
|
{
|
||||||
|
sprintf(str_buf, "<AlbumYear>%.*s</AlbumYear>", 4, date);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
is_audio = 1;
|
||||||
|
}
|
||||||
|
else if( strcmp(mime, "image/jpeg") == 0 )
|
||||||
|
{
|
||||||
|
if( passed_args->start > passed_args->total )
|
||||||
|
return 0;
|
||||||
|
passed_args->total++;
|
||||||
|
if( (passed_args->requested > -100) && (passed_args->returned >= passed_args->requested) )
|
||||||
|
return 0;
|
||||||
|
passed_args->returned++;
|
||||||
|
sprintf(str_buf, "<Item><Details>"
|
||||||
|
"<ContentType>image/*</ContentType>"
|
||||||
|
"<SourceFormat>image/jpeg</SourceFormat>"
|
||||||
|
"<SourceSize>%s</SourceSize>", size);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
if( date )
|
||||||
|
{
|
||||||
|
struct tm tm;
|
||||||
|
memset(&tm, 0, sizeof(tm));
|
||||||
|
strptime(date, "%Y-%m-%dT%H:%M:%S", &tm);
|
||||||
|
sprintf(str_buf, "<CaptureDate>0x%X</CaptureDate>", (unsigned int)mktime(&tm));
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( comment ) {
|
||||||
|
sprintf(str_buf, "<Caption>%s</Caption>", comment);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
sprintf(str_buf, "<Title>%s</Title>", title);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
if( artist ) {
|
||||||
|
sprintf(str_buf, "<ArtistName>%s</ArtistName>", artist);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( album ) {
|
||||||
|
sprintf(str_buf, "<AlbumTitle>%s</AlbumTitle>", album);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( genre ) {
|
||||||
|
sprintf(str_buf, "<MusicGenre>%s</MusicGenre>", genre);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( resolution ) {
|
||||||
|
sprintf(str_buf, "<SourceWidth>%.*s</SourceWidth>"
|
||||||
|
"<SourceHeight>%s</SourceHeight>",
|
||||||
|
(index(resolution, 'x')-resolution), resolution, (rindex(resolution, 'x')+1));
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( duration ) {
|
||||||
|
sprintf(str_buf, "<Duration>%d</Duration>",
|
||||||
|
atoi(rindex(duration, '.')+1) + (1000*atoi(rindex(duration, ':')+1)) + (60000*atoi(rindex(duration, ':')-2)) + (3600000*atoi(duration)));
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( bitrate ) {
|
||||||
|
sprintf(str_buf, "<SourceBitRate>%s</SourceBitRate>", bitrate);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( sampleFrequency ) {
|
||||||
|
sprintf(str_buf, "<SourceSampleRate>%s</SourceSampleRate>", sampleFrequency);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
sprintf(str_buf, "</Details><Links><Content><Url>/%s/%s.dat</Url>%s</Content></Links>",
|
||||||
|
is_audio?"MediaItems":"Resized", detailID, is_audio?"<AcceptsParams>No</AcceptsParams>":"");
|
||||||
|
}
|
||||||
|
else if( strncmp(class, "container", 9) == 0 )
|
||||||
|
{
|
||||||
|
if( passed_args->start > passed_args->total )
|
||||||
|
return 0;
|
||||||
|
passed_args->total++;
|
||||||
|
if( (passed_args->requested > -100) && (passed_args->returned >= passed_args->requested) )
|
||||||
|
return 0;
|
||||||
|
passed_args->returned++;
|
||||||
|
/* Determine the number of children */
|
||||||
|
sprintf(str_buf, "SELECT count(ID) from OBJECTS where PARENT_ID = '%s';", id);
|
||||||
|
ret = sql_get_table(db, str_buf, &result, NULL, NULL);
|
||||||
|
strcat(passed_args->resp, "<Item><Details>"
|
||||||
|
"<ContentType>x-container/folder</ContentType>"
|
||||||
|
"<SourceFormat>x-container/folder</SourceFormat>");
|
||||||
|
sprintf(str_buf, "<Title>%s</Title>"
|
||||||
|
"<TotalItems>%s</TotalItems>"
|
||||||
|
"</Details>", title, result[1]);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
|
||||||
|
sprintf(str_buf, "<Links><Content>"
|
||||||
|
"<Url>/TiVoConnect?Command=QueryContainer&Container=%s</Url>"
|
||||||
|
"</Content></Links>", id);
|
||||||
|
sqlite3_free_table(result);
|
||||||
|
}
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
strcat(passed_args->resp, "</Item>");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int itemCount, char * anchorItem, int anchorOffset)
|
||||||
|
{
|
||||||
|
char * resp = malloc(1048576);
|
||||||
|
char * items = malloc(1048576);
|
||||||
|
char *sql;
|
||||||
|
char *zErrMsg = NULL;
|
||||||
|
char **result;
|
||||||
|
char *title;
|
||||||
|
char what[10];
|
||||||
|
struct Response args;
|
||||||
|
int i, ret;
|
||||||
|
*items = '\0';
|
||||||
|
memset(&args, 0, sizeof(args));
|
||||||
|
|
||||||
|
args.total = itemStart;
|
||||||
|
args.resp = items;
|
||||||
|
args.requested = itemCount;
|
||||||
|
|
||||||
|
if( strlen(objectID) == 1 )
|
||||||
|
{
|
||||||
|
if( *objectID == '1' )
|
||||||
|
{
|
||||||
|
asprintf(&title, "Music on %s", friendly_name);
|
||||||
|
}
|
||||||
|
else if( *objectID == '3' )
|
||||||
|
{
|
||||||
|
asprintf(&title, "Pictures on %s", friendly_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sql = sqlite3_mprintf("SELECT NAME from OBJECTS where OBJECT_ID = '%s'", objectID);
|
||||||
|
if( (sql_get_table(db, sql, &result, &ret, NULL) == SQLITE_OK) && ret )
|
||||||
|
{
|
||||||
|
title = strdup(result[1]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
title = strdup("UNKNOWN");
|
||||||
|
}
|
||||||
|
sqlite3_free(sql);
|
||||||
|
sqlite3_free_table(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if( anchorItem )
|
||||||
|
{
|
||||||
|
if( strstr(anchorItem, "QueryContainer") )
|
||||||
|
{
|
||||||
|
strcpy(what, "OBJECT_ID");
|
||||||
|
anchorItem = rindex(anchorItem, '=')+1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
strcpy(what, "DETAIL_ID");
|
||||||
|
}
|
||||||
|
sql = sqlite3_mprintf("SELECT %s from OBJECTS where PARENT_ID = '%s'"
|
||||||
|
" order by CLASS, NAME %s", what, objectID, itemCount<0?"DESC":"ASC");
|
||||||
|
if( itemCount < 0 )
|
||||||
|
args.requested *= -1;
|
||||||
|
DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql);
|
||||||
|
if( (sql_get_table(db, sql, &result, &ret, NULL) == SQLITE_OK) && ret )
|
||||||
|
{
|
||||||
|
for( i=1; i<=ret; i++ )
|
||||||
|
{
|
||||||
|
if( strcmp(anchorItem, result[i]) == 0 )
|
||||||
|
{
|
||||||
|
if( itemCount < 0 )
|
||||||
|
itemStart = ret - i + itemCount;
|
||||||
|
else
|
||||||
|
itemStart += i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_free_table(result);
|
||||||
|
}
|
||||||
|
sqlite3_free(sql);
|
||||||
|
}
|
||||||
|
args.start = itemStart+anchorOffset;
|
||||||
|
sql = sqlite3_mprintf("SELECT * from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
|
||||||
|
" where PARENT_ID = '%s' order by CLASS, NAME", objectID);
|
||||||
|
ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
|
||||||
|
sqlite3_free(sql);
|
||||||
|
if( ret != SQLITE_OK )
|
||||||
|
{
|
||||||
|
DPRINTF(E_ERROR, L_HTTP, "SQL error: %s\n", zErrMsg);
|
||||||
|
sqlite3_free(zErrMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(resp, "<?xml version='1.0' encoding='UTF-8' ?>\n"
|
||||||
|
"<TiVoContainer>"
|
||||||
|
"<Details>"
|
||||||
|
"<ContentType>x-container/tivo-%s</ContentType>"
|
||||||
|
"<SourceFormat>x-container/folder</SourceFormat>"
|
||||||
|
"<TotalItems>%d</TotalItems>"
|
||||||
|
"<Title>%s</Title>"
|
||||||
|
"</Details>"
|
||||||
|
"<ItemStart>%d</ItemStart>"
|
||||||
|
"<ItemCount>%d</ItemCount>"
|
||||||
|
"%s" /* the actual items xml */
|
||||||
|
"</TiVoContainer>",
|
||||||
|
(objectID[0]=='1' ? "music":"photos"),
|
||||||
|
args.total+itemStart+anchorOffset,
|
||||||
|
title, itemStart+anchorOffset,
|
||||||
|
args.returned, items);
|
||||||
|
free(title);
|
||||||
|
free(items);
|
||||||
|
BuildResp_upnphttp(h, resp, strlen(resp));
|
||||||
|
free(resp);
|
||||||
|
SendResp_upnphttp(h);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ProcessTiVoCommand(struct upnphttp * h, const char * orig_path)
|
ProcessTiVoCommand(struct upnphttp * h, const char * orig_path)
|
||||||
|
{
|
||||||
|
char *path;
|
||||||
|
char *key, *val;
|
||||||
|
char *saveptr, *item;
|
||||||
|
char *command = NULL, *container = NULL, *anchorItem = NULL;
|
||||||
|
int itemStart=0, itemCount=-100, anchorOffset=0;
|
||||||
|
|
||||||
|
path = strdup(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;
|
||||||
|
}
|
||||||
|
decodeString(item, 1);
|
||||||
|
val = item;
|
||||||
|
key = strsep(&val, "=");
|
||||||
|
decodeString(val, 1);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
else if( strcasecmp(key, "AnchorItem") == 0 )
|
||||||
|
{
|
||||||
|
anchorItem = basename(val);
|
||||||
|
}
|
||||||
|
else if( strcasecmp(key, "AnchorOffset") == 0 )
|
||||||
|
{
|
||||||
|
anchorOffset = atoi(val);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DPRINTF(E_DEBUG, L_GENERAL, "Unhandled parameter [%s]\n", key);
|
||||||
|
}
|
||||||
|
item = strtok_r( NULL, "&", &saveptr );
|
||||||
|
}
|
||||||
|
if( anchorItem )
|
||||||
|
{
|
||||||
|
strip_ext(anchorItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if( command && strcmp(command, "QueryContainer") == 0 )
|
||||||
|
{
|
||||||
|
if( !container || (strcmp(container, "/") == 0) )
|
||||||
|
{
|
||||||
|
SendRootContainer(h);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendContainer(h, container, itemStart, itemCount, anchorItem, anchorOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(path);
|
||||||
|
CloseSocket_upnphttp(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ProcessTiVoRequest(struct upnphttp * h, const char * orig_path)
|
||||||
{
|
{
|
||||||
char *path;
|
char *path;
|
||||||
char *key, *val;
|
char *key, *val;
|
||||||
@ -307,8 +389,8 @@ ProcessTiVoCommand(struct upnphttp * h, const char * orig_path)
|
|||||||
char *command = NULL, *container = NULL;
|
char *command = NULL, *container = NULL;
|
||||||
int itemStart=0, itemCount=0;
|
int itemStart=0, itemCount=0;
|
||||||
|
|
||||||
path = decodeString(orig_path);
|
path = decodeString(orig_path, 0);
|
||||||
DPRINTF(E_DEBUG, L_GENERAL, "Processing TiVo command %s\n", path);
|
DPRINTF(E_DEBUG, L_GENERAL, "Processing TiVo request %s\n", path);
|
||||||
|
|
||||||
item = strtok_r( path, "&", &saveptr );
|
item = strtok_r( path, "&", &saveptr );
|
||||||
while( item != NULL )
|
while( item != NULL )
|
||||||
@ -321,38 +403,25 @@ ProcessTiVoCommand(struct upnphttp * h, const char * orig_path)
|
|||||||
val = item;
|
val = item;
|
||||||
key = strsep(&val, "=");
|
key = strsep(&val, "=");
|
||||||
DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val);
|
DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val);
|
||||||
if( strcasecmp(key, "command") == 0 )
|
if( strcasecmp(key, "width") == 0 )
|
||||||
{
|
{
|
||||||
command = val;
|
command = val;
|
||||||
}
|
}
|
||||||
else if( strcasecmp(key, "container") == 0 )
|
else if( strcasecmp(key, "height") == 0 )
|
||||||
{
|
{
|
||||||
container = val;
|
container = val;
|
||||||
}
|
}
|
||||||
else if( strcasecmp(key, "itemstart") == 0 )
|
else if( strcasecmp(key, "rotation") == 0 )
|
||||||
{
|
{
|
||||||
itemStart = atoi(val);
|
itemStart = atoi(val);
|
||||||
}
|
}
|
||||||
else if( strcasecmp(key, "itemcount") == 0 )
|
else if( strcasecmp(key, "pixelshape") == 0 )
|
||||||
{
|
{
|
||||||
itemCount = atoi(val);
|
itemCount = atoi(val);
|
||||||
}
|
}
|
||||||
item = strtok_r( NULL, "&", &saveptr );
|
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);
|
CloseSocket_upnphttp(h);
|
||||||
}
|
}
|
||||||
#endif // ENABLE_TIVO
|
#endif // TIVO_SUPPORT
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef TIVO_SUPPORT
|
||||||
void
|
void
|
||||||
ProcessTiVoCommand(struct upnphttp * h, const char * orig_path);
|
ProcessTiVoCommand(struct upnphttp * h, const char * orig_path);
|
||||||
#endif
|
#endif
|
||||||
|
43
tivo_utils.c
43
tivo_utils.c
@ -1,29 +1,31 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef TIVO_SUPPORT
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
|
||||||
/* This function stolen from byRequest */
|
/* This function based on byRequest */
|
||||||
char *
|
char *
|
||||||
decodeString(const char * string)
|
decodeString(char * string, int inplace)
|
||||||
{
|
{
|
||||||
if( !string )
|
if( !string )
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
int alloc = (int)strlen(string)+1;
|
int alloc = (int)strlen(string)+1;
|
||||||
char *ns = malloc(alloc);
|
char *ns;
|
||||||
unsigned char in;
|
unsigned char in;
|
||||||
int strindex=0;
|
int strindex=0;
|
||||||
long hex;
|
long hex;
|
||||||
|
|
||||||
if( !ns )
|
if( !inplace )
|
||||||
return NULL;
|
{
|
||||||
|
if( !(ns = malloc(alloc)) )
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
while(--alloc > 0)
|
while(--alloc > 0)
|
||||||
{
|
{
|
||||||
in = *string;
|
in = *string;
|
||||||
if(('%' == in) && isxdigit(string[1]) && isxdigit(string[2]))
|
if((in == '%') && isxdigit(string[1]) && isxdigit(string[2]))
|
||||||
{
|
{
|
||||||
/* this is two hexadecimal digits following a '%' */
|
/* this is two hexadecimal digits following a '%' */
|
||||||
char hexstr[3];
|
char hexstr[3];
|
||||||
@ -35,14 +37,29 @@ decodeString(const char * string)
|
|||||||
hex = strtol(hexstr, &ptr, 16);
|
hex = strtol(hexstr, &ptr, 16);
|
||||||
|
|
||||||
in = (unsigned char)hex; /* this long is never bigger than 255 anyway */
|
in = (unsigned char)hex; /* this long is never bigger than 255 anyway */
|
||||||
string+=2;
|
if( inplace )
|
||||||
|
{
|
||||||
|
*string = in;
|
||||||
|
memmove(string+1, string+3, alloc-2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string+=2;
|
||||||
|
}
|
||||||
alloc-=2;
|
alloc-=2;
|
||||||
}
|
}
|
||||||
|
if( !inplace )
|
||||||
ns[strindex++] = in;
|
ns[strindex++] = in;
|
||||||
string++;
|
string++;
|
||||||
}
|
}
|
||||||
ns[strindex]=0; /* terminate it */
|
if( inplace )
|
||||||
return ns;
|
{
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ns[strindex] = '\0'; /* terminate it */
|
||||||
|
return ns;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef TIVO_SUPPORT
|
||||||
char *
|
char *
|
||||||
decodeString(const char * string);
|
decodeString(const char * string, int inplace);
|
||||||
#endif
|
#endif
|
||||||
|
@ -59,6 +59,7 @@ extern struct runtime_vars_s runtime_vars;
|
|||||||
extern int runtime_flags;
|
extern int runtime_flags;
|
||||||
#define INOTIFYMASK 0x0001
|
#define INOTIFYMASK 0x0001
|
||||||
#define SYSUPTIMEMASK 0x0002
|
#define SYSUPTIMEMASK 0x0002
|
||||||
|
#define TIVOMASK 0x0004
|
||||||
|
|
||||||
#define SETFLAG(mask) runtime_flags |= mask
|
#define SETFLAG(mask) runtime_flags |= mask
|
||||||
#define GETFLAG(mask) runtime_flags & mask
|
#define GETFLAG(mask) runtime_flags & mask
|
||||||
|
253
upnphttp.c
253
upnphttp.c
@ -34,12 +34,10 @@
|
|||||||
#include "upnpglobalvars.h"
|
#include "upnpglobalvars.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include <sqlite3.h>
|
#include "sql.h"
|
||||||
#include <libexif/exif-loader.h>
|
#include <libexif/exif-loader.h>
|
||||||
#if 0 //JPEG_RESIZE
|
|
||||||
#include <gd.h>
|
#include <gd.h>
|
||||||
#endif
|
#ifdef TIVO_SUPPORT
|
||||||
#ifdef ENABLE_TIVO
|
|
||||||
#include "tivo_utils.h"
|
#include "tivo_utils.h"
|
||||||
#include "tivo_commands.h"
|
#include "tivo_commands.h"
|
||||||
#endif
|
#endif
|
||||||
@ -480,7 +478,7 @@ static void
|
|||||||
ProcessHttpQuery_upnphttp(struct upnphttp * h)
|
ProcessHttpQuery_upnphttp(struct upnphttp * h)
|
||||||
{
|
{
|
||||||
char HttpCommand[16];
|
char HttpCommand[16];
|
||||||
char HttpUrl[256];
|
char HttpUrl[512];
|
||||||
char * HttpVer;
|
char * HttpVer;
|
||||||
char * p;
|
char * p;
|
||||||
int i;
|
int i;
|
||||||
@ -498,7 +496,7 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h)
|
|||||||
while(*p!='/')
|
while(*p!='/')
|
||||||
p++;
|
p++;
|
||||||
}
|
}
|
||||||
for(i = 0; i<255 && *p != ' ' && *p != '\r'; i++)
|
for(i = 0; i<511 && *p != ' ' && *p != '\r'; i++)
|
||||||
HttpUrl[i] = *(p++);
|
HttpUrl[i] = *(p++);
|
||||||
HttpUrl[i] = '\0';
|
HttpUrl[i] = '\0';
|
||||||
while(*p==' ')
|
while(*p==' ')
|
||||||
@ -507,9 +505,9 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h)
|
|||||||
for(i = 0; i<15 && *p != '\r'; i++)
|
for(i = 0; i<15 && *p != '\r'; i++)
|
||||||
HttpVer[i] = *(p++);
|
HttpVer[i] = *(p++);
|
||||||
HttpVer[i] = '\0';
|
HttpVer[i] = '\0';
|
||||||
DPRINTF(E_INFO, L_HTTP, "HTTP REQUEST : %s %s (%s)\n",
|
/*DPRINTF(E_INFO, L_HTTP, "HTTP REQUEST : %s %s (%s)\n",
|
||||||
HttpCommand, HttpUrl, HttpVer);
|
HttpCommand, HttpUrl, HttpVer);*/
|
||||||
DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST:\n%.*s\n", h->req_buflen, h->req_buf);
|
DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf);
|
||||||
ParseHttpHeaders(h);
|
ParseHttpHeaders(h);
|
||||||
|
|
||||||
/* see if we need to wait for remaining data */
|
/* see if we need to wait for remaining data */
|
||||||
@ -589,32 +587,33 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h)
|
|||||||
SendResp_albumArt(h, HttpUrl+10);
|
SendResp_albumArt(h, HttpUrl+10);
|
||||||
CloseSocket_upnphttp(h);
|
CloseSocket_upnphttp(h);
|
||||||
}
|
}
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef TIVO_SUPPORT
|
||||||
else if(strncmp(HttpUrl, "/TiVoConnect", 12) == 0)
|
else if(strncmp(HttpUrl, "/TiVoConnect", 12) == 0)
|
||||||
{
|
{
|
||||||
if( *(HttpUrl+12) == '?' )
|
if( GETFLAG(TIVOMASK) )
|
||||||
{
|
{
|
||||||
ProcessTiVoCommand(h, HttpUrl+13);
|
if( *(HttpUrl+12) == '?' )
|
||||||
}
|
{
|
||||||
else if( *(HttpUrl+12) == '/' )
|
ProcessTiVoCommand(h, HttpUrl+13);
|
||||||
{
|
}
|
||||||
printf("TiVo request: %c\n", *(HttpUrl+12));
|
else
|
||||||
Send404(h);
|
{
|
||||||
|
printf("Invalid TiVo request! %s\n", HttpUrl+12);
|
||||||
|
Send404(h);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
printf("Invalid TiVo request! %s\n", HttpUrl+12);
|
printf("TiVo request with out TiVo support enabled! %s\n", HttpUrl+12);
|
||||||
Send404(h);
|
Send404(h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if 0 //JPEG_RESIZE
|
|
||||||
else if(strncmp(HttpUrl, "/Resized/", 9) == 0)
|
else if(strncmp(HttpUrl, "/Resized/", 9) == 0)
|
||||||
{
|
{
|
||||||
SendResp_resizedimg(h, HttpUrl+9);
|
SendResp_resizedimg(h, HttpUrl+9);
|
||||||
CloseSocket_upnphttp(h);
|
CloseSocket_upnphttp(h);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
else if(strncmp(HttpUrl, "/icons/", 7) == 0)
|
else if(strncmp(HttpUrl, "/icons/", 7) == 0)
|
||||||
{
|
{
|
||||||
SendResp_icon(h, HttpUrl+7);
|
SendResp_icon(h, HttpUrl+7);
|
||||||
@ -829,7 +828,7 @@ void
|
|||||||
SendResp_upnphttp(struct upnphttp * h)
|
SendResp_upnphttp(struct upnphttp * h)
|
||||||
{
|
{
|
||||||
int n;
|
int n;
|
||||||
DPRINTF(E_DEBUG, L_HTTP, "HTTP RESPONSE:\n%.*s\n", h->res_buflen, h->res_buf);
|
DPRINTF(E_DEBUG, L_HTTP, "HTTP RESPONSE: %.*s\n", h->res_buflen, h->res_buf);
|
||||||
n = send(h->socket, h->res_buf, h->res_buflen, 0);
|
n = send(h->socket, h->res_buf, h->res_buflen, 0);
|
||||||
if(n<0)
|
if(n<0)
|
||||||
{
|
{
|
||||||
@ -1106,7 +1105,6 @@ SendResp_thumbnail(struct upnphttp * h, char * object)
|
|||||||
sqlite3_free_table(result);
|
sqlite3_free_table(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0 //JPEG_RESIZE
|
|
||||||
void
|
void
|
||||||
SendResp_resizedimg(struct upnphttp * h, char * object)
|
SendResp_resizedimg(struct upnphttp * h, char * object)
|
||||||
{
|
{
|
||||||
@ -1115,14 +1113,54 @@ SendResp_resizedimg(struct upnphttp * h, char * object)
|
|||||||
char **result;
|
char **result;
|
||||||
char date[30];
|
char date[30];
|
||||||
time_t curtime = time(NULL);
|
time_t curtime = time(NULL);
|
||||||
int n;
|
|
||||||
FILE *imgfile;
|
FILE *imgfile;
|
||||||
gdImagePtr imsrc = 0, imdst = 0;
|
int width=640, height=480, dstw, dsth, rotation, size;
|
||||||
int dstw, dsth, srcw, srch, size;
|
|
||||||
char * data;
|
char * data;
|
||||||
|
char *path, *file_path;
|
||||||
|
char *resolution, *tn;
|
||||||
|
char *key, *val;
|
||||||
|
char *saveptr, *item;
|
||||||
|
char *pixelshape = NULL;
|
||||||
|
int rows=0, ret;
|
||||||
|
gdImagePtr imsrc = 0, imdst = 0;
|
||||||
|
ExifData *ed;
|
||||||
|
ExifLoader *l;
|
||||||
memset(header, 0, 1500);
|
memset(header, 0, 1500);
|
||||||
|
|
||||||
|
path = strdup(object);
|
||||||
|
if( strtok_r(path, "?", &saveptr) )
|
||||||
|
{
|
||||||
|
item = strtok_r(NULL, "&", &saveptr);
|
||||||
|
//continue;
|
||||||
|
}
|
||||||
|
while( item != NULL )
|
||||||
|
{
|
||||||
|
#ifdef TIVO_SUPPORT
|
||||||
|
decodeString(item, 1);
|
||||||
|
#endif
|
||||||
|
val = item;
|
||||||
|
key = strsep(&val, "=");
|
||||||
|
DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val);
|
||||||
|
if( strcasecmp(key, "width") == 0 )
|
||||||
|
{
|
||||||
|
width = atoi(val);
|
||||||
|
}
|
||||||
|
else if( strcasecmp(key, "height") == 0 )
|
||||||
|
{
|
||||||
|
height = atoi(val);
|
||||||
|
}
|
||||||
|
else if( strcasecmp(key, "rotation") == 0 )
|
||||||
|
{
|
||||||
|
rotation = atoi(val);
|
||||||
|
}
|
||||||
|
else if( strcasecmp(key, "pixelshape") == 0 )
|
||||||
|
{
|
||||||
|
pixelshape = val;
|
||||||
|
}
|
||||||
|
item = strtok_r(NULL, "&", &saveptr);
|
||||||
|
}
|
||||||
|
strip_ext(path);
|
||||||
|
|
||||||
if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE )
|
if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE )
|
||||||
{
|
{
|
||||||
DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with a resized image!\n");
|
DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with a resized image!\n");
|
||||||
@ -1130,90 +1168,107 @@ SendResp_resizedimg(struct upnphttp * h, char * object)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sprintf(sql_buf, "SELECT d.PATH, d.WIDTH, d.HEIGHT from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object);
|
sprintf(sql_buf, "SELECT PATH, RESOLUTION, THUMBNAIL from DETAILS where ID = '%s'", path);
|
||||||
sqlite3_get_table(db, sql_buf, &result, 0, 0, 0);
|
ret = sql_get_table(db, sql_buf, &result, &rows, NULL);
|
||||||
DPRINTF(E_INFO, L_HTTP, "Serving up resized image for ObjectId: %s [%s]\n", object, result[1]);
|
if( (ret != SQLITE_OK) || !rows || (access(result[3], F_OK) != 0) )
|
||||||
|
|
||||||
if( access(result[3], F_OK) == 0 )
|
|
||||||
{
|
{
|
||||||
strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
|
free(path);
|
||||||
|
DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %s!\n", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
file_path = result[3];
|
||||||
|
resolution = result[4];
|
||||||
|
tn = result[5];
|
||||||
|
DPRINTF(E_INFO, L_HTTP, "Serving resized image for ObjectId: %s [%s]\n", path, file_path);
|
||||||
|
free(path);
|
||||||
|
|
||||||
imgfile = fopen(result[3], "r");
|
/* Resizing from a thumbnail is much faster than from a large image */
|
||||||
imsrc = gdImageCreateFromJpeg(imgfile);
|
if( width <= 160 && height <= 120 && atoi(tn) )
|
||||||
imdst = gdImageCreateTrueColor(dstw, dsth);
|
{
|
||||||
srcw = atoi(result[4]);
|
l = exif_loader_new();
|
||||||
srch = atoi(result[5]);
|
exif_loader_write_file(l, file_path);
|
||||||
dstw = 640;
|
ed = exif_loader_get_data(l);
|
||||||
dsth = ((((640<<10)/srcw)*srch)>>10);
|
exif_loader_unref(l);
|
||||||
|
|
||||||
if( !imsrc )
|
if( !ed || !ed->size )
|
||||||
|
{
|
||||||
|
if( ed )
|
||||||
|
exif_data_unref(ed);
|
||||||
|
Send404(h);
|
||||||
|
sqlite3_free_table(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
imsrc = gdImageCreateFromJpegPtr(ed->size, ed->data);
|
||||||
|
exif_data_unref(ed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
imgfile = fopen(file_path, "r");
|
||||||
|
if( !imgfile )
|
||||||
{
|
{
|
||||||
Send404(h);
|
Send404(h);
|
||||||
goto error;
|
sqlite3_free_table(result);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if( dsth > 480 )
|
imsrc = gdImageCreateFromJpeg(imgfile);
|
||||||
{
|
fclose(imgfile);
|
||||||
dsth = 480;
|
|
||||||
dstw = (((480<<10)/srch) * srcw>>10);
|
|
||||||
}
|
|
||||||
gdImageCopyResized(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, srcw, srch);
|
|
||||||
data = (char *)gdImageJpegPtr(imdst, &size, -1);
|
|
||||||
sprintf(header, "%s 200 OK\r\n"
|
|
||||||
"Content-Type: image/jpeg\r\n"
|
|
||||||
"Content-Length: %d\r\n"
|
|
||||||
"Connection: close\r\n"
|
|
||||||
"Date: %s\r\n"
|
|
||||||
"EXT:\r\n"
|
|
||||||
"contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n"
|
|
||||||
"Server: RAIDiator/4.1, UPnP/1.0, MiniDLNA_TN/1.0\r\n",
|
|
||||||
h->HttpVer, size, date);
|
|
||||||
|
|
||||||
if( h->reqflags & FLAG_XFERINTERACTIVE )
|
|
||||||
{
|
|
||||||
strcat(header, "transferMode.dlna.org: Interactive\r\n");
|
|
||||||
}
|
|
||||||
else if( h->reqflags & FLAG_XFERBACKGROUND )
|
|
||||||
{
|
|
||||||
strcat(header, "transferMode.dlna.org: Background\r\n");
|
|
||||||
}
|
|
||||||
strcat(header, "\r\n");
|
|
||||||
|
|
||||||
n = send(h->socket, header, strlen(header), 0);
|
|
||||||
if(n<0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s", strerror(errno));
|
|
||||||
}
|
|
||||||
else if(n < h->res_buflen)
|
|
||||||
{
|
|
||||||
/* TODO : handle correctly this case */
|
|
||||||
DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n",
|
|
||||||
n, h->res_buflen);
|
|
||||||
}
|
|
||||||
|
|
||||||
if( h->req_command == EHead )
|
|
||||||
{
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
n = send(h->socket, data, size, 0);
|
|
||||||
if(n<0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s", strerror(errno));
|
|
||||||
}
|
|
||||||
else if(n < h->res_buflen)
|
|
||||||
{
|
|
||||||
/* TODO : handle correctly this case */
|
|
||||||
DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n",
|
|
||||||
n, h->res_buflen);
|
|
||||||
}
|
|
||||||
gdFree(data);
|
|
||||||
}
|
}
|
||||||
error:
|
if( !imsrc )
|
||||||
|
{
|
||||||
|
Send404(h);
|
||||||
|
sqlite3_free_table(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* Figure out the best destination resolution we can use */
|
||||||
|
dstw = width;
|
||||||
|
dsth = ((((width<<10)/imsrc->sx)*imsrc->sy)>>10);
|
||||||
|
if( dsth > height )
|
||||||
|
{
|
||||||
|
dsth = height;
|
||||||
|
dstw = (((height<<10)/imsrc->sy) * imsrc->sx>>10);
|
||||||
|
}
|
||||||
|
imdst = gdImageCreateTrueColor(dstw, dsth);
|
||||||
|
#if 0 // Use box filter resizer
|
||||||
|
#ifdef __sparc__
|
||||||
|
gdImageCopyResized(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy);
|
||||||
|
#else
|
||||||
|
gdImageCopyResampled(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
boxfilter_resize(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy);
|
||||||
|
data = (char *)gdImageJpegPtr(imdst, &size, 99);
|
||||||
|
DPRINTF(E_INFO, L_HTTP, "size: %d\n", size);
|
||||||
|
strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
|
||||||
|
snprintf(header, sizeof(header)-50, "%s 200 OK\r\n"
|
||||||
|
"Content-Type: image/jpeg\r\n"
|
||||||
|
"Content-Length: %d\r\n"
|
||||||
|
"Connection: close\r\n"
|
||||||
|
"Date: %s\r\n"
|
||||||
|
"EXT:\r\n"
|
||||||
|
"contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n"
|
||||||
|
"Server: RAIDiator/4.1, UPnP/1.0, MiniDLNA_TN/1.0\r\n",
|
||||||
|
h->HttpVer, size, date);
|
||||||
|
|
||||||
|
if( h->reqflags & FLAG_XFERINTERACTIVE )
|
||||||
|
{
|
||||||
|
strcat(header, "transferMode.dlna.org: Interactive\r\n");
|
||||||
|
}
|
||||||
|
else if( h->reqflags & FLAG_XFERBACKGROUND )
|
||||||
|
{
|
||||||
|
strcat(header, "transferMode.dlna.org: Background\r\n");
|
||||||
|
}
|
||||||
|
strcat(header, "\r\n");
|
||||||
|
|
||||||
|
if( (send_data(h, header, strlen(header)) == 0) && (h->req_command != EHead) )
|
||||||
|
{
|
||||||
|
send_data(h, data, size);
|
||||||
|
}
|
||||||
|
gdFree(data);
|
||||||
gdImageDestroy(imsrc);
|
gdImageDestroy(imsrc);
|
||||||
gdImageDestroy(imdst);
|
gdImageDestroy(imdst);
|
||||||
|
DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path);
|
||||||
sqlite3_free_table(result);
|
sqlite3_free_table(result);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
void
|
void
|
||||||
SendResp_dlnafile(struct upnphttp * h, char * object)
|
SendResp_dlnafile(struct upnphttp * h, char * object)
|
||||||
|
@ -408,14 +408,15 @@ BrowseContentDirectory(struct upnphttp * h, const char * action)
|
|||||||
static const char resp1[] = "</DIDL-Lite></Result>";
|
static const char resp1[] = "</DIDL-Lite></Result>";
|
||||||
static const char resp2[] = "</u:BrowseResponse>";
|
static const char resp2[] = "</u:BrowseResponse>";
|
||||||
|
|
||||||
char *resp = calloc(1, 1048576);
|
char *resp = malloc(1048576);
|
||||||
char str_buf[4096];
|
char str_buf[4096];
|
||||||
char *zErrMsg = 0;
|
char *zErrMsg = 0;
|
||||||
char *sql;
|
char *sql;
|
||||||
int ret;
|
int ret;
|
||||||
struct Response args;
|
struct Response args;
|
||||||
|
|
||||||
struct NameValueParserData data;
|
struct NameValueParserData data;
|
||||||
|
*resp = '\0';
|
||||||
|
|
||||||
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
|
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
|
||||||
int RequestedCount = atoi( GetValueFromNameValueList(&data, "RequestedCount") );
|
int RequestedCount = atoi( GetValueFromNameValueList(&data, "RequestedCount") );
|
||||||
int StartingIndex = atoi( GetValueFromNameValueList(&data, "StartingIndex") );
|
int StartingIndex = atoi( GetValueFromNameValueList(&data, "StartingIndex") );
|
||||||
@ -510,12 +511,13 @@ SearchContentDirectory(struct upnphttp * h, const char * action)
|
|||||||
static const char resp1[] = "</DIDL-Lite></Result>";
|
static const char resp1[] = "</DIDL-Lite></Result>";
|
||||||
static const char resp2[] = "</u:SearchResponse>";
|
static const char resp2[] = "</u:SearchResponse>";
|
||||||
|
|
||||||
char *resp = calloc(1, 1048576);
|
char *resp = malloc(1048576);
|
||||||
char *zErrMsg = 0;
|
char *zErrMsg = 0;
|
||||||
char *sql;
|
char *sql;
|
||||||
char str_buf[4096];
|
char str_buf[4096];
|
||||||
int ret;
|
int ret;
|
||||||
struct Response args;
|
struct Response args;
|
||||||
|
*resp = '\0';
|
||||||
|
|
||||||
struct NameValueParserData data;
|
struct NameValueParserData data;
|
||||||
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
|
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
struct Response
|
struct Response
|
||||||
{
|
{
|
||||||
char *resp;
|
char *resp;
|
||||||
|
int start;
|
||||||
int returned;
|
int returned;
|
||||||
int requested;
|
int requested;
|
||||||
int total;
|
int total;
|
||||||
|
126
utils.c
126
utils.c
@ -25,6 +25,7 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <gd.h>
|
||||||
|
|
||||||
int
|
int
|
||||||
ends_with(const char * haystack, const char * needle)
|
ends_with(const char * haystack, const char * needle)
|
||||||
@ -147,3 +148,128 @@ make_dir(char * path, mode_t mode)
|
|||||||
printf("make_dir: cannot create directory '%s'", path);
|
printf("make_dir: cannot create directory '%s'", path);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Use our own boxfilter resizer, because gdCopyImageResampled is slow,
|
||||||
|
* and gdCopyImageResized looks horrible when you downscale much. */
|
||||||
|
#define N_FRAC 8
|
||||||
|
#define MASK_FRAC ((1 << N_FRAC) - 1)
|
||||||
|
#define ROUND2(v) (((v) + (1 << (N_FRAC - 1))) >> N_FRAC)
|
||||||
|
#define DIV(x, y) ( ((x) << (N_FRAC - 3)) / ((y) >> 3) )
|
||||||
|
void
|
||||||
|
boxfilter_resize(gdImagePtr dst, gdImagePtr src,
|
||||||
|
int dstX, int dstY, int srcX, int srcY,
|
||||||
|
int dstW, int dstH, int srcW, int srcH)
|
||||||
|
{
|
||||||
|
int x, y;
|
||||||
|
int sy1, sy2, sx1, sx2;
|
||||||
|
|
||||||
|
if(!dst->trueColor)
|
||||||
|
{
|
||||||
|
gdImageCopyResized(dst, src, dstX, dstY, srcX, srcY, dstW, dstH,
|
||||||
|
srcW, srcH);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(y = dstY; y < (dstY + dstH); y++)
|
||||||
|
{
|
||||||
|
sy1 = (((y - dstY) * srcH) << N_FRAC) / dstH;
|
||||||
|
sy2 = (((y - dstY + 1) * srcH) << N_FRAC) / dstH;
|
||||||
|
for(x = dstX; x < (dstX + dstW); x++)
|
||||||
|
{
|
||||||
|
int sx, sy;
|
||||||
|
int spixels = 0;
|
||||||
|
int red = 0, green = 0, blue = 0, alpha = 0;
|
||||||
|
sx1 = (((x - dstX) * srcW) << N_FRAC) / dstW;
|
||||||
|
sx2 = (((x - dstX + 1) * srcW) << N_FRAC) / dstW;
|
||||||
|
sy = sy1;
|
||||||
|
do {
|
||||||
|
int yportion;
|
||||||
|
if((sy >> N_FRAC) == (sy1 >> N_FRAC))
|
||||||
|
{
|
||||||
|
yportion = (1 << N_FRAC) - (sy & MASK_FRAC);
|
||||||
|
if(yportion > sy2 - sy1)
|
||||||
|
{
|
||||||
|
yportion = sy2 - sy1;
|
||||||
|
}
|
||||||
|
sy = sy & ~MASK_FRAC;
|
||||||
|
}
|
||||||
|
else if(sy == (sy2 & ~MASK_FRAC))
|
||||||
|
{
|
||||||
|
yportion = sy2 & MASK_FRAC;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yportion = (1 << N_FRAC);
|
||||||
|
}
|
||||||
|
sx = sx1;
|
||||||
|
do {
|
||||||
|
int xportion;
|
||||||
|
int pcontribution;
|
||||||
|
int p;
|
||||||
|
if((sx >> N_FRAC) == (sx1 >> N_FRAC))
|
||||||
|
{
|
||||||
|
xportion = (1 << N_FRAC) - (sx & MASK_FRAC);
|
||||||
|
if(xportion > sx2 - sx1)
|
||||||
|
{
|
||||||
|
xportion = sx2 - sx1;
|
||||||
|
}
|
||||||
|
sx = sx & ~MASK_FRAC;
|
||||||
|
}
|
||||||
|
else if(sx == (sx2 & ~MASK_FRAC))
|
||||||
|
{
|
||||||
|
xportion = sx2 & MASK_FRAC;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
xportion = (1 << N_FRAC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(xportion && yportion)
|
||||||
|
{
|
||||||
|
pcontribution = (xportion * yportion) >> N_FRAC;
|
||||||
|
p = gdImageGetTrueColorPixel(src, ROUND2(sx) + srcX, ROUND2(sy) + srcY);
|
||||||
|
if(pcontribution == (1 << N_FRAC))
|
||||||
|
{
|
||||||
|
// optimization for down-scaler, which many pixel has pcontribution=1
|
||||||
|
red += gdTrueColorGetRed(p) << N_FRAC;
|
||||||
|
green += gdTrueColorGetGreen(p) << N_FRAC;
|
||||||
|
blue += gdTrueColorGetBlue(p) << N_FRAC;
|
||||||
|
alpha += gdTrueColorGetAlpha(p) << N_FRAC;
|
||||||
|
spixels += (1 << N_FRAC);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
red += gdTrueColorGetRed(p) * pcontribution;
|
||||||
|
green += gdTrueColorGetGreen(p) * pcontribution;
|
||||||
|
blue += gdTrueColorGetBlue(p) * pcontribution;
|
||||||
|
alpha += gdTrueColorGetAlpha(p) * pcontribution;
|
||||||
|
spixels += pcontribution;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sx += (1 << N_FRAC);
|
||||||
|
}
|
||||||
|
while(sx < sx2);
|
||||||
|
sy += (1 << N_FRAC);
|
||||||
|
}
|
||||||
|
while(sy < sy2);
|
||||||
|
if(spixels != 0)
|
||||||
|
{
|
||||||
|
red = DIV(red, spixels);
|
||||||
|
green = DIV(green, spixels);
|
||||||
|
blue = DIV(blue, spixels);
|
||||||
|
alpha = DIV(alpha, spixels);
|
||||||
|
}
|
||||||
|
/* Clamping to allow for rounding errors above */
|
||||||
|
if(red > (255 << N_FRAC))
|
||||||
|
red = (255 << N_FRAC);
|
||||||
|
if(green > (255 << N_FRAC))
|
||||||
|
green = (255 << N_FRAC);
|
||||||
|
if(blue > (255 << N_FRAC))
|
||||||
|
blue = (255 << N_FRAC);
|
||||||
|
if(alpha > (gdAlphaMax << N_FRAC))
|
||||||
|
alpha = (gdAlphaMax << N_FRAC);
|
||||||
|
gdImageSetPixel(dst, x, y,
|
||||||
|
gdTrueColorAlpha(ROUND2(red), ROUND2(green), ROUND2(blue), ROUND2(alpha)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
6
utils.h
6
utils.h
@ -9,6 +9,7 @@
|
|||||||
* */
|
* */
|
||||||
#ifndef __UTILS_H__
|
#ifndef __UTILS_H__
|
||||||
#define __UTILS_H__
|
#define __UTILS_H__
|
||||||
|
#include <gd.h>
|
||||||
|
|
||||||
int
|
int
|
||||||
ends_with(const char * haystack, const char * needle);
|
ends_with(const char * haystack, const char * needle);
|
||||||
@ -25,4 +26,9 @@ strip_ext(char * name);
|
|||||||
int
|
int
|
||||||
make_dir(char * path, mode_t mode);
|
make_dir(char * path, mode_t mode);
|
||||||
|
|
||||||
|
/* A GD resize function with a good balance of quality and speed */
|
||||||
|
void
|
||||||
|
boxfilter_resize(gdImagePtr dst, gdImagePtr src,
|
||||||
|
int dstX, int dstY, int srcX, int srcY,
|
||||||
|
int dstW, int dstH, int srcW, int srcH);
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
x
Reference in New Issue
Block a user