public static string GetSeasonHtmlOverviewOffline([NotNull] this ShowItem si, [NotNull] Season s)
        {
            SeriesInfo ser  = s.TheSeries;
            int        snum = s.SeasonNumber;
            string     body = "";

            if (!string.IsNullOrEmpty(ser.GetSeriesWideBannerPath()) &&
                !string.IsNullOrEmpty(TheTVDB.GetImageURL(ser.GetSeriesWideBannerPath())))
            {
                body += "<img width=758 height=140 src=\"" + TheTVDB.GetImageURL(ser.GetSeriesWideBannerPath()) +
                        "\"><br/>";
            }

            List <ProcessedEpisode> eis = si.SeasonEpisodes.ContainsKey(snum) ?
                                          si.SeasonEpisodes[snum] :
                                          ShowItem.ProcessedListFromEpisodes(s.Episodes.Values, si);

            string seasText = SeasonName(si, snum);

            if (eis.Count > 0 && eis[0].SeasonId > 0)
            {
                seasText = " - <A HREF=\"" + TheTVDB.Instance.WebsiteUrl(ser.TvdbCode, eis[0].SeasonId, false) + "\">" +
                           seasText + "</a>";
            }
            else
            {
                seasText = " - " + seasText;
            }

            body += "<h1><A HREF=\"" + TheTVDB.Instance.WebsiteUrl(si.TvdbCode, -1, true) + "\">" + si.ShowName +
                    "</A>" + seasText + "</h1>";

            DirFilesCache dfc = new DirFilesCache();

            foreach (ProcessedEpisode ei in eis)
            {
                string epl = ei.NumsAsString();

                string episodeUrl = TheTVDB.WebsiteUrl(ei.SeriesId, ei.SeasonId, ei.EpisodeId);

                body += "<A href=\"" + episodeUrl + "\" name=\"ep" + epl + "\">"; // anchor
                if (si.DvdOrder && snum == 0)
                {
                    body += "<b>" + ei.Name + "</b>";
                }
                else
                {
                    body += "<b>" + HttpUtility.HtmlEncode(CustomEpisodeName.NameForNoExt(ei, CustomEpisodeName.OldNStyle(6))) +
                            "</b>";
                }

                body += "</A>"; // anchor
                if (si.UseSequentialMatch && ei.OverallNumber != -1)
                {
                    body += " (#" + ei.OverallNumber + ")";
                }

                List <FileInfo> fl = dfc.FindEpOnDisk(ei);
                if (fl.Count > 0)
                {
                    foreach (FileInfo fi in fl)
                    {
                        string urlFilename = HttpUtility.UrlEncode(fi.FullName);
                        body += $" <A HREF=\"watch://{urlFilename}\" class=\"search\">Watch</A>";
                        body += $" <A HREF=\"explore://{urlFilename}\" class=\"search\">Show in Explorer</A>";
                    }
                }
                else
                {
                    body += " <A HREF=\"" + TVSettings.Instance.BTSearchURL(ei) + "\" class=\"search\">Search</A>";
                }

                DateTime?dt = ei.GetAirDateDt(true);
                if (dt != null && dt.Value.CompareTo(DateTime.MaxValue) != 0)
                {
                    body += "<p>" + dt.Value.ToShortDateString() + " (" + ei.HowLong() + ")";
                }

                body += "<p><p>";

                if (TVSettings.Instance.ShowEpisodePictures ||
                    TVSettings.Instance.HideMyShowsSpoilers && ei.HowLong() != "Aired")
                {
                    body += "<table><tr>";
                    body += "<td width=100% valign=top>" + GetOverview(ei) + "</td><td width=300 height=225>";
                    // 300x168 / 300x225
                    if (!string.IsNullOrEmpty(ei.Filename))
                    {
                        body += "<img src=" + TheTVDB.GetImageURL(ei.Filename) + ">";
                    }

                    body += "</td></tr></table>";
                }
                else
                {
                    body += GetOverview(ei);
                }

                body += "<p><hr><p>";
            } // for each episode in this season

            return(body);
        }
        private static void AppendEpisode([NotNull] this StringBuilder sb, [NotNull] ProcessedEpisode ep, [CanBeNull] IReadOnlyCollection <FileInfo> fl, Color backgroundColour)
        {
            string stars          = StarRating(ep.EpisodeRating);
            string episodeUrl     = TheTVDB.WebsiteUrl(ep.SeriesId, ep.SeasonId, ep.EpisodeId);
            bool   ratingIsNumber = float.TryParse(ep.EpisodeRating, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, CultureInfo.CreateSpecificCulture("en-US"), out float rating);
            string siteRating     = ratingIsNumber && rating > 0
                ? rating + "/10" + AddRatingCount(ep.SiteRatingCount ?? 0)
                : "";

            string imdbLink       = string.IsNullOrWhiteSpace(ep.ImdbCode) ? string.Empty : "http://www.imdb.com/title/" + ep.ImdbCode;
            string productionCode = string.IsNullOrWhiteSpace(ep.ProductionCode)
                ? string.Empty
                : "Production Code <br/>" + ep.ProductionCode;

            string episodeDescriptor = CustomEpisodeName.NameForNoExt(ep, CustomEpisodeName.OldNStyle(6)); // may need to include (si.DVDOrder && snum == 0)? ep.Name:
            string writersHtml       = string.IsNullOrWhiteSpace(ep.Writer) ? string.Empty : "<b>Writers:</b> " + string.Join(", ", ep.Writers);
            string directorsHtml     = string.IsNullOrWhiteSpace(ep.EpisodeDirector) ? string.Empty : "<b>Directors:</b> " + string.Join(", ", ep.Directors);
            string guestHtml         = string.IsNullOrWhiteSpace(ep.EpisodeGuestStars) ? string.Empty : "<b>Guest Stars:</b> " + string.Join(", ", ep.GuestStars);
            string possibleBreak1    = string.IsNullOrWhiteSpace(writersHtml) || string.IsNullOrWhiteSpace(directorsHtml)
                ? string.Empty
                : "<br />";
            string possibleBreak2 = string.IsNullOrWhiteSpace(writersHtml) && string.IsNullOrWhiteSpace(directorsHtml) || string.IsNullOrWhiteSpace(guestHtml)
                ? string.Empty
                : "<br />";

            string searchButton = (fl is null || fl.Count == 0) && ep.HasAired()
                ? CreateButton(TVSettings.Instance.BTSearchURL(ep), "<i class=\"fas fa-search\"></i>", "Search for Torrent...")
                : string.Empty;

            string viewButton     = string.Empty;
            string explorerButton = string.Empty;

            if (fl != null)
            {
                foreach (string urlFilename in fl.Select(fi => Uri.EscapeDataString(fi.FullName)))
                {
                    viewButton     += CreateButton($"{UI.WATCH_PROXY}{urlFilename}", "<i class=\"far fa-eye\"></i>", "Watch Now");
                    explorerButton += CreateButton($"{UI.EXPLORE_PROXY}{urlFilename}", "<i class=\"far fa-folder-open\"></i>", "Open Containing Folder");
                }
            }

            string tvdbButton = CreateButton(episodeUrl, "TVDB.com", "View on TVDB");
            string imdbButton = CreateButton(imdbLink, "IMDB.com", "View on IMDB");
            string tvButton   = CreateButton(ep.ShowUrl, "TV.com", "View on TV.com");

            sb.AppendLine($@"
                <div class=""card card-body"" style=""background-color:{backgroundColour.HexColour()}"">
                 <div class=""row"">
                  <div class=""col-md-5"">{ep.ScreenShotHtml()}</div>
                   <div class=""col-md-7 d-flex flex-column"">
                    <div class=""row"">
                     <div class=""col-md-8""><h2>{episodeDescriptor}</h2></div>
                     <div class=""col-md-4 text-right"">{ep.DateDetailsHtml()}</div>
                    </div>
				   <div>
                    <blockquote>
                     {writersHtml}{possibleBreak1}
                     {directorsHtml}{possibleBreak2}
                     {guestHtml}
                    </blockquote>
                   </div>
                   <div><p class=""lead"">{ep.HiddenOverview()}</p></div>
                   <div>
                    {searchButton}
                    {viewButton}
                    {explorerButton}
                    {tvdbButton}
                    {imdbButton}
                    {tvButton}
                   </div>
		           <div class=""row align-items-bottom flex-grow-1"">
                    <div class=""col-md-6 align-self-end"">{stars}<br>{siteRating}</div>
                    <div class=""col-md-6 align-self-end text-right"">{productionCode}</div>
                   </div>
                  </div>
                 </div>
                </div>");
        }