From 223df2111b55598abc1f42047edf97ea7841157a Mon Sep 17 00:00:00 2001 From: Justin Maggard Date: Thu, 16 Apr 2009 19:20:16 +0000 Subject: [PATCH] * 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. --- inotify.c | 4 +- metadata.c | 74 +++++----- scanner.c | 176 +++++++++++++----------- tagutils/tagutils-mp3.c | 2 +- tivo_commands.c | 296 +++++++++++++++++++++++++++------------- utils.c | 18 ++- utils.h | 3 + 7 files changed, 362 insertions(+), 211 deletions(-) diff --git a/inotify.c b/inotify.c index 0d22a2f..c546830 100644 --- a/inotify.c +++ b/inotify.c @@ -425,7 +425,9 @@ inotify_insert_directory(int fd, char *name, const char * path) if( strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0 ) continue; - esc_name = modifyString(strdup(e->d_name), "&", "&amp;", 0); + esc_name = escape_tag(e->d_name); + if( !esc_name ) + esc_name = strdup(e->d_name); asprintf(&path_buf, "%s/%s", path, e->d_name); if( e->d_type == DT_DIR ) { diff --git a/metadata.c b/metadata.c index f3e1d47..5130dc1 100644 --- a/metadata.c +++ b/metadata.c @@ -92,6 +92,23 @@ dlna_timestamp_is_present(const char * filename) return 0; } +#ifdef TIVO_SUPPORT +int +is_tivo_file(const char * path) +{ + unsigned char buf[5]; + unsigned char hdr[5] = { 'T','i','V','o','\0' }; + int fd; + + /* read file header */ + fd = open(path, O_RDONLY); + read(fd, buf, 5); + close(fd); + + return( !memcmp(buf, hdr, 5) ); +} +#endif + /* This function taken from libavutil (ffmpeg), because it's not included with all versions of libavutil. */ int get_fourcc(const char *s) @@ -99,44 +116,23 @@ get_fourcc(const char *s) return (s[0]) + (s[1]<<8) + (s[2]<<16) + (s[3]<<24); } -char * -escape_tag(const char *tag) -{ - char *esc_tag = NULL; - - if( index(tag, '&') || index(tag, '<') || index(tag, '>') ) - { - esc_tag = strdup(tag); - esc_tag = modifyString(esc_tag, "&", "&amp;", 0); - esc_tag = modifyString(esc_tag, "<", "&lt;", 0); - esc_tag = modifyString(esc_tag, ">", "&gt;", 0); - } - - return esc_tag; -} - sqlite_int64 GetFolderMetadata(const char * name, const char * path, const char * artist, const char * genre, const char * album_art, const char * art_dlna_pn) { char * sql; - char * esc_name = NULL; int ret; - esc_name = escape_tag(name); sql = sqlite3_mprintf( "INSERT into DETAILS" " (TITLE, PATH, CREATOR, ARTIST, GENRE, ALBUM_ART, ART_DLNA_PN) " "VALUES" " ('%q', %Q, %Q, %Q, %Q, %lld, %Q);", - esc_name ? esc_name : name, - path, artist, artist, genre, + name, path, artist, artist, genre, album_art ? strtoll(album_art, NULL, 10) : 0, art_dlna_pn); if( sql_exec(db, sql) != SQLITE_OK ) ret = 0; else ret = sqlite3_last_insert_rowid(db); - if( esc_name ) - free(esc_name); sqlite3_free(sql); return ret; @@ -328,7 +324,8 @@ GetImageMetadata(const char * path, char * name) FILE *infile; int width=0, height=0, thumb=0; off_t size; - char date[64], make[32], model[64]; + char *date = NULL, *cam = NULL; + char make[32], model[64] = {'\0'}; char b[1024]; char *esc_name = NULL; struct stat file; @@ -337,9 +334,6 @@ GetImageMetadata(const char * path, char * name) metadata_t m; memset(&m, '\0', sizeof(metadata_t)); - date[0] = '\0'; - model[0] = '\0'; - //DEBUG DPRINTF(E_DEBUG, L_METADATA, "Parsing %s...\n", path); if ( stat(path, &file) == 0 ) size = file.st_size; @@ -371,7 +365,7 @@ GetImageMetadata(const char * path, char * name) tag = EXIF_TAG_DATE_TIME_ORIGINAL; e = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], tag); if( e || (e = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_DATE_TIME)) ) { - strncpy(date, exif_entry_get_value(e, b, sizeof(b)), sizeof(date)); + date = strdup(exif_entry_get_value(e, b, sizeof(b))); if( strlen(date) > 10 ) { date[4] = '-'; @@ -379,15 +373,12 @@ GetImageMetadata(const char * path, char * name) date[10] = 'T'; } else { - strcpy(date, "0000-00-00"); + free(date); + date = NULL; } } - else { - strcpy(date, "0000-00-00"); - } //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * date: %s\n", date); - model[0] = '\0'; tag = EXIF_TAG_MAKE; e = exif_content_get_entry (ed->ifd[EXIF_IFD_0], tag); if( e ) @@ -400,10 +391,9 @@ GetImageMetadata(const char * path, char * name) strncpy(model, exif_entry_get_value(e, b, sizeof(b)), sizeof(model)); if( !strcasestr(model, make) ) snprintf(model, sizeof(model), "%s %s", make, exif_entry_get_value(e, b, sizeof(b))); + cam = strdup(model); } } - if( !strlen(model) ) - strcpy(model, "Unknown"); //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * model: %s\n", model); if( ed->size ) @@ -452,8 +442,8 @@ GetImageMetadata(const char * path, char * name) sql = sqlite3_mprintf( "INSERT into DETAILS" " (PATH, TITLE, SIZE, DATE, RESOLUTION, THUMBNAIL, CREATOR, DLNA_PN, MIME) " "VALUES" - " (%Q, '%q', %llu, '%s', %Q, %d, '%q', %Q, %Q);", - path, esc_name?esc_name:name, size, date, m.resolution, thumb, model, m.dlna_pn, m.mime); + " (%Q, '%q', %llu, %Q, %Q, %d, %Q, %Q, %Q);", + path, esc_name?esc_name:name, size, date, m.resolution, thumb, cam, m.dlna_pn, m.mime); //DEBUG DPRINTF(E_DEBUG, L_METADATA, "SQL: %s\n", sql); if( sql_exec(db, sql) != SQLITE_OK ) { @@ -473,6 +463,10 @@ GetImageMetadata(const char * path, char * name) free(m.mime); if( esc_name ) free(esc_name); + if( date ) + free(date); + if( cam ) + free(cam); return ret; } @@ -870,7 +864,13 @@ GetVideoMetadata(const char * path, char * name) DPRINTF(E_WARN, L_METADATA, "Unhandled format: %s\n", ctx->iformat->name); } av_close_input_file(ctx); - +#ifdef TIVO_SUPPORT + if( ends_with(path, ".TiVo") && is_tivo_file(path) ) + { + free(m.mime); + asprintf(&m.mime, "video/x-tivo-mpeg"); + } +#endif sql = sqlite3_mprintf( "INSERT into DETAILS" " (PATH, SIZE, DURATION, DATE, CHANNELS, BITRATE, SAMPLERATE, RESOLUTION," " TITLE, DLNA_PN, MIME) " diff --git a/scanner.c b/scanner.c index 77ce139..45c0ad3 100644 --- a/scanner.c +++ b/scanner.c @@ -52,6 +52,9 @@ is_video(const char * file) ends_with(file, ".mts") || ends_with(file, ".m2ts") || ends_with(file, ".m2t") || ends_with(file, ".mkv") || ends_with(file, ".vob") || ends_with(file, ".ts") || + #ifdef TIVO_SUPPORT + ends_with(file, ".TiVo") || + #endif ends_with(file, ".flv") || ends_with(file, ".xvid")); } @@ -147,88 +150,96 @@ insert_container(const char * item, const char * rootParent, const char * refID, void insert_containers(const char * name, const char *path, const char * refID, const char * class, long unsigned int detailID) { - char sql_buf[128]; char *sql; char **result; int ret; int cols, row; - long long int container; - - sprintf(sql_buf, "SELECT * from DETAILS where ID = %lu", detailID); - ret = sql_get_table(db, sql_buf, &result, &row, &cols); + sqlite_int64 container; if( strstr(class, "imageItem") ) { - char *date = result[13+cols], *cam = result[16+cols]; - char date_taken[11]; + char *date = NULL, *cam = NULL; + char date_taken[13], camera[64]; static struct virtual_item last_date; static struct virtual_item last_cam; static struct virtual_item last_camdate; static sqlite_int64 last_all_objectID = 0; + asprintf(&sql, "SELECT DATE, CREATOR from DETAILS where ID = %lu", detailID); + ret = sql_get_table(db, sql, &result, &row, &cols); + free(sql); + if( ret == SQLITE_OK ) + { + date = result[2]; + cam = result[3]; + } + if( date ) { - if( *date == '0' ) - { - strcpy(date_taken, "Unknown"); - } - else - { - strncpy(date_taken, date, 10); - date_taken[10] = '\0'; - } - if( strcmp(last_date.name, date_taken) == 0 ) - { - last_date.objectID++; - //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID); - } - else - { - container = insert_container(date_taken, "3$12", NULL, "album.photoAlbum", NULL, NULL, NULL, NULL); - sprintf(last_date.parentID, "3$12$%llX", container>>32); - last_date.objectID = (int)container; - strcpy(last_date.name, date_taken); - //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID); - } - sql = sqlite3_mprintf( "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " - "VALUES" - " ('%s$%X', '%s', '%s', '%s', %lu, %Q)", - last_date.parentID, last_date.objectID, last_date.parentID, refID, class, detailID, name); - sql_exec(db, sql); - sqlite3_free(sql); - - if( cam ) - { - if( strcmp(cam, last_cam.name) != 0 ) - { - container = insert_container(cam, "3$13", NULL, "storageFolder", NULL, NULL, NULL, NULL); - sprintf(last_cam.parentID, "3$13$%llX", container>>32); - strncpy(last_cam.name, cam, 255); - last_camdate.name[0] = '\0'; - } - if( strcmp(last_camdate.name, date_taken) == 0 ) - { - last_camdate.objectID++; - //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last camdate item: %s/%s/%s/%X\n", cam, last_camdate.name, last_camdate.parentID, last_camdate.objectID); - } - else - { - container = insert_container(date_taken, last_cam.parentID, NULL, "album.photoAlbum", NULL, NULL, NULL, NULL); - sprintf(last_camdate.parentID, "%s$%llX", last_cam.parentID, container>>32); - last_camdate.objectID = (int)container; - strcpy(last_camdate.name, date_taken); - //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached camdate item: %s/%s/%s/%X\n", cam, last_camdate.name, last_camdate.parentID, last_camdate.objectID); - } - sql = sqlite3_mprintf( "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " - "VALUES" - " ('%s$%X', '%s', '%s', '%s', %lu, %Q)", - last_camdate.parentID, last_camdate.objectID, last_camdate.parentID, refID, class, detailID, name); - sql_exec(db, sql); - sqlite3_free(sql); - } + strncpy(date_taken, date, 10); + date_taken[10] = '\0'; } + else + { + strcpy(date_taken, "Unknown Date"); + } + if( strcmp(last_date.name, date_taken) == 0 ) + { + last_date.objectID++; + //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID); + } + else + { + container = insert_container(date_taken, "3$12", NULL, "album.photoAlbum", NULL, NULL, NULL, NULL); + sprintf(last_date.parentID, "3$12$%llX", container>>32); + last_date.objectID = (int)container; + strcpy(last_date.name, date_taken); + //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID); + } + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " + "VALUES" + " ('%s$%X', '%s', '%s', '%s', %lu, %Q)", + last_date.parentID, last_date.objectID, last_date.parentID, refID, class, detailID, name); + sql_exec(db, sql); + sqlite3_free(sql); + + if( cam ) + { + strncpy(camera, cam, 63); + camera[63] = '\0'; + } + else + { + strcpy(camera, "Unknown Camera"); + } + if( strcmp(camera, last_cam.name) != 0 ) + { + container = insert_container(camera, "3$13", NULL, "storageFolder", NULL, NULL, NULL, NULL); + sprintf(last_cam.parentID, "3$13$%llX", container>>32); + strncpy(last_cam.name, camera, 255); + last_camdate.name[0] = '\0'; + } + if( strcmp(last_camdate.name, date_taken) == 0 ) + { + last_camdate.objectID++; + //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last camdate item: %s/%s/%s/%X\n", camera, last_camdate.name, last_camdate.parentID, last_camdate.objectID); + } + else + { + container = insert_container(date_taken, last_cam.parentID, NULL, "album.photoAlbum", NULL, NULL, NULL, NULL); + sprintf(last_camdate.parentID, "%s$%llX", last_cam.parentID, container>>32); + last_camdate.objectID = (int)container; + strcpy(last_camdate.name, date_taken); + //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached camdate item: %s/%s/%s/%X\n", camera, last_camdate.name, last_camdate.parentID, last_camdate.objectID); + } + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " + "VALUES" + " ('%s$%X', '%s', '%s', '%s', %lu, %Q)", + last_camdate.parentID, last_camdate.objectID, last_camdate.parentID, refID, class, detailID, name); + sql_exec(db, sql); + sqlite3_free(sql); /* All Images */ if( !last_all_objectID ) { @@ -244,8 +255,18 @@ insert_containers(const char * name, const char *path, const char * refID, const } else if( strstr(class, "audioItem") ) { - char *artist = cols ? result[7+cols]:NULL, *album = cols ? result[8+cols]:NULL, *genre = cols ? result[9+cols]:NULL; - char *album_art = cols ? result[19+cols]:NULL, *art_dlna_pn = cols ? result[20+cols]:NULL; + asprintf(&sql, "SELECT ARTIST, ALBUM, GENRE, ALBUM_ART, ART_DLNA_PN from DETAILS where ID = %lu", detailID); + ret = sql_get_table(db, sql, &result, &row, &cols); + free(sql); + if( ret != SQLITE_OK ) + return; + if( !row ) + { + sqlite3_free_table(result); + return; + } + char *artist = result[5], *album = result[6], *genre = result[7]; + char *album_art = result[8], *art_dlna_pn = result[9]; static struct virtual_item last_album; static struct virtual_item last_artist; static struct virtual_item last_artistAlbum; @@ -287,7 +308,7 @@ insert_containers(const char * name, const char *path, const char * refID, const strcpy(last_artist.name, artist); last_artistAlbum.name[0] = '\0'; /* Add this file to the "- All Albums -" container as well */ - container = insert_container("- All Albums -", last_artist.parentID, NULL, "storageFolder", NULL, genre, NULL, NULL); + container = insert_container("- All Albums -", last_artist.parentID, NULL, "storageFolder", artist, genre, NULL, NULL); sprintf(last_artistAlbumAll.parentID, "%s$%llX", last_artist.parentID, container>>32); last_artistAlbumAll.objectID = (int)container; } @@ -396,6 +417,11 @@ insert_containers(const char * name, const char *path, const char * refID, const last_all_objectID++, refID, class, detailID, name); sql_exec(db, sql); sqlite3_free(sql); + return; + } + else + { + return; } sqlite3_free_table(result); } @@ -637,7 +663,7 @@ CreateDatabase(void) if( ret != SQLITE_OK ) goto sql_failed; ret = sql_exec(db, "CREATE TABLE SETTINGS (" - "UPDATE_ID INTEGER PRIMARY KEY" + "UPDATE_ID INTEGER PRIMARY KEY DEFAULT 0" ")"); if( ret != SQLITE_OK ) goto sql_failed; @@ -762,10 +788,7 @@ ScanDirectory(const char * dir, const char * parent, enum media_types type) for (i=0; i < n; i++) { sprintf(full_path, "%s/%s", dir, namelist[i]->d_name); - if( index(namelist[i]->d_name, '&') ) - { - name = modifyString(strdup(namelist[i]->d_name), "&", "&amp;", 0); - } + name = escape_tag(namelist[i]->d_name); if( namelist[i]->d_type == DT_DIR ) { insert_directory(name?name:namelist[i]->d_name, full_path, BROWSEDIR_ID, (parent ? parent:""), i+startID); @@ -778,10 +801,7 @@ ScanDirectory(const char * dir, const char * parent, enum media_types type) fileno++; } if( name ) - { free(name); - name = NULL; - } free(namelist[i]); } free(namelist); diff --git a/tagutils/tagutils-mp3.c b/tagutils/tagutils-mp3.c index f9c7f04..46d0ca6 100644 --- a/tagutils/tagutils-mp3.c +++ b/tagutils/tagutils-mp3.c @@ -696,7 +696,7 @@ _get_mp3fileinfo(char *file, struct song_metadata *psong) index++; if(first_check) { - DPRINTF(E_WARN, L_SCANNER, "Bad header... dropping back for full frame search\n"); + DPRINTF(E_INFO, L_SCANNER, "Bad header... dropping back for full frame search [%s]\n", psong->path); first_check = 0; fp_size = 0; break; diff --git a/tivo_commands.c b/tivo_commands.c index 1e6390f..b5baef9 100644 --- a/tivo_commands.c +++ b/tivo_commands.c @@ -42,7 +42,7 @@ SendRootContainer(struct upnphttp * h) "x-container/tivo-server" "x-container/folder" "0" - "2" + "3" "%s" "" "0" @@ -71,22 +71,49 @@ SendRootContainer(struct upnphttp * h) "" "" "" - "", friendly_name, friendly_name, friendly_name); + "" + "
" + "x-container/tivo-videos" + "x-container/folder" + "Videos on %s" + "
" + "" + "" + "/TiVoConnect?Command=QueryContainer&Container=2" + "x-container/tivo-videos" + "" + "" + "
" + "", 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;", "&", 0); + modifyString(tag, "&amp;lt;", "<", 0); + modifyString(tag, "&lt;", "<", 0); + modifyString(tag, "&amp;gt;", ">", 0); + modifyString(tag, "&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, "
" - "audio/*" - "%s" - "%s" - "%s", mime, size, title); - strcat(passed_args->resp, str_buf); + flags |= FLAG_NO_PARAMS; + ret = sprintf(str_buf, "
" + "audio/*" + "%s" + "%s" + "%s", mime, size, title); + memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); + passed_args->size += ret; if( date ) { - sprintf(str_buf, "%.*s", 4, date); - strcat(passed_args->resp, str_buf); + ret = sprintf(str_buf, "%.*s", 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, "
" - "image/*" - "image/jpeg" - "%s", size); - strcat(passed_args->resp, str_buf); + flags |= FLAG_SEND_RESIZED; + ret = sprintf(str_buf, "
" + "image/*" + "image/jpeg" + "%s", 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, "0x%X", (unsigned int)mktime(&tm)); - strcat(passed_args->resp, str_buf); + ret = sprintf(str_buf, "0x%X", (unsigned int)mktime(&tm)); + memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); + passed_args->size += ret; } if( comment ) { - sprintf(str_buf, "%s", comment); - strcat(passed_args->resp, str_buf); + ret = sprintf(str_buf, "%s", 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, "
" + "video/x-tivo-mpeg" + "%s" + "%s" + "%s", 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, "0x%X", (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, "%s", modifyString(title, "&amp;", "&", 0)); - strcat(passed_args->resp, str_buf); + ret = sprintf(str_buf, "%s", unescape_tag(title)); + memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); + passed_args->size += ret; if( artist ) { - sprintf(str_buf, "%s", modifyString(artist, "&amp;", "&", 0)); - strcat(passed_args->resp, str_buf); + ret = sprintf(str_buf, "%s", unescape_tag(artist)); + memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); + passed_args->size += ret; } if( album ) { - sprintf(str_buf, "%s", modifyString(album, "&amp;", "&", 0)); - strcat(passed_args->resp, str_buf); + ret = sprintf(str_buf, "%s", unescape_tag(album)); + memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); + passed_args->size += ret; } if( genre ) { - sprintf(str_buf, "%s", modifyString(genre, "&amp;", "&", 0)); - strcat(passed_args->resp, str_buf); + ret = sprintf(str_buf, "%s", unescape_tag(genre)); + memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); + passed_args->size += ret; } if( resolution ) { - sprintf(str_buf, "%.*s" - "%s", - (index(resolution, 'x')-resolution), resolution, (rindex(resolution, 'x')+1)); - strcat(passed_args->resp, str_buf); + ret = sprintf(str_buf, "%.*s" + "%s", + (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, "%d", - 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, "%d", + 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, "%s", bitrate); - strcat(passed_args->resp, str_buf); + ret = sprintf(str_buf, "%s", bitrate); + memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); + passed_args->size += ret; } if( sampleFrequency ) { - sprintf(str_buf, "%s", sampleFrequency); - strcat(passed_args->resp, str_buf); + ret = sprintf(str_buf, "%s", sampleFrequency); + memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); + passed_args->size += ret; + } + ret = sprintf(str_buf, "
" + "%s" + "/%s/%s.dat%s", + mime, + (flags & FLAG_SEND_RESIZED)?"Resized":"MediaItems", detailID, + (flags & FLAG_NO_PARAMS)?"No":""); + 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, "" + "video/*" + "urn:tivo:image:save-until-i-delete-recording" + "" + "Videos" + "%s ", esc_name?esc_name:name); + if( esc_name ) + free(esc_name); + } + else + { + ret = sprintf(str_buf, ""); } - sprintf(str_buf, "
/%s/%s.dat%s", - is_audio?"MediaItems":"Resized", detailID, is_audio?"No":""); } 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, "
" - "x-container/folder" - "x-container/folder"); - sprintf(str_buf, "%s" - "%s" - "
", modifyString(title, "&amp;", "&", 0), result[1]); - strcat(passed_args->resp, str_buf); - - sprintf(str_buf, "" - "/TiVoConnect?Command=QueryContainer&Container=%s" - "", id); + ret = sprintf(str_buf, "" + "
" + "x-container/folder" + "x-container/folder" + "%s" + "%s" + "
" + "" + "" + "/TiVoConnect?Command=QueryContainer&Container=%s" + "x-tivo-container/folder" + "" + "", + unescape_tag(title), result[1], id); sqlite3_free_table(result); } - strcat(passed_args->resp, str_buf); - strcat(passed_args->resp, "
"); + memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); + passed_args->size += ret; + ret = sprintf(str_buf, "
"); + 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, "\n" - "" - "
" - "x-container/tivo-%s" - "x-container/folder" - "%d" - "%s" - "
" - "%d" - "%d" - "%s" /* the actual items xml */ - "
", - (objectID[0]=='1' ? "music":"photos"), - args.total, - title, args.start, - args.returned, items); - free(items); + ret = sprintf(str_buf, "\n" + "" + "
" + "x-container/tivo-%s" + "x-container/folder" + "%d" + "%s" + "
" + "%d" + "%d", + (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, "
"); + 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 ) { diff --git a/utils.c b/utils.c index f190530..0762ba9 100644 --- a/utils.c +++ b/utils.c @@ -94,12 +94,28 @@ modifyString(char * string, const char * before, const char * after, short like) return string; } +char * +escape_tag(const char *tag) +{ + char *esc_tag = NULL; + + if( strchr(tag, '&') || strchr(tag, '<') || strchr(tag, '>') ) + { + esc_tag = strdup(tag); + esc_tag = modifyString(esc_tag, "&", "&amp;", 0); + esc_tag = modifyString(esc_tag, "<", "&lt;", 0); + esc_tag = modifyString(esc_tag, ">", "&gt;", 0); + } + + return esc_tag; +} + void strip_ext(char * name) { char * period; - period = rindex(name, '.'); + period = strrchr(name, '.'); if( period ) *period = '\0'; } diff --git a/utils.h b/utils.h index 4467d4e..ead5fa7 100644 --- a/utils.h +++ b/utils.h @@ -19,6 +19,9 @@ trim(char *str); char * modifyString(char * string, const char * before, const char * after, short like); +char * +escape_tag(const char *tag); + void strip_ext(char * name);