From: E.Smith <31170571+azlm8t@users.noreply.github.com> Date: Sun, 27 Jan 2019 23:46:33 +0000 (+0000) Subject: dvr: New fmt spec for per-dir seasons and one movie per dir. (#4667) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b106250c98af2244ca9d011cd0c5081f42eb9630;p=thirdparty%2Ftvheadend.git dvr: New fmt spec for per-dir seasons and one movie per dir. (#4667) Previously the $q format specifier would only output movies as: tvmovies/title (yyyy).ts However, a common alternative is to store each movie in its own sub-directory: tvmovies/title1 (yyyy)/title1 (yyyy).ts tvmovies/title2 (yyyy)/title2 (yyyy).ts Similarly for episodes we output: tvshows/title/title - SxxEyy.ts But a common alternative is to have one directory per season: tvshows/title/Season 1/title - S01Eyy.ts tvshows/title/Season 2/title - S02Eyy.ts So we now add a "$3q" to output these common alternatives, as requested in the forums. Also add equivalent "$3Q" to output without the "genre" prefix i.e., without "tvshows/" or "tvmovies/". Issue: #4667 --- diff --git a/docs/property/pathname.md b/docs/property/pathname.md index 2cfea0ab8..452b24eae 100644 --- a/docs/property/pathname.md +++ b/docs/property/pathname.md @@ -56,7 +56,8 @@ Examples are below based on different information in the EPG: - Countdown/Countdown (episode without guide season/episode information) The `$Q` and `$q` formats also have two numeric modifiers to select -variant formats and can be used as `$1Q`, `$2Q`, `$1q`, and `$2q`. +variant formats and can be used as `$1Q`, `$2Q`, `3Q`, `$1q`, `$2q`, +and `$3q`. The number 1 variant forces the recording to be formatted as a movie, ignoring the genre from the schedule. @@ -67,6 +68,19 @@ tv series. These variants can be useful to work-around bad schedule data that gives incorrect genres for programmes. +The number 3 variants (`$3Q` and `$3q`) is an alternative directory +layout that can be used if your guide data has accurate programme +information. It will put movies in separate directories for each movie +and tvshows in separate per-season directories. + +Examples for `$3q` are: +- tvmovies/Gladiator (2000)/Gladiator (2000) +- tvshows/Bones/Season 5/Bones - S05E11 + +Examples for `$3Q` are: +- Gladiator (2000)/Gladiator (2000) +- Bones/Season 5/Bones - S05E11 + Typically the `$q` and `$Q` formats would be combined with other modifiers to generate a complete filename such as `$q$n.$x`. diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c index 27932e2ae..4f8fe4c2d 100644 --- a/src/dvr/dvr_rec.c +++ b/src/dvr/dvr_rec.c @@ -425,9 +425,21 @@ _dvr_get_tvshows_subdir(const dvr_entry_t *de) return "tvshows"; } +/// Scraper friendly sub-type options bitmask. +typedef enum { + DVR_SF_WITHOUT_SUBDIR = 0x0, /*< X.ts and Y.ts - movies and tvshows not in subdir */ + DVR_SF_WITH_GENRE_SUBDIR = 0x1, /*< tvmovies and tvshows */ + DVR_SF_WITH_PER_SEASON_SUBDIR = 0x2, /*< X/Season S/Y.ts - separate dir per-season */ + DVR_SF_WITH_PER_MOVIE_SUBDIR = 0x4 /*< X/X.ts - separate dir per-movie */ +} dvr_sf_t; + static const char * -_dvr_sub_scraper_friendly(const char *id, const char *fmt, const void *aux, char *tmp, size_t tmplen, int with_genre_subdir) +_dvr_sub_scraper_friendly(const char *id, const char *fmt, const void *aux, char *tmp, size_t tmplen, dvr_sf_t subdir_type) { + /* Directory to use for no season/special season when using + * per-season directories. Kodi "naming tv shows/Special Episodes". + */ + static const char special_season_dir[] = "Season 0"; /* Deliberately not localized. */ const dvr_entry_t *de = aux; epg_broadcast_t *ebc = de->de_bcast; @@ -502,7 +514,7 @@ _dvr_sub_scraper_friendly(const char *id, const char *fmt, const void *aux, char tvhdebug(LS_DVR, "fmt = %s is_movie = %d content_type = %d", fmt ?: "", is_movie, de->de_content_type); - char *date_buf = NULL, *episode_buf = NULL; + char *date_buf = NULL, *episode_buf = NULL, *season_dir = NULL; if (is_movie) { /* Include the year if available. This helps scraper differentiate @@ -562,14 +574,27 @@ _dvr_sub_scraper_friendly(const char *id, const char *fmt, const void *aux, char * and moving them easier since they get tracked by inotify on * just the one directory. * + * However, we allow the user to select variants to use multiple + * directories for people who so desire. + * * Example format below: - * "tvmovies/title (yyyy)" (with genre_subdir) - * "title (yyyy)" (without genre_subdir) - * "title" (without genre_subdir, no airdate) + * "tvmovies/title (yyyy)" (with genre_subdir) + * "tvmovies/title (yyyy)/title (yyyy)" (with genre_subdir + per movie subdir) + * "title (yyyy)" (without genre_subdir) + * "title" (without genre_subdir, no airdate) */ - if (with_genre_subdir) + if ((subdir_type & DVR_SF_WITH_GENRE_SUBDIR) == DVR_SF_WITH_GENRE_SUBDIR) tvh_strlcatf(tmp, tmplen, offset, "%s/", _dvr_get_tvmovies_subdir(de)); + /* For 'per movie subdir' we want "title (YYYY)/" or "title/" if no year but don't want "(YYYY)" (no title) */ + if ((subdir_type & DVR_SF_WITH_PER_MOVIE_SUBDIR) == DVR_SF_WITH_PER_MOVIE_SUBDIR && !strempty(title_buf)) { + tvh_strlcatf(tmp, tmplen, offset, "%s", title_buf); + if (!strempty(date_buf)) + tvh_strlcatf(tmp, tmplen, offset, " (%s)", date_buf); + /* Then add trailing directory slash */ + tvh_strlcatf(tmp, tmplen, offset, "%s", "/"); + } + if (!strempty(title_buf)) tvh_strlcatf(tmp, tmplen, offset, "%s", title_buf); /* Movies don't have anything relevant in sub-titles field so @@ -588,15 +613,35 @@ _dvr_sub_scraper_friendly(const char *id, const char *fmt, const void *aux, char * We put the episode number before the subtitle to make it easier * to see if we are missing episodes when you do ls. * + * User can optionally choose to have episodes in per-season directories. + * * Example formats below: * "tvshows/title/title - S01E02 - subtitle" (with genre_subdir) + * "tvshows/title/Season 1/title - S01E02 - subtitle" (with genre and per season subdir) * "title - S01E02 - subtitle" (without genre_subdir) * "title - subtitle_2001-05-04" (without genre_subdir, long running show) * "title - subtitle" (without genre_subdir, no epg info on show) */ - if (with_genre_subdir) + if ((subdir_type & DVR_SF_WITH_GENRE_SUBDIR) == DVR_SF_WITH_GENRE_SUBDIR) tvh_strlcatf(tmp, tmplen, offset, "%s/", _dvr_get_tvshows_subdir(de)); - if (!strempty(title_buf)) + + if ((subdir_type & DVR_SF_WITH_PER_SEASON_SUBDIR) == DVR_SF_WITH_PER_SEASON_SUBDIR && + !strempty(title_buf)) { + season_dir = alloca(256); + /* Note we do _not_ localize the word "Season" so we are consistent + * with what appears to be de facto directory naming conventions. + * For example the program Plex states: "Be sure to use the English word + * "Season" as noted above, even if your content is in another language." + * There seems to be disagreement between tools on whether it should be + * "Season 1" (Emby, Kodi) or "Season 01" (Plex). + */ + epg_broadcast_epnumber_format(ebc, season_dir, 256, + NULL, "Season %d", NULL, NULL, NULL); + /* E.g., "Simpsons/Season 1/Simpsons", to which we'll later add " - S01E02" + * Or "Simpsons/Season 0/Simpsons" for a tv special. + */ + tvh_strlcatf(tmp, tmplen, offset, "%s/%s/%s", title_buf, strempty(season_dir) ? special_season_dir : season_dir, title_buf); + } else if (!strempty(title_buf)) tvh_strlcatf(tmp, tmplen, offset, "%s/%s", title_buf, title_buf); if (!strempty(episode_buf)) tvh_strlcatf(tmp, tmplen, offset, " - %s", episode_buf); @@ -612,13 +657,25 @@ _dvr_sub_scraper_friendly(const char *id, const char *fmt, const void *aux, char static const char * dvr_sub_scraper_friendly_with_genre_subdir(const char *id, const char *fmt, const void *aux, char *tmp, size_t tmplen) { - return _dvr_sub_scraper_friendly(id, fmt, aux, tmp, tmplen, 1); + return _dvr_sub_scraper_friendly(id, fmt, aux, tmp, tmplen, DVR_SF_WITH_GENRE_SUBDIR); +} + +static const char * +dvr_sub_scraper_friendly_with_genre_subdir_full(const char *id, const char *fmt, const void *aux, char *tmp, size_t tmplen) +{ + return _dvr_sub_scraper_friendly(id, fmt, aux, tmp, tmplen, DVR_SF_WITH_GENRE_SUBDIR | DVR_SF_WITH_PER_SEASON_SUBDIR | DVR_SF_WITH_PER_MOVIE_SUBDIR); } static const char * dvr_sub_scraper_friendly_without_genre_subdir(const char *id, const char *fmt, const void *aux, char *tmp, size_t tmplen) { - return _dvr_sub_scraper_friendly(id, fmt, aux, tmp, tmplen, 0); + return _dvr_sub_scraper_friendly(id, fmt, aux, tmp, tmplen, DVR_SF_WITHOUT_SUBDIR); +} + +static const char * +dvr_sub_scraper_friendly_without_genre_subdir_full(const char *id, const char *fmt, const void *aux, char *tmp, size_t tmplen) +{ + return _dvr_sub_scraper_friendly(id, fmt, aux, tmp, tmplen, DVR_SF_WITHOUT_SUBDIR | DVR_SF_WITH_PER_SEASON_SUBDIR | DVR_SF_WITH_PER_MOVIE_SUBDIR); } static const char * @@ -752,9 +809,11 @@ static htsstr_substitute_t dvr_subs_entry[] = { { .id = "q", .getval = dvr_sub_scraper_friendly_with_genre_subdir }, { .id = "1q", .getval = dvr_sub_scraper_friendly_with_genre_subdir }, { .id = "2q", .getval = dvr_sub_scraper_friendly_with_genre_subdir }, + { .id = "3q", .getval = dvr_sub_scraper_friendly_with_genre_subdir_full }, { .id = "Q", .getval = dvr_sub_scraper_friendly_without_genre_subdir }, { .id = "1Q", .getval = dvr_sub_scraper_friendly_without_genre_subdir }, { .id = "2Q", .getval = dvr_sub_scraper_friendly_without_genre_subdir }, + { .id = "3Q", .getval = dvr_sub_scraper_friendly_without_genre_subdir_full }, { .id = NULL, .getval = NULL } };