diff --git a/albumart.c b/albumart.c index 2eb48d1..8040f4c 100644 --- a/albumart.c +++ b/albumart.c @@ -61,130 +61,6 @@ check_res(int width, int height, char * dlna_pn) } #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 * 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); goto error; } - #if 0 // Try our box filter resizer for now + #if 0 // Try our box filter resizer instead #ifdef __sparc__ gdImageCopyResized(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy); #else diff --git a/minidlna.c b/minidlna.c index 9d415cc..4e81a50 100644 --- a/minidlna.c +++ b/minidlna.c @@ -46,7 +46,7 @@ #include "inotify.h" #include "commonrdr.h" #include "log.h" -#ifdef ENABLE_TIVO +#ifdef TIVO_SUPPORT #include "tivo_beacon.h" #endif @@ -386,6 +386,10 @@ init(int argc, char * * argv) if( (strcmp(ary_options[i].value, "yes") != 0) && !atoi(ary_options[i].value) ) CLEARFLAG(INOTIFYMASK); break; + case ENABLE_TIVO: + if( (strcmp(ary_options[i].value, "yes") == 0) || atoi(ary_options[i].value) ) + SETFLAG(TIVOMASK); + break; default: fprintf(stderr, "Unknown option in file %s\n", optionsfile); @@ -589,6 +593,9 @@ main(int argc, char * * argv) struct timeval timeout, timeofday, lasttimeofday = {0, 0}, lastupdatetime = {0, 0}; int max_fd = -1; int last_changecnt = 0; + unsigned short int loop_cnt = 0; + int sbeacon; + struct sockaddr_in tivo_bcast; char * sql; pthread_t thread[2]; @@ -662,19 +669,21 @@ main(int argc, char * * argv) "messages. EXITING\n"); } - #ifdef ENABLE_TIVO - /* open socket for sending Tivo notifications */ - unsigned short int loop_cnt = 0; - int sbeacon = OpenAndConfTivoBeaconSocket(); - struct sockaddr_in bcast; - if(sbeacon < 0) + #ifdef TIVO_SUPPORT + if( GETFLAG(TIVOMASK) ) { - DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending Tivo beacon notify " - "messages. EXITING\n"); + DPRINTF(E_WARN, L_GENERAL, "TiVo support is enabled.\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 SendSSDPGoodbye(snotify, n_lan_addr); @@ -727,20 +736,23 @@ main(int argc, char * * argv) last_changecnt = sqlite3_total_changes(db); upnp_event_var_change_notify(EContentDirectory); } - #ifdef ENABLE_TIVO - if( loop_cnt < 10 ) + #ifdef TIVO_SUPPORT + if( GETFLAG(TIVOMASK) ) { - sendBeaconMessage(sbeacon, &bcast, sizeof(struct sockaddr_in), 1); - loop_cnt++; - } - else - { - if( loop_cnt == 30 ) + if( loop_cnt < 10 ) { - sendBeaconMessage(sbeacon, &bcast, sizeof(struct sockaddr_in), 1); - loop_cnt = 10; + sendBeaconMessage(sbeacon, &tivo_bcast, sizeof(struct sockaddr_in), 1); + loop_cnt++; + } + else + { + if( loop_cnt == 30 ) + { + sendBeaconMessage(sbeacon, &tivo_bcast, sizeof(struct sockaddr_in), 1); + loop_cnt = 10; + } + loop_cnt++; } - loop_cnt++; } #endif memcpy(&lastupdatetime, &timeofday, sizeof(struct timeval)); diff --git a/minidlna.conf b/minidlna.conf index eb2d0bf..3490585 100644 --- a/minidlna.conf +++ b/minidlna.conf @@ -21,6 +21,9 @@ album_art_names=Cover.jpg/cover.jpg/AlbumArtSmall.jpg/albumartsmall.jpg/AlbumArt # note: the default is 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 #presentation_url=http://www.mylan/index.php diff --git a/options.c b/options.c index f9a7bdc..7eb3fd0 100644 --- a/options.c +++ b/options.c @@ -33,7 +33,8 @@ static const struct { { UPNPFRIENDLYNAME, "friendly_name"}, { UPNPMEDIADIR, "media_dir"}, { UPNPALBUMART_NAMES, "album_art_names"}, - { UPNPINOTIFY, "inotify" } + { UPNPINOTIFY, "inotify" }, + { ENABLE_TIVO, "enable_tivo" } }; int diff --git a/options.h b/options.h index 8d4727e..b7260a5 100644 --- a/options.h +++ b/options.h @@ -14,19 +14,20 @@ enum upnpconfigoptions { UPNP_INVALID = 0, UPNPEXT_IFNAME = 1, /* ext_ifname */ - UPNPEXT_IP, /* ext_ip */ + UPNPEXT_IP, /* ext_ip */ UPNPLISTENING_IP, /* listening_ip */ - UPNPPORT, /* "port" */ - UPNPPRESENTATIONURL, /* presentation_url */ - UPNPNOTIFY_INTERVAL, /* notify_interval */ + UPNPPORT, /* "port" */ + UPNPPRESENTATIONURL, /* presentation_url */ + UPNPNOTIFY_INTERVAL, /* notify_interval */ UPNPSYSTEM_UPTIME, /* "system_uptime" */ - UPNPUUID, /* uuid */ - UPNPSERIAL, /* serial */ + UPNPUUID, /* uuid */ + UPNPSERIAL, /* serial */ UPNPMODEL_NUMBER, /* model_number */ UPNPFRIENDLYNAME, /* how the system should show up to DLNA clients */ UPNPMEDIADIR, /* directory to search for UPnP-A/V content */ - UPNPALBUMART_NAMES, /* list of '/'-delimited file names to check for album art */ - UPNPINOTIFY /* enable inotify on the media directories */ + UPNPALBUMART_NAMES, /* list of '/'-delimited file names to check for album art */ + UPNPINOTIFY, /* enable inotify on the media directories */ + ENABLE_TIVO /* enable support for streaming images and music to TiVo */ }; /* readoptionsfile() diff --git a/tivo_beacon.c b/tivo_beacon.c index 804ead4..eb68b1d 100644 --- a/tivo_beacon.c +++ b/tivo_beacon.c @@ -27,7 +27,7 @@ * See the file "COPYING" for more details. */ #include "config.h" -#ifdef ENABLE_TIVO +#ifdef TIVO_SUPPORT #include #include #include @@ -284,4 +284,4 @@ rcvBeaconMessage(char * beacon) return 1; return 0; } -#endif // ENABLE_TIVO +#endif // TIVO_SUPPORT diff --git a/tivo_beacon.h b/tivo_beacon.h index 231984b..c4f688e 100644 --- a/tivo_beacon.h +++ b/tivo_beacon.h @@ -1,5 +1,5 @@ #include "config.h" -#ifdef ENABLE_TIVO +#ifdef TIVO_SUPPORT /* * * A saved copy of a beacon from another tivo or another server * */ diff --git a/tivo_commands.c b/tivo_commands.c index 9e667e6..71871aa 100644 --- a/tivo_commands.c +++ b/tivo_commands.c @@ -1,12 +1,16 @@ #include "config.h" -#ifdef ENABLE_TIVO +#ifdef TIVO_SUPPORT #include #include #include +#include #include "tivo_utils.h" #include "upnpglobalvars.h" #include "upnphttp.h" +#include "upnpsoap.h" +#include "utils.h" +#include "sql.h" #include "log.h" void @@ -26,247 +30,6 @@ SendRootContainer(struct upnphttp * h) "" "0" "2" - "" - "
" - "x-container/tivo-photos" - "x-container/folder" - "Pictures on %s" - "
" - "" - "" - "/TiVoConnect?Command=QueryContainer&Container=3/Pictures on %s" - "" - "" - "
" - "" - "
" - "x-container/tivo-music" - "x-container/folder" - "Music on %s" - "
" - "" - "" - "/TiVoConnect?Command=QueryContainer&Container=1" - "" - "" - "
" - "", 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, "\n" - "" - "
" - "x-container/tivo-%s" - "x-container/folder" - "0" - "2" - "%s" - "
" - "%d" - "%d" "" "
" "x-container/tivo-photos" @@ -291,15 +54,334 @@ SendContainer(struct upnphttp * h, const char * objectID, const char * title, in "" "" "" - "", - (objectID[0]=='1') ? "music":"photos", - title, itemStart, itemCount, title, title); + "", friendly_name, friendly_name, friendly_name); BuildResp_upnphttp(h, resp, len); 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, "
" + "audio/*" + "audio/mpeg" + "%s" + "%s", size, title); + strcat(passed_args->resp, str_buf); + if( date ) + { + sprintf(str_buf, "%.*s", 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, "
" + "image/*" + "image/jpeg" + "%s", 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, "0x%X", (unsigned int)mktime(&tm)); + strcat(passed_args->resp, str_buf); + } + if( comment ) { + sprintf(str_buf, "%s", comment); + strcat(passed_args->resp, str_buf); + } + } + else + { + return 0; + } + sprintf(str_buf, "%s", title); + strcat(passed_args->resp, str_buf); + if( artist ) { + sprintf(str_buf, "%s", artist); + strcat(passed_args->resp, str_buf); + } + if( album ) { + sprintf(str_buf, "%s", album); + strcat(passed_args->resp, str_buf); + } + if( genre ) { + sprintf(str_buf, "%s", genre); + strcat(passed_args->resp, str_buf); + } + if( resolution ) { + sprintf(str_buf, "%.*s" + "%s", + (index(resolution, 'x')-resolution), resolution, (rindex(resolution, 'x')+1)); + strcat(passed_args->resp, str_buf); + } + if( duration ) { + sprintf(str_buf, "%d", + 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, "%s", bitrate); + strcat(passed_args->resp, str_buf); + } + if( sampleFrequency ) { + sprintf(str_buf, "%s", sampleFrequency); + strcat(passed_args->resp, str_buf); + } + sprintf(str_buf, "
/%s/%s.dat%s", + is_audio?"MediaItems":"Resized", detailID, is_audio?"No":""); + } + 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, "
" + "x-container/folder" + "x-container/folder"); + sprintf(str_buf, "%s" + "%s" + "
", title, result[1]); + strcat(passed_args->resp, str_buf); + + sprintf(str_buf, "" + "/TiVoConnect?Command=QueryContainer&Container=%s" + "", id); + sqlite3_free_table(result); + } + strcat(passed_args->resp, str_buf); + strcat(passed_args->resp, "
"); + + 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, "\n" + "" + "
" + "x-container/tivo-%s" + "x-container/folder" + "%d" + "%s" + "
" + "%d" + "%d" + "%s" /* the actual items xml */ + "
", + (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 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 *key, *val; @@ -307,8 +389,8 @@ ProcessTiVoCommand(struct upnphttp * h, const char * orig_path) 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); + path = decodeString(orig_path, 0); + DPRINTF(E_DEBUG, L_GENERAL, "Processing TiVo request %s\n", path); item = strtok_r( path, "&", &saveptr ); while( item != NULL ) @@ -321,38 +403,25 @@ ProcessTiVoCommand(struct upnphttp * h, const char * orig_path) val = item; key = strsep(&val, "="); DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val); - if( strcasecmp(key, "command") == 0 ) + if( strcasecmp(key, "width") == 0 ) { command = val; } - else if( strcasecmp(key, "container") == 0 ) + else if( strcasecmp(key, "height") == 0 ) { container = val; } - else if( strcasecmp(key, "itemstart") == 0 ) + else if( strcasecmp(key, "rotation") == 0 ) { itemStart = atoi(val); } - else if( strcasecmp(key, "itemcount") == 0 ) + else if( strcasecmp(key, "pixelshape") == 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 +#endif // TIVO_SUPPORT diff --git a/tivo_commands.h b/tivo_commands.h index 8130cfd..8cec076 100644 --- a/tivo_commands.h +++ b/tivo_commands.h @@ -1,5 +1,5 @@ #include "config.h" -#ifdef ENABLE_TIVO +#ifdef TIVO_SUPPORT void ProcessTiVoCommand(struct upnphttp * h, const char * orig_path); #endif diff --git a/tivo_utils.c b/tivo_utils.c index 1d439b2..4d8a20e 100644 --- a/tivo_utils.c +++ b/tivo_utils.c @@ -1,29 +1,31 @@ #include "config.h" -#ifdef ENABLE_TIVO +#ifdef TIVO_SUPPORT #include #include #include -/* This function stolen from byRequest */ +/* This function based on byRequest */ char * -decodeString(const char * string) +decodeString(char * string, int inplace) { if( !string ) return NULL; - int alloc = (int)strlen(string)+1; - char *ns = malloc(alloc); + char *ns; unsigned char in; int strindex=0; long hex; - if( !ns ) - return NULL; + if( !inplace ) + { + if( !(ns = malloc(alloc)) ) + return NULL; + } while(--alloc > 0) { 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 '%' */ char hexstr[3]; @@ -35,14 +37,29 @@ decodeString(const char * string) hex = strtol(hexstr, &ptr, 16); 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; } - - ns[strindex++] = in; + if( !inplace ) + ns[strindex++] = in; string++; } - ns[strindex]=0; /* terminate it */ - return ns; + if( inplace ) + { + return string; + } + else + { + ns[strindex] = '\0'; /* terminate it */ + return ns; + } } #endif diff --git a/tivo_utils.h b/tivo_utils.h index f4278ec..b32c13f 100644 --- a/tivo_utils.h +++ b/tivo_utils.h @@ -1,5 +1,5 @@ #include "config.h" -#ifdef ENABLE_TIVO +#ifdef TIVO_SUPPORT char * -decodeString(const char * string); +decodeString(const char * string, int inplace); #endif diff --git a/upnpglobalvars.h b/upnpglobalvars.h index 76a45b3..a3afe26 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -59,6 +59,7 @@ extern struct runtime_vars_s runtime_vars; extern int runtime_flags; #define INOTIFYMASK 0x0001 #define SYSUPTIMEMASK 0x0002 +#define TIVOMASK 0x0004 #define SETFLAG(mask) runtime_flags |= mask #define GETFLAG(mask) runtime_flags & mask diff --git a/upnphttp.c b/upnphttp.c index 36a3a0b..8191d1d 100644 --- a/upnphttp.c +++ b/upnphttp.c @@ -34,12 +34,10 @@ #include "upnpglobalvars.h" #include "utils.h" #include "log.h" -#include +#include "sql.h" #include -#if 0 //JPEG_RESIZE #include -#endif -#ifdef ENABLE_TIVO +#ifdef TIVO_SUPPORT #include "tivo_utils.h" #include "tivo_commands.h" #endif @@ -480,7 +478,7 @@ static void ProcessHttpQuery_upnphttp(struct upnphttp * h) { char HttpCommand[16]; - char HttpUrl[256]; + char HttpUrl[512]; char * HttpVer; char * p; int i; @@ -498,7 +496,7 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) while(*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] = '\0'; while(*p==' ') @@ -507,9 +505,9 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) for(i = 0; i<15 && *p != '\r'; i++) HttpVer[i] = *(p++); HttpVer[i] = '\0'; - DPRINTF(E_INFO, L_HTTP, "HTTP REQUEST : %s %s (%s)\n", - HttpCommand, HttpUrl, HttpVer); - DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST:\n%.*s\n", h->req_buflen, h->req_buf); + /*DPRINTF(E_INFO, L_HTTP, "HTTP REQUEST : %s %s (%s)\n", + HttpCommand, HttpUrl, HttpVer);*/ + DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf); ParseHttpHeaders(h); /* see if we need to wait for remaining data */ @@ -589,32 +587,33 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) SendResp_albumArt(h, HttpUrl+10); CloseSocket_upnphttp(h); } - #ifdef ENABLE_TIVO + #ifdef TIVO_SUPPORT else if(strncmp(HttpUrl, "/TiVoConnect", 12) == 0) { - if( *(HttpUrl+12) == '?' ) + if( GETFLAG(TIVOMASK) ) { - ProcessTiVoCommand(h, HttpUrl+13); - } - else if( *(HttpUrl+12) == '/' ) - { - printf("TiVo request: %c\n", *(HttpUrl+12)); - Send404(h); + if( *(HttpUrl+12) == '?' ) + { + ProcessTiVoCommand(h, HttpUrl+13); + } + else + { + printf("Invalid TiVo request! %s\n", HttpUrl+12); + Send404(h); + } } else { - printf("Invalid TiVo request! %s\n", HttpUrl+12); + printf("TiVo request with out TiVo support enabled! %s\n", HttpUrl+12); Send404(h); } } #endif -#if 0 //JPEG_RESIZE else if(strncmp(HttpUrl, "/Resized/", 9) == 0) { SendResp_resizedimg(h, HttpUrl+9); CloseSocket_upnphttp(h); } -#endif else if(strncmp(HttpUrl, "/icons/", 7) == 0) { SendResp_icon(h, HttpUrl+7); @@ -829,7 +828,7 @@ void SendResp_upnphttp(struct upnphttp * h) { 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); if(n<0) { @@ -1106,7 +1105,6 @@ SendResp_thumbnail(struct upnphttp * h, char * object) sqlite3_free_table(result); } -#if 0 //JPEG_RESIZE void SendResp_resizedimg(struct upnphttp * h, char * object) { @@ -1115,14 +1113,54 @@ SendResp_resizedimg(struct upnphttp * h, char * object) char **result; char date[30]; time_t curtime = time(NULL); - int n; FILE *imgfile; - gdImagePtr imsrc = 0, imdst = 0; - int dstw, dsth, srcw, srch, size; + int width=640, height=480, dstw, dsth, rotation, size; 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); + 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 ) { 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; } - 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); - sqlite3_get_table(db, sql_buf, &result, 0, 0, 0); - DPRINTF(E_INFO, L_HTTP, "Serving up resized image for ObjectId: %s [%s]\n", object, result[1]); - - if( access(result[3], F_OK) == 0 ) + sprintf(sql_buf, "SELECT PATH, RESOLUTION, THUMBNAIL from DETAILS where ID = '%s'", path); + ret = sql_get_table(db, sql_buf, &result, &rows, NULL); + if( (ret != SQLITE_OK) || !rows || (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"); - imsrc = gdImageCreateFromJpeg(imgfile); - imdst = gdImageCreateTrueColor(dstw, dsth); - srcw = atoi(result[4]); - srch = atoi(result[5]); - dstw = 640; - dsth = ((((640<<10)/srcw)*srch)>>10); + /* Resizing from a thumbnail is much faster than from a large image */ + if( width <= 160 && height <= 120 && atoi(tn) ) + { + l = exif_loader_new(); + exif_loader_write_file(l, file_path); + ed = exif_loader_get_data(l); + 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); - goto error; + sqlite3_free_table(result); + return; } - if( dsth > 480 ) - { - 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); + imsrc = gdImageCreateFromJpeg(imgfile); + fclose(imgfile); } - 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(imdst); + DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path); sqlite3_free_table(result); } -#endif void SendResp_dlnafile(struct upnphttp * h, char * object) diff --git a/upnpsoap.c b/upnpsoap.c index d502d61..724b8ed 100644 --- a/upnpsoap.c +++ b/upnpsoap.c @@ -408,14 +408,15 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) static const char resp1[] = "</DIDL-Lite>"; static const char resp2[] = ""; - char *resp = calloc(1, 1048576); + char *resp = malloc(1048576); char str_buf[4096]; char *zErrMsg = 0; char *sql; int ret; struct Response args; - struct NameValueParserData data; + *resp = '\0'; + ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); int RequestedCount = atoi( GetValueFromNameValueList(&data, "RequestedCount") ); int StartingIndex = atoi( GetValueFromNameValueList(&data, "StartingIndex") ); @@ -510,12 +511,13 @@ SearchContentDirectory(struct upnphttp * h, const char * action) static const char resp1[] = "</DIDL-Lite>"; static const char resp2[] = ""; - char *resp = calloc(1, 1048576); + char *resp = malloc(1048576); char *zErrMsg = 0; char *sql; char str_buf[4096]; int ret; struct Response args; + *resp = '\0'; struct NameValueParserData data; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data); diff --git a/upnpsoap.h b/upnpsoap.h index ceb8324..689ca71 100644 --- a/upnpsoap.h +++ b/upnpsoap.h @@ -17,6 +17,7 @@ struct Response { char *resp; + int start; int returned; int requested; int total; diff --git a/utils.c b/utils.c index ce82371..0fafe2a 100644 --- a/utils.c +++ b/utils.c @@ -25,6 +25,7 @@ #include #include #include +#include int 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); 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))); + } + } +} + diff --git a/utils.h b/utils.h index ace5305..192a1d2 100644 --- a/utils.h +++ b/utils.h @@ -9,6 +9,7 @@ * */ #ifndef __UTILS_H__ #define __UTILS_H__ +#include int ends_with(const char * haystack, const char * needle); @@ -25,4 +26,9 @@ strip_ext(char * name); int 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