* 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)
This commit is contained in:
		
							
								
								
									
										123
									
								
								upnpsoap.c
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								upnpsoap.c
									
									
									
									
									
								
							| @@ -107,31 +107,7 @@ GetProtocolInfo(struct upnphttp * h, const char * action) | ||||
| 		"<u:%sResponse " | ||||
| 		"xmlns:u=\"%s\">" | ||||
| 		"<Source>" | ||||
| 			"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 | ||||
| 		"</Source>" | ||||
| 		"<Sink></Sink>" | ||||
| 		"</u:%sResponse>"; | ||||
| @@ -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) | ||||
| 			"<u:BrowseResponse " | ||||
| 			"xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">" | ||||
| 			"<Result>" | ||||
| 			"<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></Result>"; | ||||
|         static const char resp2[] = "<UpdateID>0</UpdateID></u:BrowseResponse>"; | ||||
|  | ||||
| @@ -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) | ||||
| 			"<u:SearchResponse " | ||||
| 			"xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">" | ||||
| 			"<Result>" | ||||
| 			"<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></Result>"; | ||||
|         static const char resp2[] = "<UpdateID>0</UpdateID></u:SearchResponse>"; | ||||
|  | ||||
| 	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<NumberReturned>%u</NumberReturned>\n<TotalMatches>%u</TotalMatches>\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); | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user