Adding season/episode to the pvr.mythtv recordings list
#1
I have long wanted to sort recorded "box sets" by series/episode so I can see which is the next one in the series to watch
(Particularly a problem when they are transmitted out of order as CBS Action did with the original star trek series recently).

The pvr.mythtv thread suggested mythtv doesn't have season/episode information, but I've discovered it does ... sort of:
  1. Myth's metadata lookup job was already retrieving season/episode for approx. 75% of my recorded 'box set' files.
  2. If the grabber provides it (I use tv_grab_uk_rt which does) syndicatedepisodenumber in the recordedprogram table has season/episode information, although creative SQL-ing is required to populate the season/episode fields (see below).
  3. I've seen it suggested that myth 0.28 can also get season/episode from the on-air guide (if your source has it in a sensible format), but I haven't tried this myself...

As the myth services API already includes series/episode fields, I created a patch for pvr.mythtv to add this information to the recordings folder. Adding the recordings folder as a video source now makes most of my box sets appear in 'TV SHOWS' Smile.

Given that series/episode might be unavailable or unreliable for some users (at least until they tweak the mythtv data using MySQL) I added an option to turn it on and off (default=off) via the add-in setup menu.

This patch is against 1.12.18 (helix branch) and has been tested against my 0.27 mythtv server. Only one language translation for the menu title so far I'm afraid...
Code:
diff --git a/pvr.mythtv/resources/language/English (Australia)/strings.po b/pvr.mythtv/resources/language/English (Australia)/strings.po
index 1a90ac5..33c0e61 100644
--- a/pvr.mythtv/resources/language/English (Australia)/strings.po      
+++ b/pvr.mythtv/resources/language/English (Australia)/strings.po      
@@ -192,6 +192,10 @@ msgctxt "#30064"
msgid "Enable recording fanart/thumbnails"
msgstr "Enable recording fanart/thumbnails"

+msgctxt "#30065"
+msgid "Season/Episode in title where available"
+msgstr "Season/Episode in title where available"
+
msgctxt "#30100"
msgid "Protocol version: %i - Database version: %i"
msgstr "Protocol version: %i - Database version: %i"
diff --git a/pvr.mythtv/resources/language/English (New Zealand)/strings.po b/pvr.mythtv/resources/language/English (New Zealand)/strings.po
index 273f681..ce2ea45 100644
--- a/pvr.mythtv/resources/language/English (New Zealand)/strings.po    
+++ b/pvr.mythtv/resources/language/English (New Zealand)/strings.po    
@@ -192,6 +192,10 @@ msgctxt "#30064"
msgid "Enable recording fanart/thumbnails"
msgstr "Enable recording fanart/thumbnails"

+msgctxt "#30065"
+msgid "Season/Episode in title where available"
+msgstr "Season/Episode in title where available"
+
msgctxt "#30100"
msgid "Protocol version: %i - Database version: %i"
msgstr "Protocol version: %i - Database version: %i"
diff --git a/pvr.mythtv/resources/language/English (US)/strings.po b/pvr.mythtv/resources/language/English (US)/strings.po
index 3be956b..da38347 100644
--- a/pvr.mythtv/resources/language/English (US)/strings.po    
+++ b/pvr.mythtv/resources/language/English (US)/strings.po    
@@ -192,6 +192,10 @@ msgctxt "#30064"
msgid "Enable recording fanart/thumbnails"
msgstr "Enable recording fanart/thumbnails"

+msgctxt "#30065"
+msgid "Season/Episode in title where available"
+msgstr "Season/Episode in title where available"
+
msgctxt "#30100"
msgid "Protocol version: %i - Database version: %i"
msgstr "Protocol version: %i - Database version: %i"
diff --git a/pvr.mythtv/resources/language/English/strings.po b/pvr.mythtv/resources/language/English/strings.po
index 6923c79..ec4e94f 100644
--- a/pvr.mythtv/resources/language/English/strings.po
+++ b/pvr.mythtv/resources/language/English/strings.po
@@ -201,7 +201,11 @@ msgctxt "#30064"
msgid "Enable recording fanart/thumbnails"
msgstr ""

-# empty strings from id 30065 to 30099
+msgctxt "#30065"
+msgid "Season/Episode in title where available"
+msgstr ""
+
+# empty strings from id 30066 to 30099

# Systeminformation labels
msgctxt "#30100"
diff --git a/pvr.mythtv/resources/language/German/strings.po b/pvr.mythtv/resources/language/German/strings.po
index 539d6ff..512f9e6 100644
--- a/pvr.mythtv/resources/language/German/strings.po
+++ b/pvr.mythtv/resources/language/German/strings.po
@@ -192,6 +192,10 @@ msgctxt "#30064"
msgid "Enable recording fanart/thumbnails"
msgstr "Aufnahme von Fankunst/Vorschaubilder aktivieren"

+msgctxt "#30065"
+msgid "Season/Episode in title where available"
+msgstr "Saison/Episode in Titel, wo verfügbar"
+
msgctxt "#30100"
msgid "Protocol version: %i - Database version: %i"
msgstr "Protokollversion: %i - Datenbankversion: %i"
diff --git a/pvr.mythtv/resources/settings.xml b/pvr.mythtv/resources/settings.xml
index 425fdc3..ef3f2d7 100644
--- a/pvr.mythtv/resources/settings.xml
+++ b/pvr.mythtv/resources/settings.xml
@@ -29,6 +29,7 @@
     <setting id="block_shutdown" type="bool" label="30062" default="true" />
     <setting id="tunedelay" type="slider" option="int" range="5,1,30" label="30053" />
     <setting id="group_recordings" type="enum" label="30054" lvalues="30055|30056|30057" default="0" />
+    <setting id="include_series_episode" type="bool" label="30065" default="false" />
     <setting id="enable_edl" type="enum" label="30058" lvalues="30059|30060|30061" default="0" />
     <setting id="channel_icons" type="bool" label="30063" default="true" />
     <setting id="recording_icons" type="bool" label="30064" default="true" />
diff --git a/src/client.cpp b/src/client.cpp
index cb78cba..f1a8d87 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -55,6 +55,7 @@ int           g_iRecTranscoder          = 0;
bool          g_bDemuxing               = DEFAULT_HANDLE_DEMUXING;
int           g_iTuneDelay              = DEFAULT_TUNE_DELAY;
int           g_iGroupRecordings        = GROUP_RECORDINGS_ALWAYS;
+bool          g_bIncludeSeriesEpisode   = DEFAULT_INCLUDE_SERIES_EPISODE;
int           g_iEnableEDL              = ENABLE_EDL_ALWAYS;
bool          g_bBlockMythShutdown      = DEFAULT_BLOCK_SHUTDOWN;

@@ -269,6 +270,14 @@ ADDON_STATUS ADDON_Create(void *hdl, void *props)
     g_iGroupRecordings = GROUP_RECORDINGS_ALWAYS;
   }

+  /* Read settings "include_series_episode" from settings.xml */
+  if (!XBMC->GetSetting("include_series_episode", &g_bIncludeSeriesEpisode))
+    {
+      /* If setting is unknown fallback to defaults */
+      XBMC->Log(LOG_ERROR, "Couldn't get 'include_series_episode' setting, falling back to '%b' as default", DEFAULT_INCLUDE_SERIES_EPISODE);
+      g_bIncludeSeriesEpisode = DEFAULT_INCLUDE_SERIES_EPISODE;
+  }
+
   /* Read setting "enable_edl" from settings.xml */
   if (!XBMC->GetSetting("enable_edl", &g_iEnableEDL))
   {
@@ -616,6 +625,14 @@ ADDON_STATUS ADDON_SetSetting(const char *settingName, const void *settingValue)
       PVR->TriggerRecordingUpdate();
     }
   }
+  else if (str == "include_series_episode")
+  {
+    XBMC->Log(LOG_INFO, "Changed Setting 'include_series_episode' from %u to %u", g_bIncludeSeriesEpisode, *(bool*)settingValue);
+    if (g_bIncludeSeriesEpisode != *(bool*)settingValue)
+      g_bIncludeSeriesEpisode = *(bool*)settingValue;
+      PVR->TriggerRecordingUpdate();
+  }
+
   else if (str == "enable_edl")
   {
     XBMC->Log(LOG_INFO, "Changed Setting 'enable_edl' from %u to %u", g_iEnableEDL, *(int*)settingValue);
diff --git a/src/client.h b/src/client.h
index a6bb5a5..0de0392 100644
--- a/src/client.h
+++ b/src/client.h
@@ -63,6 +63,7 @@
#define GROUP_RECORDINGS_ALWAYS             0
#define GROUP_RECORDINGS_ONLY_FOR_SERIES    1
#define GROUP_RECORDINGS_NEVER              2
+#define DEFAULT_INCLUDE_SERIES_EPISODE      false
#define ENABLE_EDL_ALWAYS                   0
#define ENABLE_EDL_DIALOG                   1
#define ENABLE_EDL_NEVER                    2
@@ -106,9 +107,11 @@ extern bool         g_bRecAutoRunJob3;
extern bool         g_bRecAutoRunJob4;
extern bool         g_bRecAutoExpire;
extern int          g_iRecTranscoder;
+///* Advanced Settings
extern bool         g_bDemuxing;
extern int          g_iTuneDelay;
extern int          g_iGroupRecordings;
+extern bool         g_bIncludeSeriesEpisode;
extern int          g_iEnableEDL;
extern bool         g_bBlockMythShutdown;

diff --git a/src/cppmyth/MythProgramInfo.cpp b/src/cppmyth/MythProgramInfo.cpp
index 3b386bc..18634f9 100644
--- a/src/cppmyth/MythProgramInfo.cpp
+++ b/src/cppmyth/MythProgramInfo.cpp
@@ -312,3 +312,8 @@ uint16_t MythProgramInfo::Season() const
{
   return (m_proginfo ? m_proginfo->season : 0);
}
+
+uint16_t MythProgramInfo::Episode() const
+{
+  return (m_proginfo ? m_proginfo->episode : 0);
+}
diff --git a/src/cppmyth/MythProgramInfo.h b/src/cppmyth/MythProgramInfo.h
index c704b8c..e010145 100644
--- a/src/cppmyth/MythProgramInfo.h
+++ b/src/cppmyth/MythProgramInfo.h
@@ -80,6 +80,7 @@ public:
   std::string StorageGroup() const;
   std::string Inetref() const;
   uint16_t Season() const;
+  uint16_t Episode() const;

private:
   Myth::ProgramPtr m_proginfo;
diff --git a/src/pvrclient-mythtv.cpp b/src/pvrclient-mythtv.cpp
index 54b5e5b..64105e3 100644
--- a/src/pvrclient-mythtv.cpp
+++ b/src/pvrclient-mythtv.cpp
@@ -28,6 +28,7 @@

#include <time.h>
#include <set>
+#include <sstream>

using namespace ADDON;
using namespace PLATFORM;
@@ -808,10 +809,7 @@ PVR_ERROR PVRClientMythTV::GetRecordings(ADDON_HANDLE handle)
       //@TODO: tag.iLastPlayedPosition

       std::string id = it->second.UID();
-      std::string title = MakeProgramTitle(it->second.Title(), it->second.Subtitle());
-
       PVR_STRCPY(tag.strRecordingId, id.c_str());
-      PVR_STRCPY(tag.strTitle, title.c_str());
       PVR_STRCPY(tag.strPlot, it->second.Description().c_str());
       PVR_STRCPY(tag.strChannelName, it->second.ChannelName().c_str());

@@ -821,8 +819,33 @@ PVR_ERROR PVRClientMythTV::GetRecordings(ADDON_HANDLE handle)

       // Add recording title to directory to group everything according to its name just like MythTV does
       std::string strDirectory(it->second.RecordingGroup());
+      // Assign title to the file name only if it isn't in a containing folder and add season/episode if known
+      // Improves ttvdb scraper support, see http://kodi.wiki/view/Naming_video_files/TV_shows
+      std::string title;
+      std::string seasonEpisode = MakeSeasonEpisode(it->second.Season(), it->second.Episode());
       if (g_iGroupRecordings == GROUP_RECORDINGS_ALWAYS || (g_iGroupRecordings == GROUP_RECORDINGS_ONLY_FOR_SERIES && it->second.GetPropsSerie()))
+      {
         strDirectory.append("/").append(it->second.Title());
+        if (!seasonEpisode.empty() and g_bIncludeSeriesEpisode)
+        {
+          // Where a recording appears in a directory and has a season and episode, include it in place of the title
+          if (it->second.Subtitle().empty()) title = MakeProgramTitle(it->second.Title(), seasonEpisode);
+          else                               title = MakeProgramTitle(seasonEpisode, it->second.Subtitle());
+          XBMC->Log(LOG_DEBUG, "%s: Season/episode %s inserted in %s (%s)", __FUNCTION__,seasonEpisode.c_str(),title.c_str()),it->second.Title().c_str();
+        }
+      }
+      else
+      {
+        if (!seasonEpisode.empty() and g_bIncludeSeriesEpisode)
+        {
+          // no containing directory so include the season and episode between title and subtitle for 'by filename' sorting in recordings
+          title = MakeProgramTitle( it->second.Title(), MakeProgramTitle(seasonEpisode, it->second.Subtitle()) );
+          XBMC->Log(LOG_DEBUG, "%s: Season/episode %s inserted in %s", __FUNCTION__,seasonEpisode.c_str(),title.c_str());
+        }
+      }
+      //if title hasn's been set fall back to the standard behaviour
+      if (title.empty()) title = MakeProgramTitle(it->second.Title(), it->second.Subtitle());
+      PVR_STRCPY(tag.strTitle, title.c_str());
       PVR_STRCPY(tag.strDirectory, strDirectory.c_str());

       // Images
@@ -2223,6 +2246,22 @@ std::string PVRClientMythTV::MakeProgramTitle(const std::string& title, const st
   return epgtitle;
}

+std::string PVRClientMythTV::MakeSeasonEpisode(uint16_t season, uint16_t episode)
+{
+  std::ostringstream convert;
+  //Specials or series with only one season have a season of 0, so we can't exclude them
+  if (episode>0)
+  {
+    convert << "S";
+    if(season<10) convert << "0";
+    convert << season;
+    convert << "E";
+    if(episode<10) convert << "0";
+    convert << episode;
+  }
+  return convert.str();
+}
+
// Broacast ID is 32 bits integer and allows to identify a EPG item.
// MythTV backend doesn't provide one. So we make it encoding time and channel
// as below:
diff --git a/src/pvrclient-mythtv.h b/src/pvrclient-mythtv.h
index 13d68dc..4342a76 100644
--- a/src/pvrclient-mythtv.h
+++ b/src/pvrclient-mythtv.h
@@ -193,6 +193,7 @@ private:
    * \see class MythProgramInfo , class MythEPGInfo
    */
   static std::string MakeProgramTitle(const std::string& title, const std::string& subtitle);
+  static std::string MakeSeasonEpisode(uint16_t season, uint16_t episode);

   /**
    *

Also, here is the SQL I use to populate Season/Episode from the tv_grab_uk_rt guide data. I run this nightly with mythfilldatabase.
If you want to try it, I suggest investigating the content of your recorded and recordedprogram tables in detail before applying it to your system. This is particularly the case if you are using a different listings grabber! It certainly won't work for everyone.
Code:
-- MSQL Query to update season/episode based on UK_RadioTimes xmltvid data format ExxSxx (most series)
update recorded
inner join recordedprogram on
(
recorded.programid=recordedprogram.programid and
recorded.title=recordedprogram.title and
recorded.subtitle=recordedprogram.subtitle
)
set
recorded.season = mid(recordedprogram.syndicatedepisodenumber,locate("S",recordedprogram.syndicatedepisodenumber)+1),
recorded.episode = mid(recordedprogram.syndicatedepisodenumber,2,locate("S",recordedprogram.syndicatedepisodenumber)-2)
where
recorded.title!="" and
recorded.programid!="" and
recordedprogram.syndicatedepisodenumber!="" and
recorded.episode=0 and
recorded.season=0 and
recordedprogram.syndicatedepisodenumber like "E%S%";
-- MSQL Query to update season/episode based on UK_RadioTimes xmltvid data format Exxxx (Mostly soaps and specials)
update recorded
inner join recordedprogram on
(
recorded.programid=recordedprogram.programid and
recorded.title=recordedprogram.title and
recorded.subtitle=recordedprogram.subtitle
)
set
recorded.season = 0,
recorded.episode = mid(recordedprogram.syndicatedepisodenumber,2)
where
recorded.title!="" and
recorded.programid!="" and
recordedprogram.syndicatedepisodenumber!="" and
recorded.episode=0 and
recorded.season=0 and
recordedprogram.syndicatedepisodenumber like "E%" and
recordedprogram.syndicatedepisodenumber not like "%S%";

Any thoughts?
Reply
#2
I've submitted a 'productionized' version of the Kodi part of this change as https://github.com/xbmc/xbmc/pull/7677
Reply
#3
Kodi part of this change merged since Jarvis Alpha 2 (https://github.com/xbmc/xbmc/pull/7626)

Pvr.Mythtv part of this change merged into janbar's doityourself branch (https://github.com/janbar/pvr.mythtv/pull/12)
Reply
#4
How should I go about testing out this new feature? Do I download and install files or do I edit some files? I am currently running mythbuntu. I actually got fed up with having to work with mythical librarian in order to have these features and moved back to windows.
Reply
#5
Test out janbar's latest 'doityourself' offering of the mythtv client coupled with jarvis nightlies / roll your own from git. (see http://kodi.wiki/view/HOW-TO:Compile_Kodi_for_Linux and http://kodi.wiki/view/MythTV_PVR/BuildFromSource )

Unless you're running mythtv 0.28 alpha as a backend you will also need something similar to the SQL in post 1 to get season/episode data to populate your recordedprogram table so it can be sent to kodi.

Let me know if you've any questions / how you get on.
Reply
#6
No luck building it on my own. Jabber's doityourself compiles but kodi crashes when I install it manually. I am running mythtv 0.28 and connecting via a mac based client.

Update: Figured it out and have it running. I don't see the options to display episode numbers though. Is there something else I need to do from the backend?
Reply
#7
I have it working now and can see Season and Episode number when I choose "Recording Information" in the context menu. Is there any plan to have this information shown when the recordings are listed or to give the user the option to organize the recordings by episode number? How about for instance having the information (Season and Episode) shown below the episode description?
Reply
#8
(2015-09-03, 03:53)boukmandutty Wrote: I have it working now and can see Season and Episode number when I choose "Recording Information" in the context menu. Is there any plan to have this information shown when the recordings are listed or to give the user the option to organize the recordings by episode number? How about for instance having the information (Season and Episode) shown below the episode description?

If you want to sort your recordings by season/episode you just have to "Sort by: File"
pvr:// protocol filenames now look like this: "Star Trek s01e04 1966 The Naked Time, TV PickTV 2340_030420151830.mpg" - or similar. The 's01e04' bit is alphabetical in series/episode order, so "by file" will be "by title/series/episode/original_air_date/subtitle".

It should also be possible to add season/episode to the view as well by modifying the skin (in your skin folder look for MyPVRRecordings.xml) the tags you want to add are "Listitem.Episode" and "Listitem.Season".

Something like this (all on one line) which works for me as a mod to standard confluence :-):
Code:
<label>$INFO[ListItem.Label]$INFO[ListItem.EpisodeName, (,) ]$INFO[Listitem.Season,,.]$INFO[Listitem.Episode,,]</label>
Reply
#9
Hey thanks for the help so far. I have been messing around with the line of code you gave me a bit and this is what I have been able to come up with given my limited skills .

I wonder if some of the view options that are given when kodi imports to the TV SHOWS section can be transferred over the the LIVE TV section.

watch gallery
Reply
#10
(2015-09-08, 21:46)boukmandutty Wrote: I wonder if some of the view options that are given when kodi imports to the TV SHOWS section can be transferred over the the LIVE TV section.

Some of it should be transferable, but you need to remember that not all the information might be available in the 'recordings' screen that you can access from 'TV_SHOWS'.
Scraped information is available to a TV_SHOWS screen 'listitem' using the VideoInfoTag structure.
I believe the recordings screen 'listitem' is limited to information accessible directly from the backend via the PVR API which is stored in the PVRRecordingInfoTag structure.

For a full list of available tags, look at https://github.com/xbmc/xbmc/blob/master...r.cpp#L475 with more details of where it gets it from at https://github.com/xbmc/xbmc/blob/master....cpp#L4671

NB: LISTITEM_TITLE in the source code equates to Listitem.Title in the skin files. Don't ask me how or why, but it does.

PS: Aaahhh, so that's how you embed images in the forum: imgur...
Reply

Logout Mark Read Team Forum Stats Members Help
Adding season/episode to the pvr.mythtv recordings list0