* Fix some issues with ampersand escaping.

* Improve image date/camera metadata handling by not storing a date if none exists.
* Add preliminary TiVo video serving support.
This commit is contained in:
Justin Maggard
2009-04-16 19:20:16 +00:00
parent 0303f15fa4
commit 223df2111b
7 changed files with 362 additions and 211 deletions

View File

@ -42,7 +42,7 @@ SendRootContainer(struct upnphttp * h)
"<ContentType>x-container/tivo-server</ContentType>"
"<SourceFormat>x-container/folder</SourceFormat>"
"<TotalDuration>0</TotalDuration>"
"<TotalItems>2</TotalItems>"
"<TotalItems>3</TotalItems>"
"<Title>%s</Title>"
"</Details>"
"<ItemStart>0</ItemStart>"
@ -71,22 +71,49 @@ SendRootContainer(struct upnphttp * h)
"</Content>"
"</Links>"
"</Item>"
"</TiVoContainer>", friendly_name, friendly_name, friendly_name);
"<Item>"
"<Details>"
"<ContentType>x-container/tivo-videos</ContentType>"
"<SourceFormat>x-container/folder</SourceFormat>"
"<Title>Videos on %s</Title>"
"</Details>"
"<Links>"
"<Content>"
"<Url>/TiVoConnect?Command=QueryContainer&amp;Container=2</Url>"
"<ContentType>x-container/tivo-videos</ContentType>"
"</Content>"
"</Links>"
"</Item>"
"</TiVoContainer>", friendly_name, friendly_name, friendly_name, friendly_name);
BuildResp_upnphttp(h, resp, len);
free(resp);
SendResp_upnphttp(h);
}
char *
unescape_tag(char * tag)
{
modifyString(tag, "&amp;amp;", "&amp;", 0);
modifyString(tag, "&amp;amp;lt;", "&lt;", 0);
modifyString(tag, "&amp;lt;", "&lt;", 0);
modifyString(tag, "&amp;amp;gt;", "&gt;", 0);
modifyString(tag, "&amp;gt;", "&gt;", 0);
return tag;
}
#define FLAG_SEND_RESIZED 0x01
#define FLAG_NO_PARAMS 0x02
#define FLAG_VIDEO 0x04
int callback(void *args, int argc, char **argv, char **azColName)
{
struct Response *passed_args = (struct Response *)args;
char *id = argv[0], *class = argv[1], *detailID = argv[2], *size = argv[3], *title = argv[4], *duration = argv[5],
*bitrate = argv[6], *sampleFrequency = argv[7], *artist = argv[8], *album = argv[9], *genre = argv[10],
*comment = argv[11], *date = argv[12], *resolution = argv[13], *mime = argv[14];
*comment = argv[11], *date = argv[12], *resolution = argv[13], *mime = argv[14], *path = argv[15];
char str_buf[4096];
char **result;
int is_audio = 0;
int ret;
int flags = 0;
int ret = 0;
passed_args->total++;
if( passed_args->start >= passed_args->total )
@ -96,101 +123,167 @@ int callback(void *args, int argc, char **argv, char **azColName)
if( strncmp(class, "item", 4) == 0 )
{
unescape_tag(title);
if( strncmp(mime, "audio", 5) == 0 )
{
sprintf(str_buf, "<Item><Details>"
"<ContentType>audio/*</ContentType>"
"<SourceFormat>%s</SourceFormat>"
"<SourceSize>%s</SourceSize>"
"<SongTitle>%s</SongTitle>", mime, size, title);
strcat(passed_args->resp, str_buf);
flags |= FLAG_NO_PARAMS;
ret = sprintf(str_buf, "<Item><Details>"
"<ContentType>audio/*</ContentType>"
"<SourceFormat>%s</SourceFormat>"
"<SourceSize>%s</SourceSize>"
"<SongTitle>%s</SongTitle>", mime, size, title);
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
if( date )
{
sprintf(str_buf, "<AlbumYear>%.*s</AlbumYear>", 4, date);
strcat(passed_args->resp, str_buf);
ret = sprintf(str_buf, "<AlbumYear>%.*s</AlbumYear>", 4, date);
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
}
is_audio = 1;
}
else if( strcmp(mime, "image/jpeg") == 0 )
{
sprintf(str_buf, "<Item><Details>"
"<ContentType>image/*</ContentType>"
"<SourceFormat>image/jpeg</SourceFormat>"
"<SourceSize>%s</SourceSize>", size);
strcat(passed_args->resp, str_buf);
flags |= FLAG_SEND_RESIZED;
ret = sprintf(str_buf, "<Item><Details>"
"<ContentType>image/*</ContentType>"
"<SourceFormat>image/jpeg</SourceFormat>"
"<SourceSize>%s</SourceSize>", size);
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
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);
ret = sprintf(str_buf, "<CaptureDate>0x%X</CaptureDate>", (unsigned int)mktime(&tm));
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
}
if( comment ) {
sprintf(str_buf, "<Caption>%s</Caption>", comment);
strcat(passed_args->resp, str_buf);
ret = sprintf(str_buf, "<Caption>%s</Caption>", comment);
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
}
}
else if( strncmp(mime, "video", 5) == 0 )
{
flags |= FLAG_NO_PARAMS;
flags |= FLAG_VIDEO;
ret = sprintf(str_buf, "<Item><Details>"
"<ContentType>video/x-tivo-mpeg</ContentType>"
"<SourceFormat>%s</SourceFormat>"
"<SourceSize>%s</SourceSize>"
"<EpisodeTitle>%s</EpisodeTitle>", mime, size, title);
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
if( date )
{
struct tm tm;
memset(&tm, 0, sizeof(tm));
strptime(date, "%Y-%m-%dT%H:%M:%S", &tm);
ret = sprintf(str_buf, "<CaptureDate>0x%X</CaptureDate>", (unsigned int)mktime(&tm));
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
}
}
else
{
return 0;
}
sprintf(str_buf, "<Title>%s</Title>", modifyString(title, "&amp;amp;", "&amp;", 0));
strcat(passed_args->resp, str_buf);
ret = sprintf(str_buf, "<Title>%s</Title>", unescape_tag(title));
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
if( artist ) {
sprintf(str_buf, "<ArtistName>%s</ArtistName>", modifyString(artist, "&amp;amp;", "&amp;", 0));
strcat(passed_args->resp, str_buf);
ret = sprintf(str_buf, "<ArtistName>%s</ArtistName>", unescape_tag(artist));
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
}
if( album ) {
sprintf(str_buf, "<AlbumTitle>%s</AlbumTitle>", modifyString(album, "&amp;amp;", "&amp;", 0));
strcat(passed_args->resp, str_buf);
ret = sprintf(str_buf, "<AlbumTitle>%s</AlbumTitle>", unescape_tag(album));
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
}
if( genre ) {
sprintf(str_buf, "<MusicGenre>%s</MusicGenre>", modifyString(genre, "&amp;amp;", "&amp;", 0));
strcat(passed_args->resp, str_buf);
ret = sprintf(str_buf, "<MusicGenre>%s</MusicGenre>", unescape_tag(genre));
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
}
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);
ret = sprintf(str_buf, "<SourceWidth>%.*s</SourceWidth>"
"<SourceHeight>%s</SourceHeight>",
(index(resolution, 'x')-resolution), resolution, (rindex(resolution, 'x')+1));
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
}
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);
ret = sprintf(str_buf, "<Duration>%d</Duration>",
atoi(rindex(duration, '.')+1) + (1000*atoi(rindex(duration, ':')+1)) + (60000*atoi(rindex(duration, ':')-2)) + (3600000*atoi(duration)));
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
}
if( bitrate ) {
sprintf(str_buf, "<SourceBitRate>%s</SourceBitRate>", bitrate);
strcat(passed_args->resp, str_buf);
ret = sprintf(str_buf, "<SourceBitRate>%s</SourceBitRate>", bitrate);
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
}
if( sampleFrequency ) {
sprintf(str_buf, "<SourceSampleRate>%s</SourceSampleRate>", sampleFrequency);
strcat(passed_args->resp, str_buf);
ret = sprintf(str_buf, "<SourceSampleRate>%s</SourceSampleRate>", sampleFrequency);
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
}
ret = sprintf(str_buf, "</Details><Links><Content>"
"<ContentType>%s</ContentType>"
"<Url>/%s/%s.dat</Url>%s</Content>",
mime,
(flags & FLAG_SEND_RESIZED)?"Resized":"MediaItems", detailID,
(flags & FLAG_NO_PARAMS)?"<AcceptsParams>No</AcceptsParams>":"");
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
if( flags & FLAG_VIDEO )
{
char *name = basename(path);
char *esc_name = escape_tag(name);
ret = sprintf(str_buf, "<CustomIcon>"
"<ContentType>video/*</ContentType>"
"<Url>urn:tivo:image:save-until-i-delete-recording</Url>"
"</CustomIcon>"
"<Push><Container>Videos</Container></Push>"
"<File>%s</File> </Links>", esc_name?esc_name:name);
if( esc_name )
free(esc_name);
}
else
{
ret = sprintf(str_buf, "</Links>");
}
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 )
{
/* 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>", modifyString(title, "&amp;amp;", "&amp;", 0), result[1]);
strcat(passed_args->resp, str_buf);
sprintf(str_buf, "<Links><Content>"
"<Url>/TiVoConnect?Command=QueryContainer&amp;Container=%s</Url>"
"</Content></Links>", id);
ret = sprintf(str_buf, "<Item>"
"<Details>"
"<ContentType>x-container/folder</ContentType>"
"<SourceFormat>x-container/folder</SourceFormat>"
"<Title>%s</Title>"
"<TotalItems>%s</TotalItems>"
"</Details>"
"<Links>"
"<Content>"
"<Url>/TiVoConnect?Command=QueryContainer&amp;Container=%s</Url>"
"<ContentType>x-tivo-container/folder</ContentType>"
"</Content>"
"</Links>",
unescape_tag(title), result[1], id);
sqlite3_free_table(result);
}
strcat(passed_args->resp, str_buf);
strcat(passed_args->resp, "</Item>");
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
ret = sprintf(str_buf, "</Item>");
memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
passed_args->size += ret;
passed_args->returned++;
return 0;
@ -211,7 +304,7 @@ SendItemDetails(struct upnphttp * h, sqlite_int64 item)
args.requested = 1;
asprintf(&sql, "SELECT o.OBJECT_ID, o.CLASS, o.DETAIL_ID, d.SIZE, d.TITLE,"
" d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST, d.ALBUM,"
" d.GENRE, d.COMMENT, d.DATE, d.RESOLUTION, d.MIME "
" d.GENRE, d.COMMENT, d.DATE, d.RESOLUTION, d.MIME, d.PATH "
"from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
" where o.DETAIL_ID = %lld", item);
DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql);
@ -233,30 +326,40 @@ void
SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int itemCount, char * anchorItem,
int anchorOffset, int recurse, char * sortOrder, char * filter, unsigned long int randomSeed)
{
char * resp = malloc(1048576);
char * items = malloc(1048576);
char * resp = malloc(262144);
char *sql, *item, *saveptr;
char *zErrMsg = NULL;
char **result;
char *title;
char what[10], order[64]={0}, order2[64]={0}, myfilter[128]={0};
char what[10], order[64]={0}, order2[64]={0}, myfilter[256]={0};
char str_buf[1024];
char *which;
struct Response args;
int i, ret;
*items = '\0';
memset(&args, 0, sizeof(args));
memset(resp, 0, sizeof(262144));
args.resp = items;
args.resp = resp;
args.size = 1024;
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
asprintf(&title, "Unknown on %s", friendly_name);
switch( *objectID )
{
case '1':
asprintf(&title, "Music on %s", friendly_name);
break;
case '2':
asprintf(&title, "Videos on %s", friendly_name);
break;
case '3':
asprintf(&title, "Pictures on %s", friendly_name);
break;
default:
asprintf(&title, "Unknown on %s", friendly_name);
break;
}
}
else
{
@ -338,11 +441,11 @@ SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int ite
unhandled_order:
item = strtok_r(NULL, ",", &saveptr);
}
strcat(order, "DETAIL_ID ASC");
strcat(order, "TITLE ASC, DETAIL_ID ASC");
if( itemCount >= 0 )
strcat(order2, "DETAIL_ID ASC");
strcat(order2, "TITLE ASC, DETAIL_ID ASC");
else
strcat(order2, "DETAIL_ID DESC");
strcat(order2, "TITLE DESC, DETAIL_ID DESC");
}
}
else
@ -363,7 +466,8 @@ SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int ite
{
strcat(myfilter, " or ");
}
if( strcasecmp(item, "x-container/folder") == 0 )
if( (strcasecmp(item, "x-container/folder") == 0) ||
(strncasecmp(item, "x-tivo-container/", 17) == 0) )
{
strcat(myfilter, "CLASS glob 'container*'");
}
@ -375,6 +479,10 @@ SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int ite
{
strcat(myfilter, "MIME = 'audio/mpeg'");
}
else if( strncasecmp(item, "video", 5) == 0 )
{
strcat(myfilter, "MIME = 'video/mpeg' or MIME = 'video/x-tivo-mpeg'");
}
else
{
DPRINTF(E_INFO, L_TIVO, "Unhandled Filter [%s]\n", item);
@ -430,7 +538,7 @@ SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int ite
sqlite3Prng.isInit = 0;
sql = sqlite3_mprintf("SELECT o.OBJECT_ID, o.CLASS, o.DETAIL_ID, d.SIZE, d.TITLE,"
" d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST, d.ALBUM,"
" d.GENRE, d.COMMENT, d.DATE, d.RESOLUTION, d.MIME "
" d.GENRE, d.COMMENT, d.DATE, d.RESOLUTION, d.MIME, d.PATH "
"from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
" where %s and (%s)"
" group by DETAIL_ID"
@ -444,26 +552,27 @@ SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int ite
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,
title, args.start,
args.returned, items);
free(items);
ret = sprintf(str_buf, "<?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>",
(objectID[0]=='1' ? "music":"photos"),
args.total, title, args.start, args.returned);
args.resp = resp+1024-ret;
memcpy(args.resp, &str_buf, ret);
ret = sprintf(str_buf, "</TiVoContainer>");
memcpy(resp+args.size, &str_buf, ret+1);
args.size += ret;
args.size -= args.resp-resp;
free(title);
free(which);
BuildResp_upnphttp(h, resp, strlen(resp));
BuildResp_upnphttp(h, args.resp, args.size);
free(resp);
SendResp_upnphttp(h);
}
@ -538,7 +647,8 @@ ProcessTiVoCommand(struct upnphttp * h, const char * orig_path)
}
else if( strcasecmp(key, "Url") == 0 )
{
detailItem = strtoll(basename(val), NULL, 10);
if( val )
detailItem = strtoll(basename(val), NULL, 10);
}
else if( strcasecmp(key, "Format") == 0 )
{