Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/azatoth/minidlna.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'upnphttp.c')
-rw-r--r--upnphttp.c451
1 files changed, 423 insertions, 28 deletions
diff --git a/upnphttp.c b/upnphttp.c
index 3017a19..dd616eb 100644
--- a/upnphttp.c
+++ b/upnphttp.c
@@ -77,6 +77,9 @@
#include "log.h"
#include "sql.h"
#include <libexif/exif-loader.h>
+#ifdef THUMBNAIL_CREATION_SUPPORT
+#include <libffmpegthumbnailer/videothumbnailerc.h>
+#endif
#ifdef TIVO_SUPPORT
#include "tivo_utils.h"
#include "tivo_commands.h"
@@ -405,6 +408,10 @@ intervening space) by either an integer or the keyword "infinite". */
{
h->reqflags |= FLAG_CAPTION;
}
+ else if(strncasecmp(line, "getMediaInfo.sec", 16)==0)
+ {
+ h->reqflags |= FLAG_MEDIA_INFO;
+ }
}
next_header:
while(!(line[0] == '\r' && line[1] == '\n'))
@@ -804,11 +811,11 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h)
sendXMLdesc(h, genRootDesc);
friendly_name[i] = '\0';
}
+ else if (h->req_client == ESamsungTV)
+ sendXMLdesc(h, genRootDescSamsung);
else
- {
sendXMLdesc(h, genRootDesc);
}
- }
else if(strcmp(CONTENTDIRECTORY_PATH, HttpUrl) == 0)
{
sendXMLdesc(h, genContentDirectory);
@@ -835,6 +842,11 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h)
SendResp_albumArt(h, HttpUrl+10);
CloseSocket_upnphttp(h);
}
+ else if((strncmp(HttpUrl, "/MTA/", 5) == 0) && (h->req_client == ESamsungTV))
+ {
+ SendResp_samsung_mta_file(h, HttpUrl+5);
+ CloseSocket_upnphttp(h);
+ }
#ifdef TIVO_SUPPORT
else if(strncmp(HttpUrl, "/TiVoConnect", 12) == 0)
{
@@ -980,6 +992,7 @@ static const char httpresphead[] =
"Content-Type: %s\r\n"
"Connection: close\r\n"
"Content-Length: %d\r\n"
+ "EXT:\r\n"
"Server: " MINIDLNA_SERVER_STRING "\r\n"
// "Accept-Ranges: bytes\r\n"
; /*"\r\n";*/
@@ -1174,7 +1187,7 @@ send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset)
void
SendResp_icon(struct upnphttp * h, char * icon)
{
- char * header;
+ char * header = 0;
char * data;
int size, ret;
char mime[12];
@@ -1226,6 +1239,8 @@ SendResp_icon(struct upnphttp * h, char * icon)
"Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
mime, size, date);
+ DPRINTF(E_INFO, L_HTTP, "%s", header);
+
if( (send_data(h, header, ret, MSG_MORE) == 0) && (h->req_command != EHead) )
{
send_data(h, data, size, 0);
@@ -1288,20 +1303,21 @@ SendResp_albumArt(struct upnphttp * h, char * object)
"Connection: close\r\n"
"Date: %s\r\n"
"EXT:\r\n"
- "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
"contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n"
"Server: " MINIDLNA_SERVER_STRING "\r\n",
size, date);
+ if( h->reqflags & FLAG_REALTIMEINFO )
+ strcat(header, "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n");
+
if( h->reqflags & FLAG_XFERBACKGROUND )
- {
- strcat(header, "transferMode.dlna.org: Background\r\n\r\n");
- }
+ strcat(header, "transferMode.dlna.org: Background\r\n");
else //if( h->reqflags & FLAG_XFERINTERACTIVE )
- {
- strcat(header, "transferMode.dlna.org: Interactive\r\n\r\n");
- }
+ strcat(header, "transferMode.dlna.org: Interactive\r\n");
+
+ DPRINTF(E_INFO, L_HTTP, "%s", header);
+ strcat(header, "\r\n");
if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) )
{
@@ -1353,13 +1369,23 @@ SendResp_caption(struct upnphttp * h, char * object)
lseek(sendfh, 0, SEEK_SET);
ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n"
+/* "Server: Samsung HTTP streaming server\r\n" */
+ "Server: " MINIDLNA_SERVER_STRING "\r\n"
"Content-Type: smi/caption\r\n"
"Content-Length: %jd\r\n"
- "Connection: close\r\n"
+ "Cache-Control: no-cache\r\n"
+ "contentFeatures.dlna.org: DLNA.ORG_OP=00;DLNA.ORG_CI=0;\r\n"
+/* "Connection: close\r\n" */
"Date: %s\r\n"
- "EXT:\r\n"
- "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
- size, date);
+ "EXT:\r\n",
+ size, date);
+
+ if( h->reqflags & FLAG_XFERBACKGROUND )
+ strcat(header, "transferMode.dlna.org:Background\r\n");
+
+ DPRINTF(E_INFO, L_HTTP, "%s", header);
+
+ strcat(header, "\r\n");
if( (send_data(h, header, ret, MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) )
{
@@ -1427,20 +1453,26 @@ SendResp_thumbnail(struct upnphttp * h, char * object)
"Connection: close\r\n"
"Date: %s\r\n"
"EXT:\r\n"
- "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
"contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n"
"Server: " MINIDLNA_SERVER_STRING "\r\n",
ed->size, date);
+ if( h->reqflags & FLAG_REALTIMEINFO )
+ strcat(header, "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n");
+
if( h->reqflags & FLAG_XFERBACKGROUND )
{
- strcat(header, "transferMode.dlna.org: Background\r\n\r\n");
+ strcat(header, "transferMode.dlna.org: Background\r\n");
}
else //if( h->reqflags & FLAG_XFERINTERACTIVE )
{
- strcat(header, "transferMode.dlna.org: Interactive\r\n\r\n");
+ strcat(header, "transferMode.dlna.org: Interactive\r\n");
}
+ DPRINTF(E_INFO, L_HTTP, "%s", header);
+
+ strcat(header, "\r\n");
+
if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) )
{
send_data(h, (char *)ed->data, ed->size, 0);
@@ -1577,10 +1609,12 @@ SendResp_resizedimg(struct upnphttp * h, char * object)
"Connection: close\r\n"
"Date: %s\r\n"
"EXT:\r\n"
- "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
"contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_%s;DLNA.ORG_CI=1\r\n"
"Server: " MINIDLNA_SERVER_STRING "\r\n",
date, dlna_pn);
+ if( h->reqflags & FLAG_REALTIMEINFO )
+ strcat(header, "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n");
+
if( h->reqflags & FLAG_XFERINTERACTIVE )
{
strcat(header, "transferMode.dlna.org: Interactive\r\n");
@@ -1638,6 +1672,8 @@ SendResp_resizedimg(struct upnphttp * h, char * object)
strcat(header, str_buf);
}
+ DPRINTF(E_INFO, L_HTTP, "%s", header);
+
if( (send_data(h, header, strlen(header), 0) == 0) && (h->req_command != EHead) )
{
if( chunked )
@@ -1688,7 +1724,7 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
off_t total, offset, size;
sqlite_int64 id;
int sendfh;
- static struct { sqlite_int64 id; char path[PATH_MAX]; char mime[32]; char dlna[64]; } last_file = { 0 };
+ static struct { sqlite_int64 id; char path[PATH_MAX]; char mime[32]; char dlna[128]; char duration[32]; } last_file = { 0 };
#if USE_FORK
pid_t newpid = 0;
#endif
@@ -1696,7 +1732,7 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
id = strtoll(object, NULL, 10);
if( id != last_file.id )
{
- sprintf(sql_buf, "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", id);
+ sprintf(sql_buf, "SELECT PATH, MIME, DLNA_PN, DURATION from DETAILS where ID = '%lld'", id);
ret = sql_get_table(db, sql_buf, &result, &rows, NULL);
if( (ret != SQLITE_OK) )
{
@@ -1713,10 +1749,10 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
}
/* Cache the result */
last_file.id = id;
- strncpy(last_file.path, result[3], sizeof(last_file.path)-1);
- if( result[4] )
+ strncpy(last_file.path, result[4], sizeof(last_file.path)-1);
+ if( result[5] )
{
- strncpy(last_file.mime, result[4], sizeof(last_file.mime)-1);
+ strncpy(last_file.mime, result[5], sizeof(last_file.mime)-1);
/* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
if( h->req_client == ESamsungTV )
{
@@ -1735,12 +1771,16 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
{
last_file.mime[0] = '\0';
}
- if( result[5] )
- snprintf(last_file.dlna, sizeof(last_file.dlna), "DLNA.ORG_PN=%s", result[5]);
+ if( result[6] )
+ snprintf(last_file.dlna, sizeof(last_file.dlna), "DLNA.ORG_PN=%s", result[6]);
else if( h->reqflags & FLAG_DLNA )
strcpy(last_file.dlna, dlna_no_conv);
else
last_file.dlna[0] = '\0';
+
+ if( result[7] )
+ strncpy(last_file.duration, result[7], sizeof(last_file.duration)-1);
+
sqlite3_free_table(result);
}
#if USE_FORK
@@ -1868,16 +1908,41 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
}
}
+ if( last_file.duration && h->reqflags & FLAG_MEDIA_INFO )
+ {
+ int dur = atoi(rindex(last_file.duration, '.')+1) +
+ (1000*atoi(rindex(last_file.duration, ':')+1)) +
+ (60000*atoi(rindex(last_file.duration, ':')-2)) +
+ (3600000*atoi(last_file.duration));
+ sprintf(hdr_buf, "MediaInfo.sec: SEC_Duration=%d;\r\n", dur);
+ strcat(header, hdr_buf);
+ }
+
+ /* Special Samsung DLNA flags */
+ if( h->req_client == ESamsungTV )
+ {
+ if( strncmp(last_file.mime, "image", 5) == 0 )
+ strcat(last_file.dlna, ";DLNA.ORG_FLAGS=00D00000000000000000000000000000");
+ else if (strncmp(last_file.mime, "video", 5) == 0)
+ strcat(last_file.dlna, ";DLNA.ORG_FLAGS=01500000000000000000000000000000");
+ }
+
sprintf(hdr_buf, "Accept-Ranges: bytes\r\n"
"Connection: close\r\n"
"Date: %s\r\n"
"EXT:\r\n"
- "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
- "contentFeatures.dlna.org: %s\r\n"
- "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
+ "contentFeatures.dlna.org:%s\r\n"
+ "Server: " MINIDLNA_SERVER_STRING "\r\n",
date, last_file.dlna);
strcat(header, hdr_buf);
+ if( h->reqflags & FLAG_REALTIMEINFO )
+ strcat(header, "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n");
+
+ DPRINTF(E_INFO, L_HTTP, "%s", header);
+
+ strcat(header, "\r\n");
+
if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) )
{
send_file(h, sendfh, offset, h->req_RangeEnd);
@@ -1891,3 +1956,333 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
#endif
return;
}
+
+/* encode 6 bit to base64 char */
+static char encode_base64(unsigned char c)
+{
+ if (c < 26)
+ return 'A' + c;
+ if (c < 52)
+ return 'a' + (c-26);
+ if (c < 62)
+ return '0' + (c-52);
+ if (c == 62)
+ return '+';
+
+ /* 63 */
+ return '/';
+}
+
+void
+adjust_buffer(char** buffer, int* length)
+{
+ *length += *length; /* double buffer size */
+ *buffer = realloc(*buffer, *length+1); /* get one for the terminating zero */
+}
+
+/* append data to buffer, if necessary get more memory and rebase current ptr */
+void
+add_to_buffer(char** buffer, int* length, char** ptr, const char* data, int datalen)
+{
+ if (*ptr - *buffer > *length) {
+ *ptr -= (int)*buffer;
+ adjust_buffer(buffer, length);
+ *ptr += (int)*buffer; /* rebase ptr */
+ }
+ memcpy(*ptr, data, datalen);
+ *ptr += datalen;
+}
+
+/*
+ * generates samsung MTA files for movies
+ * An MTA file contains XML with 5 chapter points each having an inline thumbnail
+ * in base64 encoding.
+ * The files are created with the minidlna-user (e.g. root).
+ *
+ * path char* IN path and filename of movie
+ * duration int IN duration of movie in seconds
+ */
+void
+generate_external_samsung_mta_file(const char* path, int duration)
+{
+ char mta_path[PATH_MAX];
+
+ snprintf(mta_path, sizeof(mta_path), "%s.mta", path);
+
+ if (ends_with(path, ".avi") || ends_with(path, ".mkv") || ends_with(path, ".mpg")) {
+ DPRINTF(E_DEBUG, L_METADATA, "Checking for external MTA file: %s\n", mta_path);
+ if (access(mta_path, F_OK) == 0)
+ return; /* already found one, ok */
+
+ /* check movie */
+ if (access(path, F_OK) == 0) {
+ int mta_size = 0;
+ char* body = generate_samsung_mta_file(path, duration);
+ mta_size = strlen(body);
+
+ FILE* mtafile = fopen(mta_path, "w");
+ if (mtafile) {
+ int size_written = fwrite((void *)body, 1, mta_size, mtafile);
+ fclose(mtafile);
+ if (size_written != mta_size) {
+ DPRINTF(E_DEBUG, L_METADATA, "Could not write external MTA file: '%s'\n", mta_path);
+ remove(mta_path);
+ } else
+ DPRINTF(E_DEBUG, L_METADATA, "Could open external MTA file for writing: '%s'\n", mta_path);
+ }
+ }
+ }
+}
+
+char*
+generate_samsung_mta_file(const char* path, int duration)
+{
+ static const char mta_header[] =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
+ "<SEC:SECMeta xsi:schemaLocation=\"urn:samsung:metadata:2009 Video_Metadata_v1.0.xsd\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:SEC=\"urn:samsung:metadata:2009\">\r\n"
+ "<SpecVersion>1.0</SpecVersion>\r\n"
+ "<MediaInformation>\r\n"
+ "<VideoLocator>\r\n"
+ "<MediaUri>file://samsung_content.con</MediaUri>\r\n"
+ "</VideoLocator>\r\n"
+ "</MediaInformation>\r\n"
+ "<ContentInformation>\r\n"
+ "<Chaptering>\r\n"
+ ;
+ static const char mta_chapter_header[] =
+ "<ChapterSegment>\r\n"
+ "<KeyFrame>\r\n"
+ "<InlineMedia>"
+ ;
+ static const char mta_chapter_closing[] =
+ "</InlineMedia>\r\n"
+ "</KeyFrame>\r\n"
+ "<MediaPosition>\r\n"
+ "<MediaTime timePoint=\"%d\"/>\r\n"
+ "</MediaPosition>\r\n"
+ "</ChapterSegment>\r\n"
+ ;
+ static const char mta_closing[] =
+ "</Chaptering>\r\n"
+ "</ContentInformation>\r\n"
+ "</SEC:SECMeta>\r\n"
+ ;
+
+ char* buffer = 0;
+ if (ends_with(path, ".avi") || ends_with(path, ".mkv") || ends_with(path, ".mpg"))
+ {
+#ifdef THUMBNAIL_CREATION_SUPPORT
+ video_thumbnailer* vt=0;
+#endif
+ int percentage;
+ int length = 100*1024;
+ char* ptr;
+
+ buffer = malloc(length+1); /* one for the terminating zero */
+ if (!buffer)
+ return ""; /* fail (empty string!) */
+
+ ptr = buffer;
+ add_to_buffer(&buffer, &length, &ptr, mta_header, strlen(mta_header));
+
+#ifdef THUMBNAIL_CREATION_SUPPORT
+ vt = video_thumbnailer_create();
+ vt->thumbnail_image_type = Jpeg;
+#endif
+ /* Samsung accepts exactly 5 positions nothing more, nothing less */
+ for( percentage = 16; percentage < 95; percentage+=16 )
+ {
+#ifdef THUMBNAIL_CREATION_SUPPORT
+ int rc;
+ image_data* imgdata = video_thumbnailer_create_image_data();
+#endif
+ unsigned char* src = jpeg_chapter;
+ int size = sizeof(jpeg_chapter)-1;
+ int idx;
+
+ add_to_buffer(&buffer, &length, &ptr, mta_chapter_header, strlen(mta_chapter_header));
+
+#ifdef THUMBNAIL_CREATION_SUPPORT
+
+ vt->seek_percentage = percentage;
+
+ DPRINTF(E_DEBUG, L_METADATA, "generating %d%%-thumbnail for '%s'\n", percentage, path);
+ rc = video_thumbnailer_generate_thumbnail_to_buffer(vt, path, imgdata);
+ DPRINTF(E_DEBUG, L_METADATA, "rc: %d, thumbnail size=%d\n", rc, imgdata->image_data_size);
+
+ if (!rc && imgdata->image_data_size) {
+ src = imgdata->image_data_ptr;
+ size = imgdata->image_data_size;
+ }
+#endif
+
+ /* base 64 encoding of the image */
+ for(idx=0; idx < size; idx += 3)
+ {
+ unsigned char r1=0, r2=0, r3=0, b1=0, b2=0, b3=0, b4=0;
+
+ /* get 3 byte / 24 bit */
+ r1 = src[idx];
+
+ if (idx+1 < size)
+ r2 = src[idx+1];
+
+ if (idx+2 < size)
+ r3 = src[idx+2];
+
+ /* spread to 4 6-bit parts */
+ b1 = r1 >> 2;
+ b2 = ((r1 & 0x3) << 4) | (r2 >> 4);
+ b3 = ((r2 & 0xf) << 2) | (r3 >> 6);
+ b4 = r3 & 0x3f;
+
+ /* encode 6-bit output */
+ *ptr++ = encode_base64(b1);
+ *ptr++ = encode_base64(b2);
+
+ if (idx+1 < size)
+ *ptr++ = encode_base64(b3);
+ else
+ *ptr++ = '=';
+
+ if (idx+2 < size)
+ *ptr++ = encode_base64(b4);
+ else
+ *ptr++ = '=';
+
+ /* check buffer size, realloc if necessary */
+ if (ptr+2 > buffer+length) {
+ ptr -= (int)buffer;
+ adjust_buffer(&buffer, &length);
+ ptr += (int)buffer; /* rebase ptr */
+ }
+ }
+
+#ifdef THUMBNAIL_CREATION_SUPPORT
+ video_thumbnailer_destroy_image_data(imgdata);
+#endif
+ /* add time code for this thumbnail */
+ {
+ char tmp[sizeof(mta_chapter_closing)+10];
+ int pos = (duration * percentage) / 100;
+ snprintf(tmp, sizeof(mta_chapter_closing)+10, mta_chapter_closing, pos);
+ add_to_buffer(&buffer, &length, &ptr, tmp, strlen(tmp));
+ }
+ }
+#ifdef THUMBNAIL_CREATION_SUPPORT
+ video_thumbnailer_destroy(vt);
+#endif
+ add_to_buffer(&buffer, &length, &ptr, mta_closing, strlen(mta_closing));
+ *ptr = 0; /* terminate buffer! */
+ }
+ return buffer;
+}
+
+void
+SendResp_samsung_mta_file(struct upnphttp * h, char * object)
+{
+ char header[1500];
+ char sql_buf[256];
+ char **result;
+ int rows = 0;
+ char *path;
+ int duration = 0;
+ int sendfh = -1; /* default: no accessible mta file */
+ char *dash;
+
+ dash = strchr(object, '.');
+ if( dash )
+ *dash = '\0';
+
+ /* get path and duration of video */
+ sprintf(sql_buf, "select PATH, DURATION from DETAILS where ID = '%s';", object);
+ DPRINTF(E_DEBUG, L_HTTP, "Get MTA file SQL: %s\n", sql_buf);
+ sql_get_table(db, sql_buf, &result, &rows, NULL);
+ if( !rows )
+ {
+ DPRINTF(E_WARN, L_HTTP, "Object ID %s not found, responding ERROR 404\n", object);
+ Send404(h);
+ goto error;
+ }
+ path = result[2];
+ duration = ( 1 * atoi(rindex(result[3], ':')+1)) +
+ ( 60 * atoi(rindex(result[3], ':')-2)) +
+ (3600 * atoi(result[3]));
+
+ /* Is video (still) accessible? */
+ if( access(path, F_OK) == 0 ) {
+ time_t curtime = time(NULL);
+ char date[30];
+ char mta_path[PATH_MAX];
+ char* body = 0;
+ int size = 0;
+
+ snprintf(mta_path, sizeof(mta_path), "%s.mta", path);
+ DPRINTF(E_DEBUG, L_HTTP, "Looking for precalculated MTA file '%s'\n", mta_path);
+ if (access(mta_path, F_OK) == 0) { /* mta file accessible ? */
+ /* read MTA file from file*/
+ sendfh = open(mta_path, O_RDONLY);
+ if( sendfh < 0 ) {
+ DPRINTF(E_ERROR, L_HTTP, "Error opening precalculated MTA file '%s'\n", mta_path);
+ } else {
+ size = lseek(sendfh, 0, SEEK_END);
+ lseek(sendfh, 0, SEEK_SET);
+ DPRINTF(E_DEBUG, L_HTTP, "Found precalculated MTA file with size %d\n", size);
+ }
+ }
+
+ if (sendfh < 0) { /* nothing could be read ?*/
+ /* generate MTA file on-the-fly (may take some time especially on slow NAS!) */
+ DPRINTF(E_INFO, L_HTTP, "Generating MTA file on-the-fly for object (ID='%s') on [%s], duration=%d\n", object, path, duration);
+ body = generate_samsung_mta_file(path, duration);
+ if (body)
+ size = strlen(body);
+ if (!size)
+ free(body);
+ }
+
+ if (!size) { /* still no data */
+ Send404(h);
+ goto error; /* give up */
+ }
+
+ /* create corresponding HTTP header for MTA file request */
+ memset(header, 0, sizeof(header));
+ strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
+ sprintf(header, "HTTP/1.1 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_OP=00;DLNA.ORG_CI=0;\r\n"
+ "Server: " MINIDLNA_SERVER_STRING "\r\n",
+ size, date);
+
+ if( h->reqflags & FLAG_REALTIMEINFO )
+ strcat(header, "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n");
+
+ if( h->reqflags & FLAG_XFERBACKGROUND )
+ strcat(header, "transferMode.dlna.org: Background\r\n");
+ else
+ strcat(header, "transferMode.dlna.org: Interactive\r\n");
+
+ DPRINTF(E_INFO, L_HTTP, "%s", header);
+
+ strcat(header, "\r\n");
+
+ /* send header and if ok and not only HEAD request, send body */
+ if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && h->req_command != EHead )
+ {
+ if (sendfh < 0)
+ send_data(h, body, size, 0);
+ else {
+ send_file(h, sendfh, 0, size); /* always send complete file. No RANGEs */
+ close(sendfh);
+ }
+ }
+ free(body);
+ }
+ error:
+ sqlite3_free_table(result);
+}