|
|
|
@ -16,8 +16,6 @@ using System.Linq;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using MediaBrowser.Model.Extensions;
|
|
|
|
|
using MediaBrowser.Controller.Entities.TV;
|
|
|
|
|
using MediaBrowser.Controller.Providers;
|
|
|
|
|
using MediaBrowser.Model.Entities;
|
|
|
|
|
|
|
|
|
|
namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
@ -32,9 +30,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
|
|
|
|
|
private const string ApiUrl = "https://json.schedulesdirect.org/20141201";
|
|
|
|
|
|
|
|
|
|
private readonly Dictionary<string, Dictionary<string, ScheduleDirect.Station>> _channelPairingCache =
|
|
|
|
|
new Dictionary<string, Dictionary<string, ScheduleDirect.Station>>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
|
|
|
|
|
public SchedulesDirect(ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IApplicationHost appHost)
|
|
|
|
|
{
|
|
|
|
|
_logger = logger;
|
|
|
|
@ -74,33 +69,29 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
// Normalize incoming input
|
|
|
|
|
channelId = channelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I');
|
|
|
|
|
|
|
|
|
|
List<ProgramInfo> programsInfo = new List<ProgramInfo>();
|
|
|
|
|
|
|
|
|
|
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(token))
|
|
|
|
|
{
|
|
|
|
|
_logger.LogWarning("SchedulesDirect token is empty, returning empty program list");
|
|
|
|
|
return programsInfo;
|
|
|
|
|
|
|
|
|
|
return Enumerable.Empty<ProgramInfo>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var dates = GetScheduleRequestDates(startDateUtc, endDateUtc);
|
|
|
|
|
|
|
|
|
|
string stationID = channelId;
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation("Channel Station ID is: " + stationID);
|
|
|
|
|
List<ScheduleDirect.RequestScheduleForChannel> requestList =
|
|
|
|
|
new List<ScheduleDirect.RequestScheduleForChannel>()
|
|
|
|
|
_logger.LogInformation("Channel Station ID is: {ChannelID}", channelId);
|
|
|
|
|
var requestList = new List<ScheduleDirect.RequestScheduleForChannel>()
|
|
|
|
|
{
|
|
|
|
|
new ScheduleDirect.RequestScheduleForChannel()
|
|
|
|
|
{
|
|
|
|
|
stationID = stationID,
|
|
|
|
|
stationID = channelId,
|
|
|
|
|
date = dates
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var requestString = _jsonSerializer.SerializeToString(requestList);
|
|
|
|
|
_logger.LogDebug("Request string for schedules is: " + requestString);
|
|
|
|
|
_logger.LogDebug("Request string for schedules is: {RequestString}", requestString);
|
|
|
|
|
|
|
|
|
|
var httpOptions = new HttpRequestOptions()
|
|
|
|
|
{
|
|
|
|
@ -109,16 +100,17 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
CancellationToken = cancellationToken,
|
|
|
|
|
// The data can be large so give it some extra time
|
|
|
|
|
TimeoutMs = 60000,
|
|
|
|
|
LogErrorResponseBody = true
|
|
|
|
|
LogErrorResponseBody = true,
|
|
|
|
|
RequestContent = requestString
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
httpOptions.RequestHeaders["token"] = token;
|
|
|
|
|
|
|
|
|
|
httpOptions.RequestContent = requestString;
|
|
|
|
|
using (var response = await Post(httpOptions, true, info).ConfigureAwait(false))
|
|
|
|
|
using (StreamReader reader = new StreamReader(response.Content))
|
|
|
|
|
{
|
|
|
|
|
var dailySchedules = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Day>>(response.Content).ConfigureAwait(false);
|
|
|
|
|
_logger.LogDebug("Found {ScheduleCount} programs on {StationID} ScheduleDirect", dailySchedules.Count, stationID);
|
|
|
|
|
_logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
|
|
|
|
|
|
|
|
|
|
httpOptions = new HttpRequestOptions()
|
|
|
|
|
{
|
|
|
|
@ -132,14 +124,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
|
|
|
|
|
httpOptions.RequestHeaders["token"] = token;
|
|
|
|
|
|
|
|
|
|
List<string> programsID = new List<string>();
|
|
|
|
|
programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct().ToList();
|
|
|
|
|
var requestBody = "[\"" + string.Join("\", \"", programsID) + "\"]";
|
|
|
|
|
httpOptions.RequestContent = requestBody;
|
|
|
|
|
|
|
|
|
|
double wideAspect = 1.77777778;
|
|
|
|
|
var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct();
|
|
|
|
|
httpOptions.RequestContent = "[\"" + string.Join("\", \"", programsID) + "\"]";
|
|
|
|
|
|
|
|
|
|
using (var innerResponse = await Post(httpOptions, true, info).ConfigureAwait(false))
|
|
|
|
|
using (StreamReader innerReader = new StreamReader(innerResponse.Content))
|
|
|
|
|
{
|
|
|
|
|
var programDetails = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ProgramDetails>>(innerResponse.Content).ConfigureAwait(false);
|
|
|
|
|
var programDict = programDetails.ToDictionary(p => p.programID, y => y);
|
|
|
|
@ -150,8 +139,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
|
|
|
|
|
var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
var schedules = dailySchedules.SelectMany(d => d.programs);
|
|
|
|
|
foreach (ScheduleDirect.Program schedule in schedules)
|
|
|
|
|
List<ProgramInfo> programsInfo = new List<ProgramInfo>();
|
|
|
|
|
foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs))
|
|
|
|
|
{
|
|
|
|
|
//_logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
|
|
|
|
|
// " which corresponds to channel " + channelNumber + " and program id " +
|
|
|
|
@ -165,15 +154,17 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
{
|
|
|
|
|
var programEntry = programDict[schedule.programID];
|
|
|
|
|
|
|
|
|
|
var allImages = (images[imageIndex].data ?? new List<ScheduleDirect.ImageData>()).ToList();
|
|
|
|
|
var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase)).ToList();
|
|
|
|
|
var imagesWithoutText = allImages.Where(i => string.Equals(i.text, "no", StringComparison.OrdinalIgnoreCase)).ToList();
|
|
|
|
|
var allImages = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>();
|
|
|
|
|
var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
var imagesWithoutText = allImages.Where(i => string.Equals(i.text, "no", StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
|
|
|
|
double desiredAspect = 0.666666667;
|
|
|
|
|
const double desiredAspect = 0.666666667;
|
|
|
|
|
|
|
|
|
|
programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, true, desiredAspect) ??
|
|
|
|
|
GetProgramImage(ApiUrl, allImages, true, desiredAspect);
|
|
|
|
|
|
|
|
|
|
const double wideAspect = 1.77777778;
|
|
|
|
|
|
|
|
|
|
programEntry.thumbImage = GetProgramImage(ApiUrl, imagesWithText, true, wideAspect);
|
|
|
|
|
|
|
|
|
|
// Don't supply the same image twice
|
|
|
|
@ -193,18 +184,16 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
|
|
|
|
|
programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID]));
|
|
|
|
|
}
|
|
|
|
|
return programsInfo;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return programsInfo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int GetSizeOrder(ScheduleDirect.ImageData image)
|
|
|
|
|
{
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(image.height))
|
|
|
|
|
{
|
|
|
|
|
int value;
|
|
|
|
|
if (int.TryParse(image.height, out value))
|
|
|
|
|
if (int.TryParse(image.height, out int value))
|
|
|
|
|
{
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
@ -225,9 +214,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
{
|
|
|
|
|
channelNumber = map.atscMajor + "." + map.atscMinor;
|
|
|
|
|
}
|
|
|
|
|
channelNumber = channelNumber.TrimStart('0');
|
|
|
|
|
|
|
|
|
|
return channelNumber;
|
|
|
|
|
return channelNumber.TrimStart('0');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool IsMovie(ScheduleDirect.ProgramDetails programInfo)
|
|
|
|
@ -382,8 +370,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
|
|
|
|
|
if (details.movie != null)
|
|
|
|
|
{
|
|
|
|
|
int year;
|
|
|
|
|
if (!string.IsNullOrEmpty(details.movie.year) && int.TryParse(details.movie.year, out year))
|
|
|
|
|
if (!string.IsNullOrEmpty(details.movie.year) && int.TryParse(details.movie.year, out int year))
|
|
|
|
|
{
|
|
|
|
|
info.ProductionYear = year;
|
|
|
|
|
}
|
|
|
|
@ -414,18 +401,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
return date;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string GetProgramImage(string apiUrl, List<ScheduleDirect.ImageData> images, bool returnDefaultImage, double desiredAspect)
|
|
|
|
|
private string GetProgramImage(string apiUrl, IEnumerable<ScheduleDirect.ImageData> images, bool returnDefaultImage, double desiredAspect)
|
|
|
|
|
{
|
|
|
|
|
string url = null;
|
|
|
|
|
|
|
|
|
|
var matches = images;
|
|
|
|
|
|
|
|
|
|
matches = matches
|
|
|
|
|
.OrderBy(i => Math.Abs(desiredAspect - GetApsectRatio(i)))
|
|
|
|
|
var match = images
|
|
|
|
|
.OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i)))
|
|
|
|
|
.ThenByDescending(GetSizeOrder)
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
var match = matches.FirstOrDefault();
|
|
|
|
|
.FirstOrDefault();
|
|
|
|
|
|
|
|
|
|
if (match == null)
|
|
|
|
|
{
|
|
|
|
@ -434,22 +415,21 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
|
|
|
|
|
var uri = match.uri;
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(uri))
|
|
|
|
|
if (string.IsNullOrWhiteSpace(uri))
|
|
|
|
|
{
|
|
|
|
|
if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
else if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
|
|
|
|
|
{
|
|
|
|
|
url = uri;
|
|
|
|
|
return uri;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
url = apiUrl + "/image/" + uri;
|
|
|
|
|
return apiUrl + "/image/" + uri;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//_logger.LogDebug("URL for image is : " + url);
|
|
|
|
|
return url;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private double GetApsectRatio(ScheduleDirect.ImageData i)
|
|
|
|
|
private double GetAspectRatio(ScheduleDirect.ImageData i)
|
|
|
|
|
{
|
|
|
|
|
int width = 0;
|
|
|
|
|
int height = 0;
|
|
|
|
@ -614,8 +594,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(savedToken.Name) && !string.IsNullOrEmpty(savedToken.Value))
|
|
|
|
|
{
|
|
|
|
|
long ticks;
|
|
|
|
|
if (long.TryParse(savedToken.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out ticks))
|
|
|
|
|
if (long.TryParse(savedToken.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out long ticks))
|
|
|
|
|
{
|
|
|
|
|
// If it's under 24 hours old we can still use it
|
|
|
|
|
if (DateTime.UtcNow.Ticks - ticks < TimeSpan.FromHours(20).Ticks)
|
|
|
|
@ -685,8 +664,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var newToken = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false);
|
|
|
|
|
options.RequestHeaders["token"] = newToken;
|
|
|
|
|
options.RequestHeaders["token"] = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false);;
|
|
|
|
|
return await Post(options, false, providerInfo).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -724,8 +702,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var newToken = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false);
|
|
|
|
|
options.RequestHeaders["token"] = newToken;
|
|
|
|
|
options.RequestHeaders["token"] = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false);
|
|
|
|
|
return await Get(options, false, providerInfo).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -743,9 +720,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
//_logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " +
|
|
|
|
|
// httpOptions.RequestContent);
|
|
|
|
|
|
|
|
|
|
using (var responce = await Post(httpOptions, false, null).ConfigureAwait(false))
|
|
|
|
|
using (var response = await Post(httpOptions, false, null).ConfigureAwait(false))
|
|
|
|
|
{
|
|
|
|
|
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(responce.Content).ConfigureAwait(false);
|
|
|
|
|
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(response.Content).ConfigureAwait(false);
|
|
|
|
|
if (root.message == "OK")
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInformation("Authenticated with Schedules Direct token: " + root.token);
|
|
|
|
@ -828,7 +805,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
using (var httpResponse = await Get(options, false, null).ConfigureAwait(false))
|
|
|
|
|
{
|
|
|
|
|
using (var response = httpResponse.Content)
|
|
|
|
|
{
|
|
|
|
|
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Lineups>(response).ConfigureAwait(false);
|
|
|
|
@ -836,7 +812,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (HttpException ex)
|
|
|
|
|
{
|
|
|
|
|
// Apparently we're supposed to swallow this
|
|
|
|
@ -913,14 +888,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
var list = new List<ChannelInfo>();
|
|
|
|
|
|
|
|
|
|
using (var httpResponse = await Get(httpOptions, true, info).ConfigureAwait(false))
|
|
|
|
|
{
|
|
|
|
|
using (var response = httpResponse.Content)
|
|
|
|
|
{
|
|
|
|
|
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Channel>(response).ConfigureAwait(false);
|
|
|
|
|
_logger.LogInformation("Found " + root.map.Count + " channels on the lineup on ScheduleDirect");
|
|
|
|
|
_logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
|
|
|
|
|
_logger.LogInformation("Mapping Stations to Channel");
|
|
|
|
|
|
|
|
|
|
var allStations = root.stations ?? new List<ScheduleDirect.Station>();
|
|
|
|
|
var allStations = root.stations ?? Enumerable.Empty<ScheduleDirect.Station>();
|
|
|
|
|
|
|
|
|
|
foreach (ScheduleDirect.Map map in root.map)
|
|
|
|
|
{
|
|
|
|
@ -935,34 +909,22 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var name = channelNumber;
|
|
|
|
|
|
|
|
|
|
var channelInfo = new ChannelInfo
|
|
|
|
|
{
|
|
|
|
|
Id = station.stationID,
|
|
|
|
|
CallSign = station.callsign,
|
|
|
|
|
Number = channelNumber,
|
|
|
|
|
Name = name
|
|
|
|
|
Name = string.IsNullOrWhiteSpace(station.name) ? channelNumber : station.name
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (station != null)
|
|
|
|
|
{
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(station.name))
|
|
|
|
|
{
|
|
|
|
|
channelInfo.Name = station.name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
channelInfo.Id = station.stationID;
|
|
|
|
|
channelInfo.CallSign = station.callsign;
|
|
|
|
|
|
|
|
|
|
if (station.logo != null)
|
|
|
|
|
{
|
|
|
|
|
channelInfo.ImageUrl = station.logo.URL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list.Add(channelInfo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|