diff --git a/minidlnatypes.h b/minidlnatypes.h index ec4f72b..fbb63f9 100644 --- a/minidlnatypes.h +++ b/minidlnatypes.h @@ -42,6 +42,7 @@ enum client_types { EDenonReceiver, EFreeBox, EPopcornHour, + EMediaRoom, EStandardDLNA150 = 100 }; diff --git a/scanner.c b/scanner.c index 0bc17c2..67d9e0d 100644 --- a/scanner.c +++ b/scanner.c @@ -539,24 +539,27 @@ int CreateDatabase(void) { int ret, i; - const char * containers[] = { "0","-1", "root", - "1", "0", _("Music"), - "1$4", "1", _("All Music"), - "1$5", "1", _("Genre"), - "1$6", "1", _("Artist"), - "1$7", "1", _("Album"), - MUSIC_DIR_ID, "1", _("Folders"), - MUSIC_PLIST_ID, "1", _("Playlists"), - "2", "0", _("Video"), - "2$8", "2", _("All Video"), - VIDEO_DIR_ID, "2", _("Folders"), - "3", "0", _("Pictures"), - "3$11", "3", _("All Pictures"), - "3$12", "3", _("Date Taken"), - "3$13", "3", _("Camera"), - IMAGE_DIR_ID, "3", _("Folders"), - "64", "0", _("Browse Folders"), - 0 }; + const char * containers[] = { "0","-1", "root", + MUSIC_ID, "0", _("Music"), + MUSIC_ALL_ID, "1", _("All Music"), + MUSIC_GENRE_ID, "1", _("Genre"), + MUSIC_ARTIST_ID, "1", _("Artist"), + MUSIC_ALBUM_ID, "1", _("Album"), + MUSIC_DIR_ID, "1", _("Folders"), + MUSIC_PLIST_ID, "1", _("Playlists"), + + VIDEO_ID, "0", _("Video"), + VIDEO_ALL_ID, "2", _("All Video"), + VIDEO_DIR_ID, "2", _("Folders"), + + IMAGE_ID, "0", _("Pictures"), + IMAGE_ALL_ID, "3", _("All Pictures"), + IMAGE_DATE_ID, "3", _("Date Taken"), + IMAGE_CAMERA_ID, "3", _("Camera"), + IMAGE_DIR_ID, "3", _("Folders"), + + BROWSEDIR_ID, "0", _("Browse Folders"), + 0 }; ret = sql_exec(db, "CREATE TABLE OBJECTS ( " "ID INTEGER PRIMARY KEY AUTOINCREMENT, " diff --git a/scanner.h b/scanner.h index 26db9da..954da5c 100644 --- a/scanner.h +++ b/scanner.h @@ -10,11 +10,38 @@ #ifndef __SCANNER_H__ #define __SCANNER_H__ -#define BROWSEDIR_ID "64" -#define MUSIC_DIR_ID "1$14" -#define MUSIC_PLIST_ID "1$F" -#define VIDEO_DIR_ID "2$15" -#define IMAGE_DIR_ID "3$16" +/* Try to be generally PlaysForSure compatible by using similar IDs */ +#define BROWSEDIR_ID "64" + +#define MUSIC_ID "1" +#define MUSIC_ALL_ID "1$4" +#define MUSIC_GENRE_ID "1$5" +#define MUSIC_ARTIST_ID "1$6" +#define MUSIC_ALBUM_ID "1$7" +#define MUSIC_PLIST_ID "1$F" +#define MUSIC_DIR_ID "1$14" +#define MUSIC_CONTRIB_ARTIST_ID "1$100" +#define MUSIC_ALBUM_ARTIST_ID "1$107" +#define MUSIC_COMPOSER_ID "1$108" +#define MUSIC_RATING_ID "1$101" + +#define VIDEO_ID "2" +#define VIDEO_ALL_ID "2$8" +#define VIDEO_GENRE_ID "2$9" +#define VIDEO_ACTOR_ID "2$A" +#define VIDEO_SERIES_ID "2$E" +#define VIDEO_PLIST_ID "2$10" +#define VIDEO_DIR_ID "2$15" +#define VIDEO_RATING_ID "2$200" + +#define IMAGE_ID "3" +#define IMAGE_ALL_ID "3$B" +#define IMAGE_DATE_ID "3$C" +#define IMAGE_ALBUM_ID "3$D" +#define IMAGE_CAMERA_ID "3$D2" // PlaysForSure == Keyword +#define IMAGE_PLIST_ID "3$11" +#define IMAGE_DIR_ID "3$16" +#define IMAGE_RATING_ID "3$300" extern int valid_cache; diff --git a/sql.c b/sql.c index ac854f7..ce208d2 100644 --- a/sql.c +++ b/sql.c @@ -16,7 +16,9 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include +#include #include + #include "sql.h" #include "log.h" @@ -120,4 +122,79 @@ sql_get_int_field(sqlite3 *db, const char *fmt, ...) sqlite3_finalize(stmt); return ret; } - + +char * +sql_get_text_field(void *dbh, const char *fmt, ...) +{ + va_list ap; + int counter, result, len; + char *sql; + char *str; + sqlite3_stmt *stmt; + + va_start(ap, fmt); + + if (dbh == NULL) + { + DPRINTF(E_WARN, L_DB_SQL, "%s: dbh is NULL", __func__); + return NULL; + } + + sql = sqlite3_vmprintf(fmt, ap); + + //DPRINTF(E_DEBUG, L_DB_SQL, "sql: %s\n", sql); + + switch (sqlite3_prepare_v2(dbh, sql, -1, &stmt, NULL)) + { + case SQLITE_OK: + break; + default: + DPRINTF(E_ERROR, L_DB_SQL, "prepare failed: %s\n%s\n", sqlite3_errmsg, sql); + sqlite3_free(sql); + return NULL; + } + sqlite3_free(sql); + + for (counter = 0; + ((result = sqlite3_step(stmt)) == SQLITE_BUSY || result == SQLITE_LOCKED) && counter < 2; + counter++) + { + /* While SQLITE_BUSY has a built in timeout, + * SQLITE_LOCKED does not, so sleep */ + if (result == SQLITE_LOCKED) + sleep(1); + } + + switch (result) + { + case SQLITE_DONE: + /* no rows returned */ + str = NULL; + break; + + case SQLITE_ROW: + if (sqlite3_column_type(stmt, 0) == SQLITE_NULL) + { + str = NULL; + break; + } + + len = sqlite3_column_bytes(stmt, 0); + if ((str = sqlite3_malloc(len + 1)) == NULL) + { + DPRINTF(E_ERROR, L_DB_SQL, "malloc failed"); + break; + } + + strncpy(str, (char *)sqlite3_column_text(stmt, 0), len + 1); + break; + + default: + DPRINTF(E_WARN, L_DB_SQL, "%s: step failed: %s", __func__, sqlite3_errmsg); + str = NULL; + break; + } + + sqlite3_finalize(stmt); + return str; +} diff --git a/sql.h b/sql.h index 9373e2b..7775848 100644 --- a/sql.h +++ b/sql.h @@ -21,4 +21,7 @@ sql_get_table(sqlite3 *db, const char *zSql, char ***pazResult, int *pnRow, int int sql_get_int_field(sqlite3 *db, const char *fmt, ...); +char * +sql_get_text_field(void *dbh, const char *fmt, ...); + #endif diff --git a/upnphttp.c b/upnphttp.c index ea568b8..4d51a74 100644 --- a/upnphttp.c +++ b/upnphttp.c @@ -240,6 +240,7 @@ intervening space) by either an integer or the keyword "infinite". */ { h->req_client = EXbox; h->reqflags |= FLAG_MIME_AVI_AVI; + h->reqflags |= FLAG_MS_PFS; } else if(strncmp(p, "PLAYSTATION", 11)==0) { @@ -269,6 +270,11 @@ intervening space) by either an integer or the keyword "infinite". */ h->req_client = EPopcornHour; h->reqflags |= FLAG_MIME_FLAC_FLAC; } + else if(strstrc(p, "Microsoft-IPTV-Client", '\r')) + { + h->req_client = EMediaRoom; + h->reqflags |= FLAG_MS_PFS; + } else if(strstrc(p, "DLNADOC/1.50", '\r')) { h->req_client = EStandardDLNA150; diff --git a/upnphttp.h b/upnphttp.h index 1d0ff91..de98079 100644 --- a/upnphttp.h +++ b/upnphttp.h @@ -54,8 +54,8 @@ struct upnphttp { off_t req_RangeStart; off_t req_RangeEnd; long int req_chunklen; - u_int32_t reqflags; - int respflags; + uint32_t reqflags; + uint32_t respflags; /* response */ char * res_buf; int res_buflen; @@ -87,6 +87,7 @@ struct upnphttp { #define FLAG_MIME_AVI_AVI 0x00400000 #define FLAG_MIME_FLAC_FLAC 0x00800000 #define FLAG_NO_RESIZE 0x01000000 +#define FLAG_MS_PFS 0x02000000 // Microsoft PlaysForSure client /* New_upnphttp() */ struct upnphttp * diff --git a/upnpsoap.c b/upnpsoap.c index 4d479a7..ad5806b 100644 --- a/upnpsoap.c +++ b/upnpsoap.c @@ -689,15 +689,14 @@ callback(void *args, int argc, char **argv, char **azColName) if( album_art && atoi(album_art) ) { /* Video and audio album art is handled differently */ - if( *mime == 'v' && (passed_args->filter & FILTER_RES) && (passed_args->client != EXbox) ) { + if( *mime == 'v' && (passed_args->filter & FILTER_RES) && (passed_args->flags & FLAG_MS_PFS) ) { ret = sprintf(str_buf, "<res protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\">" "http://%s:%d/AlbumArt/%s-%s.jpg" "</res>", lan_addr[0].str, runtime_vars.port, album_art, detailID); memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); passed_args->size += ret; - } - else if( passed_args->filter & FILTER_UPNP_ALBUMARTURI ) { + } else if( passed_args->filter & FILTER_UPNP_ALBUMARTURI ) { ret = sprintf(str_buf, "<upnp:albumArtURI "); memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); passed_args->size += ret; @@ -712,6 +711,27 @@ callback(void *args, int argc, char **argv, char **azColName) passed_args->size += ret; } } +#ifdef PFS_HACK + if( (passed_args->flags & FLAG_MS_PFS) && *mime == 'i' ) { + ret = snprintf(str_buf, 512, "<upnp:album>%s</upnp:album>", "[No Keywords]"); + memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); + passed_args->size += ret; + + if( tn && atoi(tn) ) { + ret = snprintf(str_buf, 512, "<upnp:albumArtURI>" + "http://%s:%d/Thumbnails/%s.jpg" + "</upnp:albumArtURI>", + lan_addr[0].str, runtime_vars.port, detailID); + } else { + ret = snprintf(str_buf, 512, "<upnp:albumArtURI>" + "http://%s:%d/Resized/%s.jpg?width=160,height=160" + "</upnp:albumArtURI>", + lan_addr[0].str, runtime_vars.port, detailID); + } + memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); + passed_args->size += ret; + } +#endif if( passed_args->filter & FILTER_RES ) { mime_to_ext(mime, ext); if( (passed_args->client == EFreeBox) && tn && atoi(tn) ) { @@ -736,7 +756,7 @@ callback(void *args, int argc, char **argv, char **azColName) passed_args->size += ret; } if( bitrate && (passed_args->filter & FILTER_RES_BITRATE) ) { - if( passed_args->client == EXbox ) + if( passed_args->flags & FLAG_MS_PFS ) ret = sprintf(str_buf, "bitrate=\"%d\" ", atoi(bitrate)/1024); else ret = sprintf(str_buf, "bitrate=\"%s\" ", bitrate); @@ -893,8 +913,8 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) if( !BrowseFlag || (strcmp(BrowseFlag, "BrowseDirectChildren") && strcmp(BrowseFlag, "BrowseMetadata")) ) { SoapError(h, 402, "Invalid Args"); - if( h->req_client == EXbox ) - ObjectId = malloc(1); + if( h->reqflags & FLAG_MS_PFS ) + ObjectId = sqlite3_malloc(1); goto browse_error; } if( !ObjectId ) @@ -902,8 +922,8 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) if( !(ObjectId = GetValueFromNameValueList(&data, "ContainerID")) ) { SoapError(h, 701, "No such object error"); - if( h->req_client == EXbox ) - ObjectId = malloc(1); + if( h->reqflags & FLAG_MS_PFS ) + ObjectId = sqlite3_malloc(1); goto browse_error; } } @@ -928,14 +948,20 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) args.requested = RequestedCount; args.client = h->req_client; args.flags = h->reqflags; - if( h->req_client == EXbox ) + if( h->reqflags & FLAG_MS_PFS ) { - if( strcmp(ObjectId, "16") == 0 ) - ObjectId = strdup(IMAGE_DIR_ID); - else if( strcmp(ObjectId, "15") == 0 ) - ObjectId = strdup(VIDEO_DIR_ID); + if( strchr(ObjectId, '$') || (strcmp(ObjectId, "0") == 0) ) + { + ObjectId = sqlite3_mprintf("%s", ObjectId); + } else - ObjectId = strdup(ObjectId); + { + ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS" + "where OBJECT_ID in ('1$%s', '2$%s', '3$%s')", + ObjectId, ObjectId, ObjectId); + if( ptr ) + ObjectId = ptr; + } } DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n" " * ObjectID: %s\n" @@ -1023,9 +1049,9 @@ browse_error: if( orderBy ) free(orderBy); free(resp); - if( h->req_client == EXbox ) + if( h->reqflags & FLAG_MS_PFS ) { - free(ObjectId); + sqlite3_free(ObjectId); } } @@ -1068,10 +1094,13 @@ SearchContentDirectory(struct upnphttp * h, const char * action) StartingIndex = atoi(ptr); if( !ContainerID ) { - SoapError(h, 701, "No such object error"); - if( h->req_client == EXbox ) - ContainerID = malloc(1); - goto search_error; + if( !(ContainerID = GetValueFromNameValueList(&data, "ObjectID")) ) + { + SoapError(h, 701, "No such object error"); + if( h->reqflags & FLAG_MS_PFS ) + ContainerID = sqlite3_malloc(1); + goto search_error; + } } memset(&args, 0, sizeof(args)); @@ -1094,20 +1123,22 @@ SearchContentDirectory(struct upnphttp * h, const char * action) args.requested = RequestedCount; args.client = h->req_client; args.flags = h->reqflags; - if( h->req_client == EXbox ) + if( h->reqflags & FLAG_MS_PFS ) { - if( strcmp(ContainerID, "4") == 0 ) - ContainerID = strdup("1$4"); - else if( strcmp(ContainerID, "5") == 0 ) - ContainerID = strdup("1$5"); - else if( strcmp(ContainerID, "6") == 0 ) - ContainerID = strdup("1$6"); - else if( strcmp(ContainerID, "7") == 0 ) - ContainerID = strdup("1$7"); - else if( strcmp(ContainerID, "F") == 0 ) - ContainerID = strdup(MUSIC_PLIST_ID); + if( strchr(ContainerID, '$') || (strcmp(ContainerID, "0") == 0) ) + { + ContainerID = sqlite3_mprintf("%s", ContainerID); + } else - ContainerID = strdup(ContainerID); + { + ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS" + "where OBJECT_ID in ('1$%s', '2$%s', '3$%s')", + ContainerID, ContainerID, ContainerID); + if( ptr ) + ContainerID = ptr; + else + ContainerID = sqlite3_mprintf("%s", ContainerID); + } #if 0 // Looks like the 360 already does this /* Sort by track number for some containers */ if( orderBy && @@ -1261,9 +1292,9 @@ search_error: if( newSearchCriteria ) free(newSearchCriteria); free(resp); - if( h->req_client == EXbox ) + if( h->reqflags & FLAG_MS_PFS ) { - free(ContainerID); + sqlite3_free(ContainerID); } }