From 6eaf47a8d6d65f8994d8d61986b59233a6b8b8b7 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 18 Oct 2013 23:07:40 +0200 Subject: [PATCH 1/9] Show items with genre=Podcast as Podcast items in Remote - also add Podcast smart playlist to default playlists --- src/db.c | 6 +++++- src/filescanner_ffmpeg.c | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/db.c b/src/db.c index 8a36a48e..99c414d0 100644 --- a/src/db.c +++ b/src/db.c @@ -4122,9 +4122,12 @@ db_perthread_deinit(void) "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \ " VALUES(4, 'TV Shows', 1, 'f.media_kind = 64', 0, '', 0, 5);" +#define Q_PL5 \ + "INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \ + " VALUES(5, 'Podcasts', 1, 'f.media_kind = 4', 0, '', 0, 1);" + /* These are the remaining automatically-created iTunes playlists, but * their query is unknown - " VALUES(5, 'Podcasts', 0, 'media_kind = 128 ', 0, '', 0, 1);" " VALUES(6, 'iTunes U', 0, 'media_kind = 256', 0, '', 0, 13);" " VALUES(7, 'Audiobooks', 0, 'media_kind = 512', 0, '', 0, 7);" " VALUES(8, 'Purchased', 0, 'media_kind = 1024', 0, '', 0, 8);" @@ -4177,6 +4180,7 @@ static const struct db_init_query db_init_queries[] = { Q_PL2, "create default smart playlist 'Music'" }, { Q_PL3, "create default smart playlist 'Movies'" }, { Q_PL4, "create default smart playlist 'TV Shows'" }, + { Q_PL5, "create default smart playlist 'Podcasts'" }, { Q_SCVER, "set schema version" }, }; diff --git a/src/filescanner_ffmpeg.c b/src/filescanner_ffmpeg.c index 668eefb6..3b58db1c 100644 --- a/src/filescanner_ffmpeg.c +++ b/src/filescanner_ffmpeg.c @@ -616,6 +616,11 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi) { mfi->media_kind = 2; } + /* Podcasts are either media_kind 4, 6 or 7 - unsure what the difference is */ + else if (strcmp(mfi->genre,"Podcast") == 0) + { + mfi->media_kind = 4; + } skip_extract: if (mdcount == 0) From eb1e827f1dcbfbc6c1b19fdf7b0a1ecd80acc247 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 19 Oct 2013 10:11:33 +0200 Subject: [PATCH 2/9] Fix for Podcast detection - if there is no metadata --- src/filescanner_ffmpeg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filescanner_ffmpeg.c b/src/filescanner_ffmpeg.c index 3b58db1c..7b5f7928 100644 --- a/src/filescanner_ffmpeg.c +++ b/src/filescanner_ffmpeg.c @@ -617,7 +617,7 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi) mfi->media_kind = 2; } /* Podcasts are either media_kind 4, 6 or 7 - unsure what the difference is */ - else if (strcmp(mfi->genre,"Podcast") == 0) + else if ((mdcount > 0) && mfi->genre && (strcmp(mfi->genre,"Podcast") == 0)) { mfi->media_kind = 4; } From c70496bb58a3532ebaf8f493b45c527ef719342b Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 19 Oct 2013 11:07:10 +0200 Subject: [PATCH 3/9] Accept item-spec in playspec requests --- src/httpd_dacp.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 65710c4e..97e17a76 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -841,7 +841,9 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u int ret; /* /ctrl-int/1/playspec?database-spec='dmap.persistentid:0x1'&container-spec='dmap.persistentid:0x5'&container-item-spec='dmap.containeritemid:0x9' - * With our DAAP implementation, container-spec is the playlist ID and container-item-spec is the song ID + * or (Apple Remote when playing a Podcast) + * /ctrl-int/1/playspec?database-spec='dmap.persistentid:0x1'&container-spec='dmap.persistentid:0x5'&item-spec='dmap.itemid:0x9' + * With our DAAP implementation, container-spec is the playlist ID and container-item-spec/item-spec is the song ID */ s = daap_session_find(req, query, evbuf); @@ -881,9 +883,11 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u { /* Start song ID */ param = evhttp_find_header(query, "container-item-spec"); + if (!param) + param = evhttp_find_header(query, "item-spec"); if (!param) { - DPRINTF(E_LOG, L_DACP, "No container-item-spec in playspec request\n"); + DPRINTF(E_LOG, L_DACP, "No container-item-spec/item-spec in playspec request\n"); goto out_fail; } @@ -891,7 +895,7 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u param = strchr(param, ':'); if (!param) { - DPRINTF(E_LOG, L_DACP, "Malformed container-item-spec parameter in playspec request\n"); + DPRINTF(E_LOG, L_DACP, "Malformed container-item-spec/item-spec parameter in playspec request\n"); goto out_fail; } @@ -900,7 +904,7 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u ret = safe_hextou32(param, &id); if (ret < 0) { - DPRINTF(E_LOG, L_DACP, "Couldn't convert container-item-spec to an integer in playspec (%s)\n", param); + DPRINTF(E_LOG, L_DACP, "Couldn't convert container-item-spec/item-spec to an integer in playspec (%s)\n", param); goto out_fail; } From c935f8cc3b1a1b5d1b82eccc242ee2e3407302c2 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 21 Oct 2013 22:52:04 +0200 Subject: [PATCH 4/9] Extend conditions identifying podcasts (media_kind=4) - if path contains "/Podcasts/" - if iTunes XML says it's a podcast --- src/filescanner_ffmpeg.c | 4 ++++ src/filescanner_itunes.c | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/src/filescanner_ffmpeg.c b/src/filescanner_ffmpeg.c index 7b5f7928..b4c7da4c 100644 --- a/src/filescanner_ffmpeg.c +++ b/src/filescanner_ffmpeg.c @@ -617,6 +617,10 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi) mfi->media_kind = 2; } /* Podcasts are either media_kind 4, 6 or 7 - unsure what the difference is */ + else if (mfi->path && strstr(mfi->path, "/Podcasts/")) + { + mfi->media_kind = 4; + } else if ((mdcount > 0) && mfi->genre && (strcmp(mfi->genre,"Podcast") == 0)) { mfi->media_kind = 4; diff --git a/src/filescanner_itunes.c b/src/filescanner_itunes.c index 8764adf3..46a727fb 100644 --- a/src/filescanner_itunes.c +++ b/src/filescanner_itunes.c @@ -419,6 +419,13 @@ process_track_file(plist_t trk) } } + /* Set media_kind to 4 (Podcast) if Podcast is true */ + ret = get_dictval_bool_from_key(trk, "Podcast", &boolean); + if ((ret == 0) && boolean) + { + mfi->media_kind = 4; + } + /* Don't let album_artist set to "Unknown artist" if we've * filled artist from the iTunes data in the meantime */ From 74c7e6ceda8d1cea7921b3102efdf32ea1ec161d Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 21 Oct 2013 22:54:45 +0200 Subject: [PATCH 5/9] Add support for daap.songuserplaycount It's used to give proper replies to Remote when it asks for unplayed Podcasts. Maps to play_count in the database, which already existed (very good forward thinking by the original authors). --- src/daap_query.gperf | 1 + src/dmap_fields.gperf | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/daap_query.gperf b/src/daap_query.gperf index 92d08918..fb61d635 100644 --- a/src/daap_query.gperf +++ b/src/daap_query.gperf @@ -35,6 +35,7 @@ struct dmap_query_field_map; "daap.songtime", "f.song_length", 1 "daap.songtrackcount", "f.total_tracks", 1 "daap.songtracknumber", "f.track", 1 +"daap.songuserplaycount", "f.play_count", 1 "daap.songyear", "f.year", 1 "com.apple.itunes.mediakind", "f.media_kind", 1 "com.apple.itunes.extended-media-kind", "f.media_kind", 1 diff --git a/src/dmap_fields.gperf b/src/dmap_fields.gperf index 999c9ffb..61e95412 100644 --- a/src/dmap_fields.gperf +++ b/src/dmap_fields.gperf @@ -79,6 +79,7 @@ static const struct dmap_field_map dfm_dmap_aseq = { -1, static const struct dmap_field_map dfm_dmap_asfm = { dbmfi_offsetof(type), -1, -1 }; static const struct dmap_field_map dfm_dmap_asgn = { dbmfi_offsetof(genre), -1, -1 }; static const struct dmap_field_map dfm_dmap_asdt = { dbmfi_offsetof(description), -1, -1 }; +static const struct dmap_field_map dfm_dmap_aspc = { dbmfi_offsetof(play_count), -1, -1 }; static const struct dmap_field_map dfm_dmap_asrv = { -1, -1, -1 }; static const struct dmap_field_map dfm_dmap_assr = { dbmfi_offsetof(samplerate), -1, -1 }; static const struct dmap_field_map dfm_dmap_assz = { dbmfi_offsetof(file_size), -1, -1 }; @@ -198,6 +199,7 @@ struct dmap_field; "daap.songgenre", "asgn", &dfm_dmap_asgn, DMAP_TYPE_STRING "daap.songkeywords", "asky", &dfm_dmap_asky, DMAP_TYPE_STRING "daap.songlongcontentdescription", "aslc", &dfm_dmap_aslc, DMAP_TYPE_STRING +"daap.songuserplaycount", "aspc", &dfm_dmap_aspc, DMAP_TYPE_UINT "daap.songrelativevolume", "asrv", &dfm_dmap_asrv, DMAP_TYPE_BYTE "daap.sortartist", "assa", &dfm_dmap_assa, DMAP_TYPE_STRING "daap.sortcomposer", "assc", &dfm_dmap_assc, DMAP_TYPE_STRING From 1111590a9e05ecfaff2f0b3899f2cf2f4b475d94 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 22 Oct 2013 21:04:39 +0200 Subject: [PATCH 6/9] Enable compilations by default and add Podcasts-dir to defaults --- forked-daapd.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forked-daapd.conf b/forked-daapd.conf index ac422153..5ed7e1e6 100644 --- a/forked-daapd.conf +++ b/forked-daapd.conf @@ -27,7 +27,7 @@ library { directories = { "/srv/music" } # Directories containing compilations # Matches anywhere in the path (not a regexp, though) -# compilations = { "/compilations/" } + compilations = { "/Compilations/", "/Podcasts/" } # Artwork file names (without file type extension) # forked-daapd will look for jpg and png files with these base names From 209d17c148b27d567bd595b1b4275cc60aa7d050 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 24 Oct 2013 23:14:26 +0200 Subject: [PATCH 7/9] Rework of podcast and compilation scanning - identify podcasts by config path - always set album_artist to empty for podcasts - album_artist for compilations made configurable - clean up media type enumeration --- forked-daapd.conf | 21 ++++++++-- src/conffile.c | 2 + src/filescanner.c | 86 ++++++++++++++++++++++++++++++++-------- src/filescanner.h | 6 ++- src/filescanner_ffmpeg.c | 9 ----- src/filescanner_m3u.c | 2 +- 6 files changed, 95 insertions(+), 31 deletions(-) diff --git a/forked-daapd.conf b/forked-daapd.conf index 5ed7e1e6..e56842d9 100644 --- a/forked-daapd.conf +++ b/forked-daapd.conf @@ -25,9 +25,24 @@ library { # Directories to index directories = { "/srv/music" } - # Directories containing compilations - # Matches anywhere in the path (not a regexp, though) - compilations = { "/Compilations/", "/Podcasts/" } + + # Directories containing podcasts + # For each directory that is indexed the path is matched against these + # names. If there is a match all items in the directory are marked as + # podcasts. Eg. if you index /srv/music, and your podcasts are in + # /srv/music/Podcasts, you can set this to "/Podcasts". +# podcasts = { "/Podcasts" } + + # Directories containing compilations (eg soundtracks) + # For each directory that is indexed the path is matched against these + # names. If there is a match all items in the directory are marked as + # compilations. +# compilations = { "/Compilations" } + + # Compilations usually have many artists, and if you don't want every + # artist to be listed when artist browsing in Remote, you can set + # a single name which will be used for all music in the compilation dir +# compilation_artist = "Various artists" # Artwork file names (without file type extension) # forked-daapd will look for jpg and png files with these base names diff --git a/src/conffile.c b/src/conffile.c index fdf127d0..b940c1ab 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -61,6 +61,8 @@ static cfg_opt_t sec_library[] = CFG_STR("password", NULL, CFGF_NONE), CFG_STR_LIST("directories", NULL, CFGF_NONE), CFG_STR_LIST("compilations", NULL, CFGF_NONE), + CFG_STR_LIST("podcasts", NULL, CFGF_NONE), + CFG_STR("compilation_artist", NULL, CFGF_NONE), CFG_STR_LIST("artwork_basenames", "{artwork,cover}", CFGF_NONE), CFG_BOOL("itunes_overrides", cfg_false, CFGF_NONE), CFG_STR_LIST("no_transcode", NULL, CFGF_NONE), diff --git a/src/filescanner.c b/src/filescanner.c index 76ede928..08e46b47 100644 --- a/src/filescanner.c +++ b/src/filescanner.c @@ -156,9 +156,11 @@ normalize_fixup_tag(char **tag, char *src_tag) static void fixup_tags(struct media_file_info *mfi) { + cfg_t *lib; size_t len; char *tag; char *sep = " - "; + char *ca; if (mfi->genre && (strlen(mfi->genre) == 0)) { @@ -272,16 +274,36 @@ fixup_tags(struct media_file_info *mfi) normalize_fixup_tag(&mfi->album_sort, mfi->album); normalize_fixup_tag(&mfi->title_sort, mfi->title); - /* If we don't have an album_artist, set it to artist */ - if (!mfi->album_artist) + /* We need to set album_artist according to media type and config */ + if (mfi->compilation) /* Compilation */ { - if (mfi->compilation) + lib = cfg_getsec(cfg, "library"); + ca = cfg_getstr(lib, "compilation_artist"); + if (ca && mfi->album_artist) + { + free(mfi->album_artist); + mfi->album_artist = strdup(ca); + } + else if (ca && !mfi->album_artist) + { + mfi->album_artist = strdup(ca); + } + else if (!ca && !mfi->album_artist) { mfi->album_artist = strdup(""); mfi->album_artist_sort = strdup(""); } - else - mfi->album_artist = strdup(mfi->artist); + } + else if (mfi->media_kind == 4) /* Podcast */ + { + if (mfi->album_artist) + free(mfi->album_artist); + mfi->album_artist = strdup(""); + mfi->album_artist_sort = strdup(""); + } + else if (!mfi->album_artist) /* Regular media without album_artist */ + { + mfi->album_artist = strdup(mfi->artist); } if (!mfi->album_artist_sort && (strcmp(mfi->album_artist, mfi->artist) == 0)) @@ -296,7 +318,7 @@ fixup_tags(struct media_file_info *mfi) void -process_media_file(char *file, time_t mtime, off_t size, int compilation, int url, struct extinf_ctx *extinf) +process_media_file(char *file, time_t mtime, off_t size, int type, struct extinf_ctx *extinf) { struct media_file_info mfi; char *filename; @@ -373,7 +395,7 @@ process_media_file(char *file, time_t mtime, off_t size, int compilation, int ur mfi.time_modified = mtime; mfi.file_size = size; - if (!url) + if (!(type & F_SCAN_TYPE_URL)) { mfi.data_kind = 0; /* real file */ ret = scan_metadata_ffmpeg(file, &mfi); @@ -397,7 +419,10 @@ process_media_file(char *file, time_t mtime, off_t size, int compilation, int ur goto out; } - mfi.compilation = compilation; + if (type & F_SCAN_TYPE_COMPILATION) + mfi.compilation = 1; + if (type & F_SCAN_TYPE_PODCAST) + mfi.media_kind = 4; /* podcast */ if (!mfi.item_kind) mfi.item_kind = 2; /* music */ @@ -491,7 +516,7 @@ process_deferred_playlists(void) /* Thread: scan */ static void -process_file(char *file, time_t mtime, off_t size, int compilation, int flags) +process_file(char *file, time_t mtime, off_t size, int type, int flags) { char *ext; @@ -520,9 +545,28 @@ process_file(char *file, time_t mtime, off_t size, int compilation, int flags) } /* Not any kind of special file, so let's see if it's a media file */ - process_media_file(file, mtime, size, compilation, 0, NULL); + process_media_file(file, mtime, size, type, NULL); } +/* Thread: scan */ +static int +check_podcast(char *path) +{ + cfg_t *lib; + int ndirs; + int i; + + lib = cfg_getsec(cfg, "library"); + ndirs = cfg_size(lib, "podcasts"); + + for (i = 0; i < ndirs; i++) + { + if (strstr(path, cfg_getnstr(lib, "podcasts", i))) + return 1; + } + + return 0; +} /* Thread: scan */ static int @@ -559,7 +603,7 @@ process_directory(char *path, int flags) #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) struct kevent kev; #endif - int compilation; + int type; int ret; if (flags & F_SCAN_BULK) @@ -590,8 +634,12 @@ process_directory(char *path, int flags) return; } - /* Check for a compilation directory */ - compilation = check_compilation(path); + /* Check if compilation and/or podcast directory */ + type = 0; + if (check_compilation(path)) + type |= F_SCAN_TYPE_COMPILATION; + if (check_podcast(path)) + type |= F_SCAN_TYPE_PODCAST; for (;;) { @@ -655,7 +703,7 @@ process_directory(char *path, int flags) } if (S_ISREG(sb.st_mode)) - process_file(entry, sb.st_mtime, sb.st_size, compilation, flags); + process_file(entry, sb.st_mtime, sb.st_size, type, flags); else if (S_ISDIR(sb.st_mode)) push_dir(&dirstack, entry); else @@ -968,7 +1016,7 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie struct stat sb; char *deref = NULL; char *file = path; - int compilation; + int type; int ret; DPRINTF(E_DBG, L_SCAN, "File event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd); @@ -1041,9 +1089,13 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie } } - compilation = check_compilation(path); + type = 0; + if (check_compilation(path)) + type |= F_SCAN_TYPE_COMPILATION; + if (check_podcast(path)) + type |= F_SCAN_TYPE_PODCAST; - process_file(file, sb.st_mtime, sb.st_size, compilation, 0); + process_file(file, sb.st_mtime, sb.st_size, type, 0); if (deref) free(deref); diff --git a/src/filescanner.h b/src/filescanner.h index da4d78e7..8f2bf753 100644 --- a/src/filescanner.h +++ b/src/filescanner.h @@ -4,6 +4,10 @@ #include "db.h" +#define F_SCAN_TYPE_PODCAST (1 << 0) +#define F_SCAN_TYPE_COMPILATION (1 << 1) +#define F_SCAN_TYPE_URL (1 << 2) + int filescanner_init(void); @@ -18,7 +22,7 @@ struct extinf_ctx }; void -process_media_file(char *file, time_t mtime, off_t size, int compilation, int url, struct extinf_ctx *extinf); +process_media_file(char *file, time_t mtime, off_t size, int type, struct extinf_ctx *extinf); /* Actual scanners */ int diff --git a/src/filescanner_ffmpeg.c b/src/filescanner_ffmpeg.c index b4c7da4c..668eefb6 100644 --- a/src/filescanner_ffmpeg.c +++ b/src/filescanner_ffmpeg.c @@ -616,15 +616,6 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi) { mfi->media_kind = 2; } - /* Podcasts are either media_kind 4, 6 or 7 - unsure what the difference is */ - else if (mfi->path && strstr(mfi->path, "/Podcasts/")) - { - mfi->media_kind = 4; - } - else if ((mdcount > 0) && mfi->genre && (strcmp(mfi->genre,"Podcast") == 0)) - { - mfi->media_kind = 4; - } skip_extract: if (mdcount == 0) diff --git a/src/filescanner_m3u.c b/src/filescanner_m3u.c index 76c18e7a..e18bf329 100644 --- a/src/filescanner_m3u.c +++ b/src/filescanner_m3u.c @@ -212,7 +212,7 @@ scan_m3u_playlist(char *file, time_t mtime) if (extinf.found) DPRINTF(E_INFO, L_SCAN, "Playlist has EXTINF metadata, artist is '%s', title is '%s'\n", extinf.artist, extinf.title); - process_media_file(filename, mtime, 0, 0, 1, &extinf); + process_media_file(filename, mtime, 0, F_SCAN_TYPE_URL, &extinf); } /* Regular file */ else From 63231eb850c7429d006155fb94663876731dc81b Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 25 Oct 2013 23:29:34 +0200 Subject: [PATCH 8/9] Enable podcasts and compilation in config --- forked-daapd.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forked-daapd.conf b/forked-daapd.conf index e56842d9..a8f5efde 100644 --- a/forked-daapd.conf +++ b/forked-daapd.conf @@ -31,18 +31,18 @@ library { # names. If there is a match all items in the directory are marked as # podcasts. Eg. if you index /srv/music, and your podcasts are in # /srv/music/Podcasts, you can set this to "/Podcasts". -# podcasts = { "/Podcasts" } + podcasts = { "/Podcasts" } # Directories containing compilations (eg soundtracks) # For each directory that is indexed the path is matched against these # names. If there is a match all items in the directory are marked as # compilations. -# compilations = { "/Compilations" } + compilations = { "/Compilations" } # Compilations usually have many artists, and if you don't want every # artist to be listed when artist browsing in Remote, you can set # a single name which will be used for all music in the compilation dir -# compilation_artist = "Various artists" + compilation_artist = "Various artists" # Artwork file names (without file type extension) # forked-daapd will look for jpg and png files with these base names From 84b8a7653d90fac70c68d7e5410e80264f20dddf Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 26 Oct 2013 23:13:02 +0200 Subject: [PATCH 9/9] Enable play count with Remote The purpose is to get the "Unplayed" tab for Podcasts in Remote working, which requires that we increase the counter even when playback is with Remote/RAOP (and not just iTunes streaming, which was already working) --- src/player.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/player.c b/src/player.c index bd1cdb35..de2e20e2 100644 --- a/src/player.c +++ b/src/player.c @@ -1148,6 +1148,8 @@ source_check(void) { i++; + db_file_inc_playcount((int)cur_playing->id); + /* Stop playback if: * - at end of playlist (NULL) * - repeat OFF and at end of playlist (wraparound)