diff --git a/Makefile b/Makefile index b47fddd..b72dd6a 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ BASEOBJS = minidlna.o upnphttp.o upnpdescgen.o upnpsoap.o \ sql.o utils.o metadata.o scanner.o inotify.o \ tivo_utils.o tivo_beacon.o tivo_commands.o \ tagutils/textutils.o tagutils/misc.o tagutils/tagutils.o \ - image_utils.o albumart.o log.o + playlist.o image_utils.o albumart.o log.o ALLOBJS = $(BASEOBJS) $(LNXOBJS) @@ -100,7 +100,7 @@ uuid.o: uuid.h testupnpdescgen.o: config.h upnpdescgen.h upnpdescgen.o: config.h upnpdescgen.h minidlnapath.h upnpglobalvars.h upnpdescgen.o: minidlnatypes.h upnpdescstrings.h -scanner.o: upnpglobalvars.h metadata.h utils.h sql.h scanner.h log.h +scanner.o: upnpglobalvars.h metadata.h utils.h sql.h scanner.h log.h playlist.h metadata.o: upnpglobalvars.h metadata.h albumart.h utils.h sql.h log.h albumart.o: upnpglobalvars.h albumart.h utils.h image_utils.h sql.h log.h tagutils/misc.o: tagutils/misc.h @@ -108,6 +108,8 @@ tagutils/textutils.o: tagutils/misc.h tagutils/textutils.h log.h tagutils/tagutils.o: tagutils/tagutils-asf.c tagutils/tagutils-flc.c tagutils/tagutils-plist.c tagutils/tagutils-misc.c tagutils/tagutils.o: tagutils/tagutils-aac.c tagutils/tagutils-asf.h tagutils/tagutils-flc.h tagutils/tagutils-mp3.c tagutils/tagutils-wav.c tagutils/tagutils.o: tagutils/tagutils-ogg.c tagutils/tagutils-aac.h tagutils/tagutils.h tagutils/tagutils-mp3.h tagutils/tagutils-ogg.h log.h +playlist.o: playlist.h +inotify.o: inotify.h playlist.h image_utils.o: image_utils.h tivo_utils.o: config.h tivo_utils.h tivo_beacon.o: config.h tivo_beacon.h tivo_utils.h diff --git a/inotify.c b/inotify.c index f79d59b..8a5cfa7 100644 --- a/inotify.c +++ b/inotify.c @@ -1,3 +1,20 @@ +/* MiniDLNA media server + * Copyright (C) 2008-2010 Justin Maggard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ #include "config.h" #include #include @@ -26,6 +43,7 @@ #include "scanner.h" #include "metadata.h" #include "albumart.h" +#include "playlist.h" #include "log.h" #define EVENT_SIZE ( sizeof (struct inotify_event) ) @@ -43,6 +61,7 @@ struct watch static struct watch *watches; static struct watch *lastwatch = NULL; +static time_t next_pl_fill = 0; char *get_path_from_wd(int wd) { @@ -293,11 +312,13 @@ inotify_insert_file(char * name, const char * path) case ALL_MEDIA: if( !is_image(path) && !is_audio(path) && - !is_video(path) ) + !is_video(path) && + !is_playlist(path) ) return -1; break; case AUDIO_ONLY: - if( !is_audio(path) ) + if( !is_audio(path) && + !is_playlist(path) ) return -1; break; case VIDEO_ONLY: @@ -337,6 +358,12 @@ inotify_insert_file(char * name, const char * path) return -1; } } + else if( is_playlist(path) && (sql_get_int_field(db, "SELECT ID from PLAYLISTS where PATH = '%q'", path) > 0) ) + { + DPRINTF(E_DEBUG, L_INOTIFY, "Re-reading modified playlist.\n", path); + inotify_remove_file(path_buf); + next_pl_fill = 1; + } sqlite3_free_table(result); } sqlite3_free(sql); @@ -350,7 +377,7 @@ inotify_insert_file(char * name, const char * path) do { - DPRINTF(E_DEBUG, L_INOTIFY, "Checking %s\n", parent_buf); + //DEBUG DPRINTF(E_DEBUG, L_INOTIFY, "Checking %s\n", parent_buf); sql = sqlite3_mprintf("SELECT OBJECT_ID from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" " where d.PATH = '%q' and REF_ID is NULL", parent_buf); if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) && rows ) @@ -390,8 +417,14 @@ inotify_insert_file(char * name, const char * path) if( !depth ) { + //DEBUG DPRINTF(E_DEBUG, L_INOTIFY, "Inserting %s\n", name); insert_file(name, path, id+2, get_next_available_id("OBJECTS", id)); free(id); + if( (is_audio(path) || is_playlist(path)) && next_pl_fill != 1 ) + { + next_pl_fill = time(NULL) + 120; // Schedule a playlist scan for 2 minutes from now. + //DEBUG DPRINTF(E_WARN, L_INOTIFY, "Playlist scan scheduled for %s", ctime(&next_pl_fill)); + } } return depth; } @@ -495,11 +528,12 @@ inotify_remove_file(const char * path) char **result; char * art_cache; sqlite_int64 detailID = 0; - int i, rows, children, ret = 1; + int i, rows, children, playlist, ret = 1; /* Invalidate the scanner cache so we don't insert files into non-existent containers */ valid_cache = 0; - sql = sqlite3_mprintf("SELECT ID from DETAILS where PATH = '%q'", path); + playlist = is_playlist(path); + sql = sqlite3_mprintf("SELECT ID from %s where PATH = '%q'", playlist?"PLAYLISTS":"DETAILS", path); if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) ) { if( rows ) @@ -510,14 +544,30 @@ inotify_remove_file(const char * path) sqlite3_free_table(result); } sqlite3_free(sql); - if( detailID ) + if( playlist && detailID ) + { + sql_exec(db, "DELETE from PLAYLISTS where ID = %lld", detailID); + sql_exec(db, "DELETE from DETAILS where ID =" + " (SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s$%lld')", + MUSIC_PLIST_ID, detailID); + sql_exec(db, "DELETE from OBJECTS where OBJECT_ID = '%s$%lld' or PARENT_ID = '%s$%lld'", + MUSIC_PLIST_ID, detailID, MUSIC_PLIST_ID, detailID); + } + else if( detailID ) { /* Delete the parent containers if we are about to empty them. */ asprintf(&sql, "SELECT PARENT_ID from OBJECTS where DETAIL_ID = %lld", detailID); if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) ) { - for( i=1; i < rows; i++ ) + for( i=1; i <= rows; i++ ) { + /* If it's a playlist item, adjust the item count of the playlist */ + if( strncmp(result[i], MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) + { + sql_exec(db, "UPDATE PLAYLISTS set FOUND = (FOUND-1) where ID = %d", + atoi(strrchr(result[i], '$') + 1)); + } + children = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", result[i]); if( children < 0 ) continue; @@ -615,6 +665,11 @@ start_inotify() length = poll(pollfds, 1, timeout); if( !length ) { + if( next_pl_fill && (time(NULL) >= next_pl_fill) ) + { + fill_playlists(); + next_pl_fill = 0; + } continue; } else if( length < 0 ) diff --git a/minissdp.c b/minissdp.c index a39b0fd..1554cc2 100644 --- a/minissdp.c +++ b/minissdp.c @@ -287,7 +287,7 @@ SendSSDPNotifies(int s, const char * host, unsigned short port, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) ); if(n < 0) { - DPRINTF(E_ERROR, L_SSDP, "sendto(udp_notify=%d, %s): %s", s, host, strerror(errno)); + DPRINTF(E_ERROR, L_SSDP, "sendto(udp_notify=%d, %s): %s\n", s, host, strerror(errno)); } i++; } diff --git a/playlist.c b/playlist.c new file mode 100644 index 0000000..85b394b --- /dev/null +++ b/playlist.c @@ -0,0 +1,196 @@ +/* MiniDLNA media server + * Copyright (C) 2009-2010 Justin Maggard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "tagutils/tagutils.h" + +#include "upnpglobalvars.h" +#include "scanner.h" +#include "metadata.h" +#include "utils.h" +#include "sql.h" +#include "log.h" + +int +insert_playlist(const char * path, char * name) +{ + struct song_metadata plist; + struct stat file; + int items = 0, matches; + char type[4]; + + strncpy(type, strrchr(name, '.')+1, 4); + + if( start_plist(path, NULL, &file, NULL, type) != 0 ) + { + DPRINTF(E_WARN, L_SCANNER, "Bad playlist [%s]\n", path); + return -1; + } + while( next_plist_track(&plist, &file, NULL, type) == 0 ) + { + items++; + freetags(&plist); + } + strip_ext(name); + + DPRINTF(E_DEBUG, L_SCANNER, "Playlist %s contains %d items\n", name, items); + + matches = sql_get_int_field(db, "SELECT count(*) from PLAYLISTS where NAME = '%q'", name); + if( matches > 0 ) + { + sql_exec(db, "INSERT into PLAYLISTS" + " (NAME, PATH, ITEMS) " + "VALUES" + " ('%q(%d)', '%q', %d)", + name, matches, path, items); + } + else + { + sql_exec(db, "INSERT into PLAYLISTS" + " (NAME, PATH, ITEMS) " + "VALUES" + " ('%q', '%q', %d)", + name, path, items); + } + return 0; +} + +int +fill_playlists() +{ + int rows, i, found, len; + char **result; + char *plpath, *plname, *fname; + char class[] = "playlistContainer"; + struct song_metadata plist; + struct stat file; + char type[4]; + sqlite_int64 plID, detailID; + char sql_buf[1024] = "SELECT ID, NAME, PATH from PLAYLISTS where ITEMS > FOUND"; + + if( sql_get_table(db, sql_buf, &result, &rows, NULL) != SQLITE_OK ) + return -1; + if( !rows ) + { + sqlite3_free_table(result); + return 0; + } + + rows++; + for( i=3; i 0 ) + { + DPRINTF(E_DEBUG, L_SCANNER, "+ %s found in db\n", fname); + sql_exec(db, "INSERT into OBJECTS" + " (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, NAME, REF_ID) " + "SELECT" + " '%s$%llX$%d', '%s$%llX', CLASS, DETAIL_ID, NAME, OBJECT_ID from OBJECTS" + " where DETAIL_ID = %lld and OBJECT_ID glob '64$*'", + MUSIC_PLIST_ID, plID, plist.track, + MUSIC_PLIST_ID, plID, + detailID); + found++; + } + else + { + DPRINTF(E_DEBUG, L_SCANNER, "- %s not found in db\n", fname); + if( strchr(fname, '\\') ) + { + fname = modifyString(fname, "\\", "/", 0); + goto retry; + } + else if( (fname = strchr(fname, '/')) ) + { + fname++; + goto retry; + } + } + freetags(&plist); + } + sql_exec(db, "UPDATE PLAYLISTS set FOUND = %d where ID = %lld", found, plID); + } + sqlite3_free_table(result); + + return 0; +} + diff --git a/playlist.h b/playlist.h new file mode 100644 index 0000000..95fd462 --- /dev/null +++ b/playlist.h @@ -0,0 +1,19 @@ +/* Playlist handling + * + * Project : minidlna + * Website : http://sourceforge.net/projects/minidlna/ + * Author : Justin Maggard + * Copyright (c) 2008-2010 Justin Maggard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef __PLAYLIST_H__ +#define __PLAYLIST_H__ + +int +insert_playlist(const char * path, char * name); + +int +fill_playlists(void); + +#endif // __PLAYLIST_H__ diff --git a/scanner.c b/scanner.c index a90ed1d..edad1ec 100644 --- a/scanner.c +++ b/scanner.c @@ -30,6 +30,7 @@ #include "upnpglobalvars.h" #include "metadata.h" +#include "playlist.h" #include "utils.h" #include "sql.h" #include "scanner.h" @@ -479,6 +480,11 @@ insert_file(char * name, const char * path, const char * parentID, int object) if( !detailID ) strcpy(name, orig_name); } + else if( is_playlist(name) ) + { + if( insert_playlist(path, name) == 0 ) + return 1; + } if( !detailID && is_audio(name) ) { strcpy(base, MUSIC_DIR_ID); @@ -534,7 +540,8 @@ CreateDatabase(void) "1$5", "1", "Genre", "1$6", "1", "Artist", "1$7", "1", "Album", - "1$14", "1", "Folders", + MUSIC_DIR_ID, "1", "Folders", + MUSIC_PLIST_ID, "1", "Playlists", "2", "0", "Video", "2$8", "2", "All Video", VIDEO_DIR_ID, "2", "Folders", @@ -542,7 +549,7 @@ CreateDatabase(void) "3$11", "3", "All Pictures", "3$12", "3", "Date Taken", "3$13", "3", "Camera", - "3$16", "3", "Folders", + IMAGE_DIR_ID, "3", "Folders", "64", "0", "Browse Folders", 0 }; @@ -595,6 +602,15 @@ CreateDatabase(void) ")"); if( ret != SQLITE_OK ) goto sql_failed; + ret = sql_exec(db, "CREATE TABLE PLAYLISTS (" + "ID INTEGER PRIMARY KEY AUTOINCREMENT, " + "NAME TEXT NOT NULL, " + "PATH TEXT NOT NULL, " + "ITEMS INTEGER DEFAULT 0, " + "FOUND INTEGER DEFAULT 0" + ")"); + if( ret != SQLITE_OK ) + goto sql_failed; ret = sql_exec(db, "CREATE TABLE SETTINGS (" "UPDATE_ID INTEGER PRIMARY KEY DEFAULT 0, " "FLAGS INTEGER DEFAULT 0" @@ -636,8 +652,10 @@ filter_audio(const struct dirent *d) (d->d_type == DT_LNK) || (d->d_type == DT_UNKNOWN) || ((d->d_type == DT_REG) && - is_audio(d->d_name) ) - ) ); + (is_audio(d->d_name) || + is_playlist(d->d_name) + ) + ) )); } int @@ -674,7 +692,8 @@ filter_media(const struct dirent *d) ((d->d_type == DT_REG) && (is_image(d->d_name) || is_audio(d->d_name) || - is_video(d->d_name) + is_video(d->d_name) || + is_playlist(d->d_name) ) ) )); } @@ -796,6 +815,8 @@ start_scanner() * client that uses UPnPSearch on large containers). */ sql_exec(db, "create INDEX IDX_SEARCH_OPT ON OBJECTS(OBJECT_ID, CLASS, DETAIL_ID);"); + fill_playlists(); + //JM: Set up a db version number, so we know if we need to rebuild due to a new structure. sql_exec(db, "pragma user_version = %d;", DB_VERSION); } diff --git a/scanner.h b/scanner.h index 36247f7..26db9da 100644 --- a/scanner.h +++ b/scanner.h @@ -12,6 +12,7 @@ #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" diff --git a/tagutils/tagutils-plist.c b/tagutils/tagutils-plist.c index 1c4f1cf..91d618a 100644 --- a/tagutils/tagutils-plist.c +++ b/tagutils/tagutils-plist.c @@ -31,38 +31,45 @@ #include "log.h" -#define MAX_BUF 1024 +#define MAX_BUF 4096 static FILE *fp = 0; static int _utf8bom = 0; +static int _trackno; static int (*_next_track)(struct song_metadata*, struct stat*, char*, char*); static int _m3u_next_track(struct song_metadata*, struct stat*, char*, char*); +static int _pls_next_track(struct song_metadata*, struct stat*, char*, char*); int -start_plist(char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type) +start_plist(const char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type) { char *fname, *suffix; _next_track = 0; _utf8bom = 0; + _trackno = 0; - if(!strcmp(type, "m3u")) + if(strcmp(type, "m3u") == 0) _next_track = _m3u_next_track; + else if(strcmp(type, "pls") == 0) + _next_track = _pls_next_track; if(!_next_track) { - DPRINTF(E_ERROR, L_SCAN_SCANNER, "Non supported type of playlist <%s>\n", type); + DPRINTF(E_ERROR, L_SCANNER, "Unsupported playlist type <%s>\n", type); return -1; } if(!(fp = fopen(path, "rb"))) { - DPRINTF(E_ERROR, L_SCAN_SCANNER, "Cannot open %s\n", path); + DPRINTF(E_ERROR, L_SCANNER, "Cannot open %s\n", path); return -1; } - // + if(!psong) + return 0; + memset((void*)psong, 0, sizeof(struct song_metadata)); psong->is_plist = 1; psong->path = strdup(path); @@ -121,6 +128,7 @@ _m3u_next_track(struct song_metadata *psong, struct stat *stat, char *lang, char p[--len] = '\0'; } psong->path = strdup(p); + psong->track = ++_trackno; return 0; } p = fgets(buf, MAX_BUF, fp); @@ -128,7 +136,57 @@ _m3u_next_track(struct song_metadata *psong, struct stat *stat, char *lang, char } fclose(fp); - return -1; + return 1; +} + +int +_pls_next_track(struct song_metadata *psong, struct stat *stat, char *lang, char *type) +{ + char buf[MAX_BUF], *p; + int len; + + memset((void*)psong, 0, sizeof(struct song_metadata)); + + // read first line + p = fgets(buf, MAX_BUF, fp); + + while(p) + { + while(isspace(*p)) p++; + if(*p && *p != '#') + { + // verify that it's a valid pls playlist + if(!_trackno) + { + if(strncmp(p, "[playlist]", 10)) + break; + _trackno++; + goto next_line; + } + + if(strncmp(p, "File", 4)) + goto next_line; + + psong->track = strtol(p+4, &p, 10); + if(!psong->track || !p) + goto next_line; + _trackno = psong->track; + // check dos format + len = strlen(++p); + + while(p[len - 1] == '\r' || p[len - 1] == '\n') + { + p[--len] = '\0'; + } + psong->path = strdup(p); + return 0; + } +next_line: + p = fgets(buf, MAX_BUF, fp); + } + + fclose(fp); + return 1; } int diff --git a/tagutils/tagutils.c b/tagutils/tagutils.c index 1669be1..821c59a 100644 --- a/tagutils/tagutils.c +++ b/tagutils/tagutils.c @@ -146,6 +146,7 @@ static taghandler taghandlers[] = { #include "tagutils-asf.c" #include "tagutils-wav.c" #include "tagutils-pcm.c" +#include "tagutils-plist.c" //********************************************************************************* // freetags() diff --git a/tagutils/tagutils.h b/tagutils/tagutils.h index 801a084..6622d03 100644 --- a/tagutils/tagutils.h +++ b/tagutils/tagutils.h @@ -115,7 +115,7 @@ extern void make_composite_tags(struct song_metadata *psong); extern int readtags(char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type); extern void freetags(struct song_metadata *psong); -extern int start_plist(char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type); +extern int start_plist(const char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type); extern int next_plist_track(struct song_metadata *psong, struct stat *stat, char *lang, char *type); #endif diff --git a/upnpglobalvars.h b/upnpglobalvars.h index 20f7cd8..ab3f659 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -21,11 +21,11 @@ #include -#define MINIDLNA_VERSION "1.0.16.3" +#define MINIDLNA_VERSION "1.0.17" #define CLIENT_CACHE_SLOTS 20 #define USE_FORK 1 -#define DB_VERSION 4 +#define DB_VERSION 5 #if 0 // Add these once the newer ffmpeg libs that can detect WMAPRO are more widely used "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," diff --git a/upnpsoap.c b/upnpsoap.c index 2df2625..7909bda 100644 --- a/upnpsoap.c +++ b/upnpsoap.c @@ -670,6 +670,9 @@ callback(void *args, int argc, char **argv, char **azColName) memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); passed_args->size += ret; } + if( strncmp(id, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) { + track = strrchr(id, '$')+1; + } if( track && atoi(track) && (passed_args->filter & FILTER_UPNP_ORIGINALTRACKNUMBER) ) { ret = sprintf(str_buf, "<upnp:originalTrackNumber>%s</upnp:originalTrackNumber>", track); memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1); @@ -920,7 +923,7 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) if( h->req_client == EXbox ) { if( strcmp(ObjectId, "16") == 0 ) - ObjectId = strdup("3$16"); + ObjectId = strdup(IMAGE_DIR_ID); else if( strcmp(ObjectId, "15") == 0 ) ObjectId = strdup(VIDEO_DIR_ID); else @@ -950,11 +953,24 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) { ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", ObjectId); totalMatches = (ret > 0) ? ret : 0; -#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */ ret = 0; - if( totalMatches < 10000 ) + if( SortCriteria ) + { +#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */ + if( totalMatches < 10000 ) #endif orderBy = parse_sort_criteria(SortCriteria, &ret); + } + else + { + if( strncmp(ObjectId, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) + { + if( strcmp(ObjectId, MUSIC_PLIST_ID) == 0 ) + asprintf(&orderBy, "order by d.TITLE"); + else + asprintf(&orderBy, "order by length(OBJECT_ID), OBJECT_ID"); + } + } /* If it's a DLNA client, return an error for bad sort criteria */ if( (args.flags & FLAG_DLNA) && ret ) { @@ -1080,6 +1096,8 @@ SearchContentDirectory(struct upnphttp * h, const char * action) 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); else ContainerID = strdup(ContainerID); #if 0 // Looks like the 360 already does this diff --git a/utils.c b/utils.c index c4bebd2..dd16307 100644 --- a/utils.c +++ b/utils.c @@ -239,6 +239,12 @@ is_image(const char * file) return (ends_with(file, ".jpg") || ends_with(file, ".jpeg")); } +int +is_playlist(const char * file) +{ + return (ends_with(file, ".m3u") || ends_with(file, ".pls")); +} + int resolve_unknown_type(const char * path, enum media_types dir_type) { @@ -258,11 +264,13 @@ resolve_unknown_type(const char * path, enum media_types dir_type) case ALL_MEDIA: if( is_image(path) || is_audio(path) || - is_video(path) ) + is_video(path) || + is_playlist(path) ) type = TYPE_FILE; break; case AUDIO_ONLY: - if( is_audio(path) ) + if( is_audio(path) || + is_playlist(path) ) type = TYPE_FILE; break; case VIDEO_ONLY: diff --git a/utils.h b/utils.h index 50ce879..7cc34d0 100644 --- a/utils.h +++ b/utils.h @@ -40,6 +40,9 @@ is_audio(const char * file); int is_image(const char * file); +int +is_playlist(const char * file); + int resolve_unknown_type(const char * path, enum media_types dir_type);