From 8cab1a28007f409a146281a18fc472c453398838 Mon Sep 17 00:00:00 2001 From: Justin Maggard Date: Fri, 30 Jan 2009 08:50:09 +0000 Subject: [PATCH] * Add some conditional statements to work with more ffmpeg library versions. * Fix a crash bug when generating a friendly name if the hostname does not contain a dot. * Implement folder browsing by media type. * Add initial DLNA-compliant music album art support. (Using a <=160x160 cover.jpg, or folder.jpg, etc) --- Makefile | 4 +- metadata.c | 110 +++++--------------- metadata.h | 2 +- minidlna.c | 5 +- scanner.c | 151 +++++++++++++++++++-------- sql.c | 15 +-- sql.h | 2 +- upnpdescgen.c | 26 +---- upnpglobalvars.h | 28 +++++ upnphttp.c | 261 ++++++++++++++++++++++++++++++----------------- upnphttp.h | 2 + upnpsoap.c | 123 +++++++++++++--------- upnpsoap.h | 7 ++ 13 files changed, 433 insertions(+), 303 deletions(-) diff --git a/Makefile b/Makefile index ec327d8..c517dce 100644 --- a/Makefile +++ b/Makefile @@ -28,12 +28,12 @@ BASEOBJS = minidlna.o upnphttp.o upnpdescgen.o upnpsoap.o \ upnpreplyparse.o minixml.o \ getifaddr.o daemonize.o upnpglobalvars.o \ options.o minissdp.o upnpevents.o \ - sql.o metadata.o scanner.o + sql.o utils.o metadata.o albumart.o scanner.o ALLOBJS = $(BASEOBJS) $(LNXOBJS) #LIBS = -liptc -LIBS = -lexif -ljpeg -ltag_c -lsqlite3 -lavformat -luuid #-lgd +LIBS = -lexif -ljpeg -ltag_c -lid3tag -lsqlite3 -lavformat -luuid #-lgd TESTUPNPDESCGENOBJS = testupnpdescgen.o upnpdescgen.o diff --git a/metadata.c b/metadata.c index 2af68c9..96d2a0a 100644 --- a/metadata.c +++ b/metadata.c @@ -37,6 +37,8 @@ #include "upnpglobalvars.h" #include "metadata.h" +#include "albumart.h" +#include "utils.h" #include "sql.h" #define FLAG_ARTIST 0x01 @@ -52,81 +54,6 @@ #define AAC 0x00000100 #define AAC_MULT5 0x00000200 -int -ends_with(const char * haystack, const char * needle) -{ - const char *found = strcasestr(haystack, needle); - return (found && found[strlen(needle)] == '\0'); -} - -char * -trim(char *str) -{ - if (!str) - return(NULL); - int i; - for (i=0; i <= strlen(str) && (isspace(str[i]) || str[i] == '"'); i++) { - str++; - } - for (i=(strlen(str)-1); i >= 0 && (isspace(str[i]) || str[i] == '"'); i--) { - str[i] = '\0'; - } - return str; -} - -char * -modifyString(char * string, const char * before, const char * after, short like) -{ - int oldlen, newlen, chgcnt = 0; - char *s, *p, *t; - - oldlen = strlen(before); - newlen = strlen(after); - if( newlen > oldlen ) - { - s = string; - while( (p = strstr(s, before)) ) - { - chgcnt++; - s = p+oldlen; - } - string = realloc(string, strlen(string)+((newlen-oldlen)*chgcnt)+1); - } - - s = string; - while( s ) - { - p = strcasestr(s, before); - if( !p ) - return string; - if( like ) - { - t = p+oldlen; - while( isspace(*t) ) - t++; - if( *t == '"' ) - while( *++t != '"' ) - continue; - memmove(t+1, t, strlen(t)+1); - *t = '%'; - } - memmove(p + newlen, p + oldlen, strlen(p + oldlen) + 1); - memcpy(p, after, newlen); - s = p + newlen; - } - if( newlen < oldlen ) - string = realloc(string, strlen(string)+1); - - return string; -} - -void -strip_ext(char * name) -{ - if( rindex(name, '.') ) - *rindex(name, '.') = '\0'; -} - /* This function shamelessly copied from libdlna */ #define MPEG_TS_SYNC_CODE 0x47 #define MPEG_TS_PACKET_LENGTH_DLNA 192 /* prepends 4 bytes to TS packet */ @@ -162,16 +89,17 @@ dlna_timestamp_is_present(const char * filename) } sqlite_int64 -GetFolderMetadata(const char * name, const char * artist) +GetFolderMetadata(const char * name, const char * artist, const char * genre, const char * album_art) { char * sql; int ret; sql = sqlite3_mprintf( "INSERT into DETAILS" - " (TITLE, ARTIST) " + " (TITLE, CREATOR, ARTIST, GENRE, ALBUM_ART) " "VALUES" - " ('%q', %Q);", - name, artist); + " ('%q', %Q, %Q, %Q, %lld);", + name, artist, artist, genre, + album_art ? strtoll(album_art, NULL, 10) : 0); if( sql_exec(db, sql) != SQLITE_OK ) ret = 0; @@ -307,9 +235,9 @@ GetAudioMetadata(const char * path, char * name) sql = sqlite3_mprintf( "INSERT into DETAILS" " (PATH, SIZE, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE," - " TITLE, CREATOR, ARTIST, ALBUM, GENRE, COMMENT, TRACK, DLNA_PN, MIME) " + " TITLE, CREATOR, ARTIST, ALBUM, GENRE, COMMENT, TRACK, DLNA_PN, MIME, ALBUM_ART) " "VALUES" - " (%Q, %llu, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, '%s', '%s');", + " (%Q, %llu, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, '%s', '%s', %lld);", path, size, duration, taglib_audioproperties_channels(properties), taglib_audioproperties_bitrate(properties)*1024, taglib_audioproperties_samplerate(properties), @@ -320,8 +248,8 @@ GetAudioMetadata(const char * path, char * name) genre, comment, taglib_tag_track(tag), - dlna_pn, mime); - + dlna_pn, mime, + find_album_art(path) ); taglib_tag_free_strings(); taglib_file_free(audio_file); @@ -552,7 +480,7 @@ GetVideoMetadata(const char * path, char * name) int audio_profile = 0; ts_timestamp_t ts_timestamp = NONE; int duration, hours, min, sec, ms; - aac_object_type_t aac_type = 0; + aac_object_type_t aac_type = AAC_INVALID; metadata_t m; memset(&m, '\0', sizeof(m)); date[0] = '\0'; @@ -597,9 +525,15 @@ GetVideoMetadata(const char * path, char * name) case CODEC_ID_AAC: if( !ctx->streams[audio_stream]->codec->extradata_size || !ctx->streams[audio_stream]->codec->extradata ) + { printf("No AAC type\n"); + } else - aac_type = ctx->streams[audio_stream]->codec->extradata[0] >> 3; + { + uint8_t data; + memcpy(&data, ctx->streams[audio_stream]->codec->extradata, 1); + aac_type = data >> 3; + } switch( aac_type ) { /* AAC Low Complexity variants */ @@ -642,15 +576,21 @@ GetVideoMetadata(const char * path, char * name) else if ( ctx->streams[audio_stream]->codec->bit_rate <= 385000 ) audio_profile = WMA_FULL; break; + #ifdef CODEC_ID_WMAPRO case CODEC_ID_WMAPRO: audio_profile = WMA_PRO; break; + #endif case CODEC_ID_MP2: audio_profile = MP2; break; default: if( (ctx->streams[audio_stream]->codec->codec_id >= CODEC_ID_PCM_S16LE) && + #ifdef CODEC_ID_PCM_F64LE (ctx->streams[audio_stream]->codec->codec_id <= CODEC_ID_PCM_F64LE) ) + #else + (ctx->streams[audio_stream]->codec->codec_id <= CODEC_ID_PCM_S24DAUD) ) + #endif audio_profile = PCM; else printf("Unhandled audio codec [%X]\n", ctx->streams[audio_stream]->codec->codec_id); diff --git a/metadata.h b/metadata.h index 1594527..d6c0518 100644 --- a/metadata.h +++ b/metadata.h @@ -44,7 +44,7 @@ char * modifyString(char * string, const char * before, const char * after, short like); sqlite_int64 -GetFolderMetadata(const char * name, const char * artist); +GetFolderMetadata(const char * name, const char * artist, const char * genre, const char * album_art); sqlite_int64 GetAudioMetadata(const char * path, char * name); diff --git a/minidlna.c b/minidlna.c index 3cc2840..d66dbd0 100644 --- a/minidlna.c +++ b/minidlna.c @@ -211,12 +211,15 @@ parselanaddr(struct lan_addr_s * lan_addr, const char * str) void getfriendlyname(char * buf, int len) { + char * dot = NULL; char * hn = calloc(1, 256); if( gethostname(hn, 256) == 0 ) { strncpy(buf, hn, len-1); buf[len] = '\0'; - *strstr(buf, ".") = '\0'; + dot = index(buf, '.'); + if( dot ) + *dot = '\0'; } else { diff --git a/scanner.c b/scanner.c index eabd284..b70280c 100644 --- a/scanner.c +++ b/scanner.c @@ -28,6 +28,7 @@ #include "upnpglobalvars.h" #include "metadata.h" +#include "utils.h" #include "sql.h" #include "scanner.h" @@ -57,24 +58,24 @@ is_image(const char * file) } long long int -insert_container(const char * tmpTable, const char * item, const char * rootParent, const char *subParent, const char *class, const char *artist) +insert_container(const char * tmpTable, const char * item, const char * rootParent, const char *subParent, + const char *class, const char *artist, const char *genre, const char *album_art) { char **result; char *sql; int cols, rows, ret; - char *zErrMsg = NULL; int parentID = 0, objectID = 0; sqlite_int64 detailID; sql = sqlite3_mprintf("SELECT * from %s where ITEM = '%q' and SUBITEM = '%q'", tmpTable, item, subParent); - ret = sql_get_table(db, sql, &result, &rows, &cols, &zErrMsg); + ret = sql_get_table(db, sql, &result, &rows, &cols); sqlite3_free(sql); if( cols ) { sscanf(result[4], "%X", &parentID); sqlite3_free_table(result); sql = sqlite3_mprintf("SELECT OBJECT_ID, max(ID) from OBJECTS where PARENT_ID = '%s$%X'", rootParent, parentID); - ret = sql_get_table(db, sql, &result, 0, &cols, &zErrMsg); + ret = sql_get_table(db, sql, &result, 0, &cols); sqlite3_free(sql); if( result[2] && (sscanf(rindex(result[2], '$')+1, "%X", &objectID) == 1) ) { @@ -85,7 +86,7 @@ insert_container(const char * tmpTable, const char * item, const char * rootPare { sqlite3_free_table(result); sql = sqlite3_mprintf("SELECT OBJECT_ID, max(ID) from OBJECTS where PARENT_ID = '%s'", rootParent); - sql_get_table(db, sql, &result, &rows, &cols, &zErrMsg); + sql_get_table(db, sql, &result, &rows, &cols); sqlite3_free(sql); if( result[2] && (sscanf(rindex(result[2], '$')+1, "%X", &parentID) == 1) ) { @@ -95,7 +96,7 @@ insert_container(const char * tmpTable, const char * item, const char * rootPare { parentID = 0; } - detailID = GetFolderMetadata(item, artist); + detailID = GetFolderMetadata(item, artist, genre, album_art); sql = sqlite3_mprintf( "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME) " "VALUES" @@ -120,13 +121,12 @@ insert_containers(const char * name, const char *path, const char * refID, const char **result; int ret; int cols, row; - char *zErrMsg = NULL; long long int container; int parentID; int objectID = -1; sprintf(sql_buf, "SELECT * from DETAILS where ID = %lu", detailID); - ret = sql_get_table(db, sql_buf, &result, &row, &cols, &zErrMsg); + ret = sql_get_table(db, sql_buf, &result, &row, &cols); if( strstr(class, "imageItem") ) { @@ -143,7 +143,7 @@ insert_containers(const char * name, const char *path, const char * refID, const { strncpy(date_taken, date, 10); } - container = insert_container("DATES", date_taken, "3$12", NULL, "album.photoAlbum", NULL); + container = insert_container("DATES", date_taken, "3$12", NULL, "album.photoAlbum", NULL, NULL, NULL); parentID = container>>32; objectID = container; sql = sqlite3_mprintf( "INSERT into OBJECTS" @@ -156,12 +156,12 @@ insert_containers(const char * name, const char *path, const char * refID, const } if( cam && date ) { - container = insert_container("CAMS", cam, "3$13", NULL, "storageFolder", NULL); + container = insert_container("CAMS", cam, "3$13", NULL, "storageFolder", NULL, NULL, NULL); parentID = container>>32; //objectID = container; char parent[64]; sprintf(parent, "3$13$%X", parentID); - long long int subcontainer = insert_container("CAMDATE", date_taken, parent, cam, "storageFolder", NULL); + long long int subcontainer = insert_container("CAMDATE", date_taken, parent, cam, "storageFolder", NULL, NULL, NULL); int subParentID = subcontainer>>32; int subObjectID = subcontainer; sql = sqlite3_mprintf( "INSERT into OBJECTS" @@ -183,7 +183,8 @@ 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 *artist = cols ? result[7+cols]:NULL, *album = cols ? result[8+cols]:NULL; + char *genre = cols ? result[9+cols]:NULL, *album_art = cols ? result[19+cols]:NULL; static char last_artist[1024] = "0"; static int last_artist_parentID, last_artist_objectID; static char last_album[1024]; @@ -202,7 +203,7 @@ insert_containers(const char * name, const char *path, const char * refID, const else { strcpy(last_artist, artist); - container = insert_container("ARTISTS", artist, "1$6", NULL, "person.musicArtist", NULL); + container = insert_container("ARTISTS", artist, "1$6", NULL, "person.musicArtist", NULL, genre, NULL); parentID = container>>32; objectID = container; last_artist_objectID = objectID; @@ -226,7 +227,7 @@ insert_containers(const char * name, const char *path, const char * refID, const else { strcpy(last_album, album); - container = insert_container("ALBUMS", album, "1$7", NULL, "album.musicAlbum", artist); + container = insert_container("ALBUMS", album, "1$7", NULL, "album.musicAlbum", artist, genre, album_art); parentID = container>>32; objectID = container; last_album_objectID = objectID; @@ -250,7 +251,7 @@ insert_containers(const char * name, const char *path, const char * refID, const else { strcpy(last_genre, genre); - container = insert_container("GENRES", genre, "1$5", NULL, "genre.musicGenre", NULL); + container = insert_container("GENRES", genre, "1$5", NULL, "genre.musicGenre", NULL, NULL, NULL); parentID = container>>32; objectID = container; last_genre_objectID = objectID; @@ -273,33 +274,82 @@ insert_containers(const char * name, const char *path, const char * refID, const sql_exec(db, sql); sqlite3_free(sql); } + else if( strstr(class, "videoItem") ) + { + static int last_all_objectID = 0; + + /* All Music */ + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " + "VALUES" + " ('2$8$%X', '2$8', '%s', '%s', %lu, %Q)", + last_all_objectID++, refID, class, detailID, name); + sql_exec(db, sql); + sqlite3_free(sql); + } sqlite3_free_table(result); } int -insert_directory(const char * name, const char * path, const char * parentID, int objectID) +insert_directory(const char * name, const char * path, const char * base, const char * parentID, int objectID) { char * sql; - int ret, i; + int ret, found = 0; sqlite_int64 detailID; char * refID = NULL; char class[] = "container.storageFolder"; - const char * const base[] = { BROWSEDIR_ID, MUSIC_DIR_ID, VIDEO_DIR_ID, IMAGE_DIR_ID, 0 }; + char * id_buf = NULL; + char * parent_buf = NULL; + char **result; + char *dir = strdup(path); - detailID = GetFolderMetadata(name, NULL); - for( i=0; base[i]; i++ ) + if( strcmp(base, BROWSEDIR_ID) != 0 ) + asprintf(&refID, "%s%s$%X", BROWSEDIR_ID, parentID, objectID); + + if( refID ) { - sql = sqlite3_mprintf( "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) " - "VALUES" - " ('%s%s$%X', '%s%s', %Q, '%lld', '%s', '%q')", - base[i], parentID, objectID, base[i], parentID, refID, detailID, class, name); - //DEBUG printf("SQL: %s\n", sql); - ret = sql_exec(db, sql); + dir = dirname(dir); + asprintf(&id_buf, "%s%s$%X", base, parentID, objectID); + asprintf(&parent_buf, "%s%s", base, parentID); + while( !found ) + { + sql = sqlite3_mprintf("SELECT count(OBJECT_ID) from OBJECTS where OBJECT_ID = '%s'", id_buf); + if( (sql_get_table(db, sql, &result, NULL, NULL) == SQLITE_OK) && atoi(result[1]) ) + break; + /* Does not exist. Need to create, and may need to create parents also */ + sql = sqlite3_mprintf("SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s'", refID); + if( (sql_get_table(db, sql, &result, NULL, NULL) == SQLITE_OK) && atoi(result[1]) ) + { + detailID = atoi(result[1]); + } + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) " + "VALUES" + " ('%s', '%s', %Q, '%lld', '%s', '%q')", + id_buf, parent_buf, refID, detailID, class, rindex(dir, '/')+1); + sql_exec(db, sql); + if( rindex(id_buf, '$') ) + *rindex(id_buf, '$') = '\0'; + if( rindex(parent_buf, '$') ) + *rindex(parent_buf, '$') = '\0'; + if( rindex(refID, '$') ) + *rindex(refID, '$') = '\0'; + dir = dirname(dir); + } sqlite3_free(sql); - if( !i ) - asprintf(&refID, "%s%s$%X", base[0], parentID, objectID); + free(refID); + return 1; } + + detailID = GetFolderMetadata(name, NULL, NULL, NULL); + sql = sqlite3_mprintf( "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) " + "VALUES" + " ('%s%s$%X', '%s%s', %Q, '%lld', '%s', '%q')", + base, parentID, objectID, base, parentID, refID, detailID, class, name); + //DEBUG printf("SQL: %s\n", sql); + ret = sql_exec(db, sql); + sqlite3_free(sql); if( refID ) free(refID); @@ -314,11 +364,16 @@ insert_file(char * name, const char * path, const char * parentID, int object) char objectID[64]; unsigned long int detailID = 0; char base[8]; + char * typedir_parentID; + int typedir_objectID; static long unsigned int fileno = 0; printf("Scanned %lu files...\r", fileno++); fflush(stdout); sprintf(objectID, "%s%s$%X", BROWSEDIR_ID, parentID, object); + typedir_parentID = strdup(parentID); + sscanf(rindex(typedir_parentID, '$')+1, "%X", &typedir_objectID); + *rindex(typedir_parentID, '$') = '\0'; if( is_image(name) ) { @@ -342,6 +397,8 @@ insert_file(char * name, const char * path, const char * parentID, int object) if( !detailID ) return -1; + insert_directory(name, path, base, typedir_parentID, typedir_objectID); + sql = sqlite3_mprintf( "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, NAME) " "VALUES" @@ -349,20 +406,18 @@ insert_file(char * name, const char * path, const char * parentID, int object) objectID, BROWSEDIR_ID, parentID, class, detailID, name); //DEBUG printf("SQL: %s\n", sql); sql_exec(db, sql); - sqlite3_free(sql); - #if 0 + sql = sqlite3_mprintf( "INSERT into OBJECTS" - " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, PATH, NAME) " + " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" - " ('%s%s$%X', '%s%s', '%s', '%s', %lu, '%q', '%q')", - base, parentID, object, base, parentID, objectID, class, detailID, path, name); + " ('%s%s$%X', '%s%s', '%s', '%s', %lu, '%q')", + base, parentID, object, base, parentID, objectID, class, detailID, name); //DEBUG printf("SQL: %s\n", sql); sql_exec(db, sql); sqlite3_free(sql); - #else + insert_containers(name, path, objectID, class, detailID); - #endif - return -1; + return 0; } int @@ -426,7 +481,15 @@ create_database(void) "THUMBNAIL BOOL DEFAULT 0, " "CREATOR TEXT, " "DLNA_PN TEXT, " - "MIME TEXT" + "MIME TEXT, " + "ALBUM_ART INTEGER DEFAULT 0" + ");"); + if( ret != SQLITE_OK ) + goto sql_failed; + ret = sql_exec(db, "CREATE TABLE ALBUM_ART ( " + "ID INTEGER PRIMARY KEY AUTOINCREMENT, " + "PATH TEXT NOT NULL, " + "EMBEDDED BOOL DEFAULT 0" ");"); if( ret != SQLITE_OK ) goto sql_failed; @@ -435,7 +498,7 @@ create_database(void) sprintf(sql_buf, "INSERT into OBJECTS (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME)" " values " "('%s', '%s', %lld, 'container.storageFolder', '%s')", - containers[i], containers[i+1], GetFolderMetadata(containers[i+2], NULL), containers[i+2]); + containers[i], containers[i+1], GetFolderMetadata(containers[i+2], NULL, NULL, NULL), containers[i+2]); ret = sql_exec(db, sql_buf); if( ret != SQLITE_OK ) goto sql_failed; @@ -453,6 +516,7 @@ create_database(void) sql_exec(db, "create INDEX IDX_OBJECTS_CLASS ON OBJECTS(CLASS);"); sql_exec(db, "create INDEX IDX_DETAILS_PATH ON DETAILS(PATH);"); sql_exec(db, "create INDEX IDX_DETAILS_ID ON DETAILS(ID);"); + sql_exec(db, "create INDEX IDX_ALBUM_ART ON ALBUM_ART(ID);"); sql_failed: @@ -523,7 +587,7 @@ ScanDirectory(const char * dir, const char * parent) } if( S_ISDIR(entry.st_mode) ) { - insert_directory(name?name:namelist[i]->d_name, full_path, (parent ? parent:""), i); + insert_directory(name?name:namelist[i]->d_name, full_path, BROWSEDIR_ID, (parent ? parent:""), i); sprintf(parent_id, "%s$%X", (parent ? parent:""), i); ScanDirectory(full_path, parent_id); } @@ -537,8 +601,11 @@ ScanDirectory(const char * dir, const char * parent) free(namelist[i]); } free(namelist); - chdir(dirname((char*)dir)); - if( !parent ) + if( parent ) + { + chdir(dirname((char*)dir)); + } + else { printf("Scanning \"%s\" finished!\n", dir); #if USE_FORK diff --git a/sql.c b/sql.c index 9883987..bcf51f1 100644 --- a/sql.c +++ b/sql.c @@ -25,7 +25,6 @@ sql_exec(sqlite3 * db, const char * sql) char *errMsg = NULL; //DEBUG printf("SQL: %s\n", sql); - //ret = sqlite3_exec(db, sql, 0, 0, &errMsg); ret = sqlite3_exec(db, sql, 0, 0, &errMsg); if( ret != SQLITE_OK ) { @@ -37,16 +36,18 @@ sql_exec(sqlite3 * db, const char * sql) } int -sql_get_table(sqlite3 *db, const char *zSql, char ***pazResult, int *pnRow, int *pnColumn, char **pzErrmsg) +sql_get_table(sqlite3 *db, const char *sql, char ***pazResult, int *pnRow, int *pnColumn) { - //DEBUG printf("SQL: %s\n", zSql); int ret; - ret = sqlite3_get_table(db, zSql, pazResult, pnRow, pnColumn, pzErrmsg); + char *errMsg = NULL; + //DEBUG printf("SQL: %s\n", sql); + + ret = sqlite3_get_table(db, sql, pazResult, pnRow, pnColumn, &errMsg); if( ret != SQLITE_OK ) { - fprintf(stderr, "SQL ERROR [%s]\n%s\n", *pzErrmsg, zSql); - if (*pzErrmsg) - sqlite3_free(*pzErrmsg); + fprintf(stderr, "SQL ERROR %d [%s]\n%s\n", ret, errMsg, sql); + if (errMsg) + sqlite3_free(errMsg); } return ret; } diff --git a/sql.h b/sql.h index 7e15511..fc57610 100644 --- a/sql.h +++ b/sql.h @@ -16,6 +16,6 @@ int sql_exec(sqlite3 * db, const char * sql); int -sql_get_table(sqlite3 *db, const char *zSql, char ***pazResult, int *pnRow, int *pnColumn, char **pzErrmsg); +sql_get_table(sqlite3 *db, const char *zSql, char ***pazResult, int *pnRow, int *pnColumn); #endif diff --git a/upnpdescgen.c b/upnpdescgen.c index d3f0849..f9d1b16 100644 --- a/upnpdescgen.c +++ b/upnpdescgen.c @@ -84,31 +84,7 @@ static const char * const upnpallowedvalues[] = "IN_PROGRESS", "STOPPED", 0, - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," /* 44 */ - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_HD_NA_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AC3_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01," - "http-get:*:audio/x-ms-wma:*," - "http-get:*:audio/wav:*," - "http-get:*:audio/mp4:*," - "http-get:*:audio/x-aiff:*," - "http-get:*:audio/x-flac:*," - "http-get:*:application/ogg:*," - "http-get:*:image/jpeg:*," - "http-get:*:image/gif:*," - "http-get:*:audio/x-mpegurl:*," - "http-get:*:video/mpeg:*," - "http-get:*:video/x-msvideo:*," - "http-get:*:video/avi:*," - "http-get:*:video/mpeg2:*," - "http-get:*:video/dvd:*," - "http-get:*:video/x-ms-wmv:*", + RESOURCE_PROTOCOL_INFO_VALUES, /* 44 */ 0, "", /* 46 */ 0 diff --git a/upnpglobalvars.h b/upnpglobalvars.h index 32a5eb9..82fd3ca 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -16,6 +16,34 @@ #define USE_FORK 1 #define DB_VERSION 1 +#define RESOURCE_PROTOCOL_INFO_VALUES \ + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," \ + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ + "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ + "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_HD_NA_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ + "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_HD_NA;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ + "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AC3_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ + "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \ + "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01," \ + "http-get:*:audio/x-ms-wma:*," \ + "http-get:*:audio/wav:*," \ + "http-get:*:audio/mp4:*," \ + "http-get:*:audio/x-aiff:*," \ + "http-get:*:audio/x-flac:*," \ + "http-get:*:application/ogg:*," \ + "http-get:*:image/jpeg:*," \ + "http-get:*:image/gif:*," \ + "http-get:*:audio/x-mpegurl:*," \ + "http-get:*:video/mpeg:*," \ + "http-get:*:video/x-msvideo:*," \ + "http-get:*:video/avi:*," \ + "http-get:*:video/mpeg2:*," \ + "http-get:*:video/dvd:*," \ + "http-get:*:video/x-ms-wmv:*" + /* file to store all leases */ #ifdef ENABLE_LEASEFILE extern const char * lease_file; diff --git a/upnphttp.c b/upnphttp.c index 9b4df54..3e44169 100644 --- a/upnphttp.c +++ b/upnphttp.c @@ -33,6 +33,7 @@ #include #include "upnpglobalvars.h" +#include "utils.h" #include #include #if 0 //JPEG_RESIZE @@ -175,7 +176,8 @@ intervening space) by either an integer or the keyword "infinite". */ h->reqflags |= FLAG_RANGE; h->req_RangeEnd = atoll(index(p+6, '-')+1); h->req_RangeStart = atoll(p+6); -printf("Range Start-End: %lld - %lld\n", h->req_RangeStart, h->req_RangeEnd?h->req_RangeEnd:-1); + printf("Range Start-End: %lld - %lld\n", + h->req_RangeStart, h->req_RangeEnd?h->req_RangeEnd:-1); } } else if(strncasecmp(line, "Host", 4)==0) @@ -532,7 +534,7 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) HttpVer[i] = '\0'; syslog(LOG_INFO, "HTTP REQUEST : %s %s (%s)", HttpCommand, HttpUrl, HttpVer); - //DEBUG printf("HTTP REQUEST:\n%.*s\n", h->req_buflen, h->req_buf); + printf("HTTP REQUEST:\n%.*s\n", h->req_buflen, h->req_buf); ParseHttpHeaders(h); /* see if we need to wait for remaining data */ @@ -607,6 +609,11 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) SendResp_thumbnail(h, HttpUrl+12); CloseSocket_upnphttp(h); } + else if(strncmp(HttpUrl, "/AlbumArt/", 10) == 0) + { + SendResp_albumArt(h, HttpUrl+10); + CloseSocket_upnphttp(h); + } #if 0 //JPEG_RESIZE else if(strncmp(HttpUrl, "/Resized/", 7) == 0) { @@ -833,7 +840,7 @@ void SendResp_upnphttp(struct upnphttp * h) { int n; -printf("HTTP RESPONSE:\n%.*s\n", h->res_buflen, h->res_buf); + printf("HTTP RESPONSE:\n%.*s\n", h->res_buflen, h->res_buf); n = send(h->socket, h->res_buf, h->res_buflen, 0); if(n<0) { @@ -847,6 +854,127 @@ printf("HTTP RESPONSE:\n%.*s\n", h->res_buflen, h->res_buf); } } +int +send_data(struct upnphttp * h, char * header, size_t size) +{ + int n; + + n = send(h->socket, header, size, 0); + if(n<0) + { + syslog(LOG_ERR, "send(res_buf): %m"); + } + else if(n < h->res_buflen) + { + /* TODO : handle correctly this case */ + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", + n, h->res_buflen); + } + else + { + return 0; + } + return 1; +} + +void +send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset) +{ + off_t send_size; + + while( offset < end_offset ) + { + send_size = ( ((end_offset - offset) < MAX_BUFFER_SIZE) ? (end_offset - offset + 1) : MAX_BUFFER_SIZE); + off_t ret = sendfile(h->socket, sendfd, &offset, send_size); + if( ret == -1 ) + { + printf("sendfile error :: error no. %d [%s]\n", errno, strerror(errno)); + if( errno == 32 || errno == 9 || errno == 54 || errno == 104 ) + break; + } + /*else + { + printf("sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset); + }*/ + } +} + +void +SendResp_albumArt(struct upnphttp * h, char * object) +{ + char header[1500]; + char sql_buf[256]; + char **result; + int rows; + char *path; + char date[30]; + time_t curtime = time(NULL); + off_t offset = 0, size; + int sendfh; + + memset(header, 0, 1500); + + if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) + { + syslog(LOG_NOTICE, "Hey, you can't specify transferMode as Streaming with an image!"); + Send406(h); + return; + } + + strip_ext(object); + sprintf(sql_buf, "SELECT PATH from ALBUM_ART where ID = %s", object); + sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0); + if( !rows ) + { + syslog(LOG_NOTICE, "ALBUM_ART ID %s not found, responding ERROR 404", object); + Send404(h); + goto error; + } + path = result[1]; + printf("Serving album art ID: %s [%s]\n", object, path); + + if( access(path, F_OK) == 0 ) + { + strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); + + sendfh = open(path, O_RDONLY); + if( sendfh < 0 ) { + printf("Error opening %s\n", path); + goto error; + } + size = lseek(sendfh, 0, SEEK_END); + lseek(sendfh, 0, SEEK_SET); + + sprintf(header, "HTTP/1.1 200 OK\r\n" + "Content-Type: image/jpeg\r\n" + "Content-Length: %lld\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/1.0\r\n", + size, date); + + if( h->reqflags & FLAG_XFERBACKGROUND ) + { + strcat(header, "transferMode.dlna.org: Background\r\n\r\n"); + } + else //if( h->reqflags & FLAG_XFERINTERACTIVE ) + { + strcat(header, "transferMode.dlna.org: Interactive\r\n\r\n"); + } + + + if( (send_data(h, header, strlen(header)) == 0) && (h->req_command != EHead) && (sendfh > 0) ) + { + send_file(h, sendfh, offset, size); + } + close(sendfh); + } + error: + sqlite3_free_table(result); +} + void SendResp_thumbnail(struct upnphttp * h, char * object) { @@ -854,9 +982,9 @@ SendResp_thumbnail(struct upnphttp * h, char * object) char sql_buf[256]; char **result; int rows; + char *path; char date[30]; time_t curtime = time(NULL); - int n; ExifData *ed; ExifLoader *l; @@ -869,7 +997,8 @@ SendResp_thumbnail(struct upnphttp * h, char * object) return; } - sprintf(sql_buf, "SELECT d.PATH from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object); + strip_ext(object); + sprintf(sql_buf, "SELECT PATH from DETAILS where ID = '%s'", object); sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0); if( !rows ) { @@ -877,20 +1006,23 @@ SendResp_thumbnail(struct upnphttp * h, char * object) Send404(h); goto error; } - printf("Serving up thumbnail for ObjectId: %s [%s]\n", object, result[1]); + path = result[1]; + printf("Serving thumbnail for ObjectId: %s [%s]\n", object, path); - if( access(result[1], F_OK) == 0 ) + if( access(path, F_OK) == 0 ) { strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); l = exif_loader_new(); - exif_loader_write_file(l, result[1]); + exif_loader_write_file(l, path); ed = exif_loader_get_data(l); exif_loader_unref(l); - if( !ed->size ) + if( !ed || !ed->size ) { Send404(h); + if( ed ) + exif_data_unref(ed); goto error; } sprintf(header, "HTTP/1.1 200 OK\r\n" @@ -900,47 +1032,21 @@ SendResp_thumbnail(struct upnphttp * h, char * object) "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", + "Server: RAIDiator/4.1, UPnP/1.0, MiniDLNA/1.0\r\n", ed->size, date); if( h->reqflags & FLAG_XFERBACKGROUND ) { - strcat(header, "transferMode.dlna.org: Background\r\n"); + strcat(header, "transferMode.dlna.org: Background\r\n\r\n"); } else //if( h->reqflags & FLAG_XFERINTERACTIVE ) { - strcat(header, "transferMode.dlna.org: Interactive\r\n"); - } - strcat(header, "\r\n"); - - n = send(h->socket, header, strlen(header), 0); - if(n<0) - { - syslog(LOG_ERR, "send(res_buf): %m"); - } - else if(n < h->res_buflen) - { - /* TODO : handle correctly this case */ - syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", - n, h->res_buflen); + strcat(header, "transferMode.dlna.org: Interactive\r\n\r\n"); } - if( h->req_command == EHead ) + if( (send_data(h, header, strlen(header)) == 0) && (h->req_command != EHead) ) { - exif_data_unref(ed); - goto error; - } - - n = send(h->socket, ed->data, ed->size, 0); - if(n<0) - { - syslog(LOG_ERR, "send(res_buf): %m"); - } - else if(n < h->res_buflen) - { - /* TODO : handle correctly this case */ - syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", - n, h->res_buflen); + send_data(h, (char *)ed->data, ed->size); } exif_data_unref(ed); } @@ -1067,15 +1173,17 @@ SendResp_dlnafile(struct upnphttp * h, char * object) int rows; char date[30]; time_t curtime = time(NULL); - off_t total, send_size; + off_t total, offset, size; char *path, *mime, *dlna; + int sendfh; #if USE_FORK pid_t newpid = 0; #endif memset(header, 0, 1500); - sprintf(sql_buf, "SELECT d.PATH, d.MIME, d.DLNA_PN from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where OBJECT_ID = '%s'", object); + strip_ext(object); + sprintf(sql_buf, "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%s'", object); sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0); if( !rows ) { @@ -1093,11 +1201,11 @@ SendResp_dlnafile(struct upnphttp * h, char * object) path = result[3]; mime = result[4]; dlna = result[5]; - printf("ObjectId: %s [%s]\n", object, path); + printf("Serving DetailID: %s [%s]\n", object, path); if( h->reqflags & FLAG_XFERSTREAMING ) { - if( strncmp(mime, "imag", 4) == 0 ) + if( strncmp(mime, "image", 5) == 0 ) { syslog(LOG_NOTICE, "Hey, you can't specify transferMode as Streaming with an image!"); Send406(h); @@ -1112,7 +1220,7 @@ SendResp_dlnafile(struct upnphttp * h, char * object) Send400(h); goto error; } - if( strncmp(mime, "imag", 4) != 0 ) + if( strncmp(mime, "image", 5) != 0 ) { syslog(LOG_NOTICE, "Hey, you can't specify transferMode as Interactive without an image!"); Send406(h); @@ -1121,15 +1229,17 @@ SendResp_dlnafile(struct upnphttp * h, char * object) } strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); - off_t offset = h->req_RangeStart; - int sendfh = open(path, O_RDONLY); + offset = h->req_RangeStart; + sendfh = open(path, O_RDONLY); if( sendfh < 0 ) { - printf("Error opening %s\n", result[2]); + printf("Error opening %s\n", path); goto error; } - off_t size = lseek(sendfh, 0, SEEK_END); + size = lseek(sendfh, 0, SEEK_END); lseek(sendfh, 0, SEEK_SET); + sprintf(header, "HTTP/1.1 20%c OK\r\n" + "Content-Type: %s\r\n", (h->reqflags & FLAG_RANGE ? '6' : '0'), mime); if( h->reqflags & FLAG_RANGE ) { if( !h->req_RangeEnd ) @@ -1149,10 +1259,7 @@ SendResp_dlnafile(struct upnphttp * h, char * object) goto error; } - sprintf(hdr_buf, "HTTP/1.1 206 OK\r\n" - "Content-Type: %s\r\n", mime); - strcpy(header, hdr_buf); - if( h->req_RangeEnd && (h->req_RangeEnd < size) ) + if( h->req_RangeEnd < size ) { total = h->req_RangeEnd - h->req_RangeStart + 1; sprintf(hdr_buf, "Content-Length: %llu\r\n" @@ -1172,11 +1279,7 @@ SendResp_dlnafile(struct upnphttp * h, char * object) { h->req_RangeEnd = size; total = size; - sprintf(hdr_buf, "%s 200 OK\r\n" - "Content-Type: %s\r\n" - "Content-Length: %llu\r\n", - "HTTP/1.1", mime, total); - //h->HttpVer, mime, total); + sprintf(hdr_buf, "Content-Length: %llu\r\n", total); } strcat(header, hdr_buf); @@ -1186,16 +1289,20 @@ SendResp_dlnafile(struct upnphttp * h, char * object) } else if( h->reqflags & FLAG_XFERBACKGROUND ) { - if( strncmp(mime, "imag", 4) == 0 ) + if( strncmp(mime, "image", 5) == 0 ) strcat(header, "transferMode.dlna.org: Background\r\n"); } else //if( h->reqflags & FLAG_XFERINTERACTIVE ) { if( (strncmp(mime, "video", 5) == 0) || (strncmp(mime, "audio", 5) == 0) ) + { strcat(header, "transferMode.dlna.org: Streaming\r\n"); + } else + { strcat(header, "transferMode.dlna.org: Interactive\r\n"); + } } sprintf(hdr_buf, "Accept-Ranges: bytes\r\n" @@ -1207,42 +1314,12 @@ SendResp_dlnafile(struct upnphttp * h, char * object) date, dlna); strcat(header, hdr_buf); - int n; - n = send(h->socket, header, strlen(header), 0); - if(n<0) + if( (send_data(h, header, strlen(header)) == 0) && (h->req_command != EHead) && (sendfh > 0) ) { - syslog(LOG_ERR, "send(res_buf): %m"); - } - else if(n < h->res_buflen) - { - /* TODO : handle correctly this case */ - syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", - n, h->res_buflen); + send_file(h, sendfh, offset, h->req_RangeEnd); } + close(sendfh); - if( h->req_command == EHead ) - { - close(sendfh); - } - else if( sendfh > 0 ) - { - while( offset < h->req_RangeEnd ) - { - send_size = (( (h->req_RangeEnd - offset) < MAX_BUFFER_SIZE ) ? (h->req_RangeEnd - offset + 1) : MAX_BUFFER_SIZE); - off_t ret = sendfile(h->socket, sendfh, &offset, send_size); - if( ret == -1 ) - { - printf("sendfile error :: error no. %d [%s]\n", errno, strerror(errno)); - if( errno == 32 || errno == 9 || errno == 54 || errno == 104 ) - break; - } - /*else - { - printf("sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset); - }*/ - } - close(sendfh); - } error: sqlite3_free_table(result); #if USE_FORK diff --git a/upnphttp.h b/upnphttp.h index ffb5a60..52c6c93 100644 --- a/upnphttp.h +++ b/upnphttp.h @@ -121,6 +121,8 @@ BuildResp2_upnphttp(struct upnphttp * h, int respcode, void SendResp_upnphttp(struct upnphttp *); +void +SendResp_albumArt(struct upnphttp *, char * url); void SendResp_resizedimg(struct upnphttp *, char * url); void diff --git a/upnpsoap.c b/upnpsoap.c index 50caf40..ec17cdc 100644 --- a/upnpsoap.c +++ b/upnpsoap.c @@ -107,31 +107,7 @@ GetProtocolInfo(struct upnphttp * h, const char * action) "" "" - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_HD_NA_ISO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AC3_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," - "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01," - "http-get:*:audio/x-ms-wma:*," - "http-get:*:audio/wav:*," - "http-get:*:audio/mp4:*," - "http-get:*:audio/x-aiff:*," - "http-get:*:audio/x-flac:*," - "http-get:*:application/ogg:*," - "http-get:*:image/jpeg:*," - "http-get:*:image/gif:*," - "http-get:*:audio/x-mpegurl:*," - "http-get:*:video/mpeg:*," - "http-get:*:video/x-msvideo:*," - "http-get:*:video/avi:*," - "http-get:*:video/mpeg2:*," - "http-get:*:video/dvd:*," - "http-get:*:video/x-ms-wmv:*" + RESOURCE_PROTOCOL_INFO_VALUES "" "" ""; @@ -231,10 +207,10 @@ GetCurrentConnectionInfo(struct upnphttp * h, const char * action) static int callback(void *args, int argc, char **argv, char **azColName) { struct Response { char *resp; int returned; int requested; int total; char *filter; } *passed_args = (struct Response *)args; - char *id = argv[1], *parent = argv[2], *refID = argv[3], *class = argv[4], *size = argv[9], *title = argv[10], + 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]; + *resolution = argv[21], *tn = argv[22], *creator = argv[23], *dlna_pn = argv[24], *mime = argv[25], *album_art = argv[26]; char dlna_buf[64]; char str_buf[4096]; char **result; @@ -292,6 +268,14 @@ static int callback(void *args, int argc, char **argv, char **azColName) 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")) ) { + sprintf(str_buf, "<upnp:albumArtURI %s" + ">http://%s:5555/AlbumArt/%s.jpg</upnp:albumArtURI>", + (!passed_args->filter || strstr(passed_args->filter, "upnp:albumArtURI@dlna:profileID")) ? + "dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlnaorg:metadata-1-0/\"" : "", + lan_addr[0].str, 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")) ) { @@ -319,9 +303,9 @@ static int callback(void *args, int argc, char **argv, char **azColName) strcat(passed_args->resp, str_buf); } sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\">" - "http://%s:5555/MediaItems/%s" + "http://%s:5555/MediaItems/%s.dat" "</res>", - mime, dlna_buf, lan_addr[0].str, id); + mime, dlna_buf, lan_addr[0].str, detailID); #if 0 //JPEG_RESIZE if( dlna_pn && (strncmp(dlna_pn, "JPEG_LRG", 8) == 0) ) { strcat(passed_args->resp, str_buf); @@ -336,9 +320,9 @@ static int callback(void *args, int argc, char **argv, char **azColName) strcat(passed_args->resp, str_buf); strcat(passed_args->resp, "<res "); sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\">" - "http://%s:5555/Thumbnails/%s" + "http://%s:5555/Thumbnails/%s.dat" "</res>", - mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, id); + mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, detailID); } strcat(passed_args->resp, str_buf); } @@ -346,8 +330,8 @@ static int callback(void *args, int argc, char **argv, char **azColName) } else if( strncmp(class, "container", 9) == 0 ) { - sprintf(str_buf, "SELECT count(*) from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where PARENT_ID = '%s' order by d.TRACK, d.TITLE, o.NAME;", id); - ret = sqlite3_get_table(db, str_buf, &result, 0, 0, 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")) @@ -368,9 +352,27 @@ static int callback(void *args, int argc, char **argv, char **azColName) } sprintf(str_buf, ">" "<dc:title>%s</dc:title>" - "<upnp:class>object.%s</upnp:class>" - "</container>", + "<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")) ) { + sprintf(str_buf, "<upnp:albumArtURI dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlnaorg:metadata-1-0/\"" + ">http://%s:5555/AlbumArt/%s.jpg</upnp:albumArtURI>", lan_addr[0].str, album_art); + strcat(passed_args->resp, str_buf); + } + sprintf(str_buf, "</container>"); sqlite3_free_table(result); } strcat(passed_args->resp, str_buf); @@ -385,7 +387,8 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) "" "" - "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\">\n"; + "<DIDL-Lite" + CONTENT_DIRECTORY_SCHEMAS; static const char resp1[] = "</DIDL-Lite>"; static const char resp2[] = "0"; @@ -410,6 +413,10 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) memset(str_buf, '\0', sizeof(str_buf)); memset(&args, 0, sizeof(args)); strcpy(resp, resp0); + /* See if we need to include DLNA namespace reference */ + if( (strlen(Filter) <= 1) || strstr(Filter, "dlna") ) + strcat(resp, DLNA_NAMESPACE); + strcat(resp, ">\n"); args.total = StartingIndex; args.returned = 0; @@ -467,13 +474,14 @@ SearchContentDirectory(struct upnphttp * h, const char * action) "" "" - "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\">\n"; + "<DIDL-Lite" + CONTENT_DIRECTORY_SCHEMAS; static const char resp1[] = "</DIDL-Lite>"; static const char resp2[] = "0"; char *resp = calloc(1, 1048576); char *zErrMsg = 0; - char sql_buf[4096]; + char *sql; char str_buf[4096]; int ret; struct Response { char *resp; int returned; int requested; int total; char *filter; } args; @@ -486,8 +494,8 @@ SearchContentDirectory(struct upnphttp * h, const char * action) char * Filter = GetValueFromNameValueList(&data, "Filter"); char * SearchCriteria = GetValueFromNameValueList(&data, "SearchCriteria"); char * SortCriteria = GetValueFromNameValueList(&data, "SortCriteria"); + char * newSearchCriteria = NULL; - memset(str_buf, '\0', sizeof(str_buf)); memset(&args, 0, sizeof(args)); args.total = 0; @@ -503,6 +511,10 @@ SearchContentDirectory(struct upnphttp * h, const char * action) if( SortCriteria ) printf("Asked for SortCriteria: %s\n", SortCriteria); strcpy(resp, resp0); + /* See if we need to include DLNA namespace reference */ + if( (strlen(Filter) <= 1) || strstr(Filter, "dlna") ) + strcat(resp, DLNA_NAMESPACE); + strcat(resp, ">\n"); if( !Filter ) { @@ -513,10 +525,11 @@ SearchContentDirectory(struct upnphttp * h, const char * action) if( strlen(Filter) > 1 ) args.filter = Filter; if( strcmp(ContainerID, "0") == 0 ) - *ContainerID = '%'; + *ContainerID = '*'; if( !SearchCriteria ) { - asprintf(&SearchCriteria, "1 = 1"); + asprintf(&newSearchCriteria, "1 = 1"); + SearchCriteria = newSearchCriteria; } else { @@ -533,25 +546,41 @@ SearchContentDirectory(struct upnphttp * h, const char * action) SearchCriteria = modifyString(SearchCriteria, "exists false", "is NULL", 0); SearchCriteria = modifyString(SearchCriteria, "@refID", "REF_ID", 0); SearchCriteria = modifyString(SearchCriteria, "object.", "", 0); + #if 0 + if( strstr(SearchCriteria, "&") ) + { + newSearchCriteria = modifyString(strdup(SearchCriteria), "&", "&amp;", 0); + SearchCriteria = newSearchCriteria; + } + #endif } printf("Translated SearchCriteria: %s\n", SearchCriteria); args.resp = resp; - sprintf(sql_buf, "SELECT * from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" - " where OBJECT_ID like '%s$%%' and (%s) order by d.TRACK, d.TITLE, o.NAME limit %d, -1;", - ContainerID, SearchCriteria, StartingIndex); - printf("Search SQL: %s\n", sql_buf); - ret = sqlite3_exec(db, sql_buf, callback, (void *) &args, &zErrMsg); + sql = sqlite3_mprintf("SELECT * from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" + " where OBJECT_ID glob '%s$*' and (%s) " + "%z" + " order by d.TRACK, d.TITLE, o.NAME limit %d, -1;", + ContainerID, SearchCriteria, + (*ContainerID == '*') ? NULL : + sqlite3_mprintf("UNION ALL SELECT * from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" + " where OBJECT_ID = '%s' and (%s) ", ContainerID, SearchCriteria), + StartingIndex); + printf("Search SQL: %s\n", sql); + ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); if( ret != SQLITE_OK ){ - printf("SQL error: %s\n", zErrMsg); + printf("SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql); sqlite3_free(zErrMsg); } + sqlite3_free(sql); strcat(resp, resp1); sprintf(str_buf, "\n%u\n%u\n", args.returned, args.total); strcat(resp, str_buf); strcat(resp, resp2); BuildSendAndCloseSoapResp(h, resp, strlen(resp)); ClearNameValueList(&data); + if( newSearchCriteria ) + free(newSearchCriteria); free(resp); } diff --git a/upnpsoap.h b/upnpsoap.h index ae5b031..258478c 100644 --- a/upnpsoap.h +++ b/upnpsoap.h @@ -7,6 +7,13 @@ #ifndef __UPNPSOAP_H__ #define __UPNPSOAP_H__ +#define CONTENT_DIRECTORY_SCHEMAS \ + " xmlns:dc=\"http://purl.org/dc/elements/1.1/\"" \ + " xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\"" \ + " xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\"" +#define DLNA_NAMESPACE \ + " xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"" + /* ExecuteSoapAction(): * this method executes the requested Soap Action */ void