Stahovani i filmu, zprehledneni tabulky s kvalitami streamu. Oprava odkazu na obrazky.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
@@ -27,7 +28,7 @@ public class Metadata
|
||||
/// <param name="limit">Maximum returned items count.</param>
|
||||
/// <param name="cancel">Asynchronous cancellation.</param>
|
||||
/// <returns>Response.</returns>
|
||||
public static Task<FilterResponse?> SearchAsync(string expression, ItemOrder order = ItemOrder.Descending, FilterSortBy sort = FilterSortBy.Score, ItemType type = ItemType.All, int offset = 0, int limit = 0, CancellationToken cancel = default)
|
||||
public async static Task<FilterResponse?> SearchAsync(string expression, ItemOrder order = ItemOrder.Descending, FilterSortBy sort = FilterSortBy.Score, ItemType type = ItemType.All, int offset = 0, int limit = 0, CancellationToken cancel = default)
|
||||
{
|
||||
if (expression == null)
|
||||
throw new ArgumentNullException();
|
||||
@@ -39,7 +40,10 @@ public class Metadata
|
||||
UriBuilder uri = new UriBuilder(new Uri(ApiFilter, "search"));
|
||||
uri.Query = $"?access_token={AccessToken}&value={Uri.EscapeDataString(expression)}&order={ToString(order)}&sort={ToString(sort)}&type={ToString(type)}&from={offset.ToString()}&size={limit.ToString()}";
|
||||
|
||||
return Program._http.GetFromJsonAsync<FilterResponse>(uri.Uri, CreateFilterJsonOptions(), cancel);
|
||||
FilterResponse? result = await Program._http.GetFromJsonAsync<FilterResponse>(uri.Uri, CreateFilterJsonOptions(), cancel);
|
||||
if (result != null)
|
||||
result = FixFilterResponse(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -49,7 +53,7 @@ public class Metadata
|
||||
/// <param name="sort">Result ordering column.</param>
|
||||
/// <param name="cancel">Asynchronous cancellation.</param>
|
||||
/// <returns>Response.</returns>
|
||||
public static Task<FilterResponse?> ChildrenAsync(string parentId, FilterSortBy sort = FilterSortBy.Episode, CancellationToken cancel = default)
|
||||
public async static Task<FilterResponse?> ChildrenAsync(string parentId, FilterSortBy sort = FilterSortBy.Episode, CancellationToken cancel = default)
|
||||
{
|
||||
if (parentId == null)
|
||||
throw new ArgumentNullException();
|
||||
@@ -57,7 +61,10 @@ public class Metadata
|
||||
UriBuilder uri = new UriBuilder(new Uri(ApiFilter, "parent"));
|
||||
uri.Query = $"?access_token={AccessToken}&value={parentId}&sort={ToString(sort)}&size={MaxPageLimit.ToString()}";
|
||||
|
||||
return Program._http.GetFromJsonAsync<FilterResponse>(uri.Uri, CreateFilterJsonOptions(), cancel);
|
||||
FilterResponse? result = await Program._http.GetFromJsonAsync<FilterResponse>(uri.Uri, CreateFilterJsonOptions(), cancel);
|
||||
if (result != null)
|
||||
result = FixFilterResponse(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -85,14 +92,17 @@ public class Metadata
|
||||
/// <param name="suggestHeight">Requested image height that may however get rounded or completely ignored.</param>
|
||||
/// <param name="thumbUri">On success the thumbnail address.</param>
|
||||
/// <returns>True if thumbnail url got calculated, false otherwise.</returns>
|
||||
public static bool TryGetThumbnail(Uri imageUri, int suggestWidth, int suggestHeight, [NotNullWhen(true)] out Uri? thumbUri) {
|
||||
public static bool TryGetThumbnail(Uri imageUri, int suggestWidth, int suggestHeight, [NotNullWhen(true)] out Uri? thumbUri)
|
||||
{
|
||||
if (imageUri == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
switch (imageUri.Host) {
|
||||
|
||||
switch (imageUri.Host)
|
||||
{
|
||||
case "image.tmdb.org":
|
||||
const string TmdbOrigPrefix = "/t/p/original/";
|
||||
if (imageUri.AbsolutePath.StartsWith(TmdbOrigPrefix)) {
|
||||
if (imageUri.AbsolutePath.StartsWith(TmdbOrigPrefix))
|
||||
{
|
||||
UriBuilder ub = new UriBuilder(imageUri);
|
||||
ub.Path = "/t/p/w300_and_h450_bestv2/" + ub.Path.Substring(TmdbOrigPrefix.Length);
|
||||
thumbUri = ub.Uri;
|
||||
@@ -100,29 +110,69 @@ public class Metadata
|
||||
}
|
||||
break;
|
||||
|
||||
case "img.csfd.cz":
|
||||
if (imageUri.AbsolutePath.StartsWith("/files/images/film/posters/")) {
|
||||
// 140px, 280px, 420px
|
||||
int w;
|
||||
if (suggestWidth < 160)
|
||||
w = 140;
|
||||
else if (suggestWidth < 320)
|
||||
w = 280;
|
||||
else
|
||||
w = 420;
|
||||
UriBuilder ub = new UriBuilder(imageUri);
|
||||
ub.Host = "image.pmgstatic.com";
|
||||
ub.Path = "/cache/resized/w" + w.ToString(CultureInfo.InvariantCulture) + ub.Path;
|
||||
thumbUri = ub.Uri;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case "img.csfd.cz":
|
||||
if (imageUri.AbsolutePath.StartsWith("/files/images/film/posters/"))
|
||||
{
|
||||
// 140px, 280px, 420px
|
||||
int w;
|
||||
if (suggestWidth < 160)
|
||||
w = 140;
|
||||
else if (suggestWidth < 320)
|
||||
w = 280;
|
||||
else
|
||||
w = 420;
|
||||
UriBuilder ub = new UriBuilder(imageUri);
|
||||
ub.Host = "image.pmgstatic.com";
|
||||
ub.Path = "/cache/resized/w" + w.ToString(CultureInfo.InvariantCulture) + ub.Path;
|
||||
thumbUri = ub.Uri;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
thumbUri = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static FilterResponse FixFilterResponse(FilterResponse res)
|
||||
{
|
||||
if (res == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
// Fix image URLs as they may miss the https scheme
|
||||
if (res.hits != null && res.hits.hits != null)
|
||||
foreach (MediaInfo i in res.hits.hits)
|
||||
{
|
||||
if (i._source == null)
|
||||
continue;
|
||||
|
||||
if (i._source.cast != null)
|
||||
foreach (Cast j in i._source.cast)
|
||||
j.thumbnail = FixImageUrl(j.thumbnail);
|
||||
|
||||
if (i._source.i18n_info_labels != null)
|
||||
foreach (InfoLabelI18n j in i._source.i18n_info_labels) {
|
||||
if (j.art != null) {
|
||||
j.art.poster = FixImageUrl(j.art.poster);
|
||||
j.art.fanart = FixImageUrl(j.art.fanart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static string FixImageUrl(string url)
|
||||
{
|
||||
if (url != null && !url.StartsWith("http")) {
|
||||
if (url.StartsWith("//"))
|
||||
url = "https:" + url;
|
||||
else
|
||||
url = "https://" + url;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
private static string ToString(ItemOrder value)
|
||||
{
|
||||
switch (value)
|
||||
@@ -171,7 +221,8 @@ public class Metadata
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonSerializerOptions CreateFilterJsonOptions() {
|
||||
private static JsonSerializerOptions CreateFilterJsonOptions()
|
||||
{
|
||||
JsonSerializerOptions options = new JsonSerializerOptions();
|
||||
options.Converters.Add(new CustomDateTimeConverter());
|
||||
return options;
|
||||
|
||||
@@ -41,9 +41,11 @@ public abstract class BasicLayout
|
||||
w.WriteLine("<title>" + HttpUtility.HtmlEncode(title) + "</title>");
|
||||
w.WriteLine("<style>");
|
||||
w.WriteLine(".search-results { display: flex; flex-wrap: wrap; }");
|
||||
w.WriteLine(".media-episodes { display: flex; flex-direction: column; gap: 1em; }");
|
||||
w.WriteLine(".media-episodes, .media-streams { display: flex; flex-direction: column; gap: 1em; }");
|
||||
w.WriteLine(".search-results > .media-info { width: 25%; }");
|
||||
w.WriteLine(".media-episodes > .media-info { height: 12vh; display: flex; align-items: center; }");
|
||||
w.WriteLine(".media-streams > .media-stream:nth-child(even) { background-color: #CCC; }");
|
||||
w.WriteLine(".media-streams > .media-stream > a { display: flex; gap: 1em; align-items: center; }");
|
||||
w.WriteLine(".search-results .media-art { margin: 1em 1em 0em 1em }");
|
||||
w.WriteLine(".media-episodes .media-art { margin: 1em; width: 12vh; height: 12vh; }");
|
||||
w.WriteLine(".search-results .media-art img { width: 100%; }");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using StreamCinemaLib.API;
|
||||
using StreamCinemaWeb.Layouts;
|
||||
|
||||
namespace StreamCinemaWeb.Pages;
|
||||
@@ -20,5 +21,18 @@ public class MediaPage : BasicLayout
|
||||
protected override async Task RenderContentAsync(HttpRequest req, TextWriter w)
|
||||
{
|
||||
w.WriteLine("<h1>" + HttpUtility.HtmlEncode(_title) + "</h1>");
|
||||
w.WriteLine("<div>TODO</div>");
|
||||
w.WriteLine("<h2>Zvolte kvalitu</h2>");
|
||||
|
||||
StreamCinemaLib.API.Stream[]? res = await Metadata.StreamsAsync(_id, cancel: req.HttpContext.RequestAborted);
|
||||
|
||||
if (res == null || res.Length == 0)
|
||||
{
|
||||
w.WriteLine("<em>Nic nenalezeno</em>");
|
||||
}
|
||||
else
|
||||
{
|
||||
StreamsPage.RenderStreams(w, res, _title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,43 +31,48 @@ public class StreamsPage : BasicLayout
|
||||
}
|
||||
else
|
||||
{
|
||||
w.WriteLine("<div class=\"media-streams\">");
|
||||
foreach (StreamCinemaLib.API.Stream i in res)
|
||||
{
|
||||
w.WriteLine("<div class=\"media-stream\"><a href=\"/link/" + i.provider + "/" + HttpUtility.UrlEncode(i.ident) + "/" + HttpUtility.UrlEncode(i.name) + "/" + HttpUtility.UrlEncode(_title) + "\">");
|
||||
|
||||
w.WriteLine($"<div class=\"stream-basic\">{Math.Round((double)(i.size ?? 0) / (1024 * 1024 * 1024), 2)} GB ({i.date_added})</div>");
|
||||
|
||||
if (i.video != null && i.video.Count != 0)
|
||||
{
|
||||
StreamVideo a = i.video.First();
|
||||
string is3D = a.is3d ? " (3D)" : "";
|
||||
w.WriteLine($"<div class=\"video-channel\">{a.width}x{a.height} {a.codec}{is3D} {Math.Round((a.duration ?? 0) / 60)} min</div>");
|
||||
}
|
||||
|
||||
if (i.audio != null && i.audio.Count != 0)
|
||||
{
|
||||
w.WriteLine("<div class=\"audio-channels\">");
|
||||
foreach (StreamAudio j in i.audio)
|
||||
{
|
||||
w.WriteLine($"<div class=\"audio-channel\">{j.language} {j.codec} {j.channels}</div>");
|
||||
}
|
||||
w.WriteLine("</div>");
|
||||
}
|
||||
|
||||
if (i.subtitles != null && i.subtitles.Count != 0)
|
||||
{
|
||||
w.WriteLine("<div class=\"subtitle-channels\">");
|
||||
foreach (StreamSubtitle j in i.subtitles)
|
||||
{
|
||||
w.WriteLine($"<div class=\"subtitle-channel\">{j.language}</div>");
|
||||
}
|
||||
w.WriteLine("</div>");
|
||||
}
|
||||
|
||||
w.WriteLine("</a></div>");
|
||||
}
|
||||
w.WriteLine("</div>");
|
||||
RenderStreams(w, res, _title);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RenderStreams(TextWriter w, IEnumerable<StreamCinemaLib.API.Stream> streams, string title)
|
||||
{
|
||||
w.WriteLine("<div class=\"media-streams\">");
|
||||
foreach (StreamCinemaLib.API.Stream i in streams)
|
||||
{
|
||||
w.WriteLine("<div class=\"media-stream\"><a href=\"/link/" + i.provider + "/" + HttpUtility.UrlEncode(i.ident) + "/" + HttpUtility.UrlEncode(i.name) + "/" + HttpUtility.UrlEncode(title) + "\">");
|
||||
|
||||
w.WriteLine($"<div class=\"stream-basic\">{Math.Round((double)(i.size ?? 0) / (1024 * 1024 * 1024), 2)} GB ({i.date_added})</div>");
|
||||
|
||||
if (i.video != null && i.video.Count != 0)
|
||||
{
|
||||
StreamVideo a = i.video.First();
|
||||
string is3D = a.is3d ? " (3D)" : "";
|
||||
w.WriteLine($"<div class=\"video-channel\">{a.width}x{a.height} {a.codec}{is3D} {Math.Round((a.duration ?? 0) / 60)} min</div>");
|
||||
}
|
||||
|
||||
if (i.audio != null && i.audio.Count != 0)
|
||||
{
|
||||
w.WriteLine("<div class=\"audio-channels\">");
|
||||
foreach (StreamAudio j in i.audio)
|
||||
{
|
||||
w.WriteLine($"<div class=\"audio-channel\">{j.language} {j.codec} {j.channels}</div>");
|
||||
}
|
||||
w.WriteLine("</div>");
|
||||
}
|
||||
|
||||
if (i.subtitles != null && i.subtitles.Count != 0)
|
||||
{
|
||||
w.WriteLine("<div class=\"subtitle-channels\">");
|
||||
foreach (StreamSubtitle j in i.subtitles)
|
||||
{
|
||||
w.WriteLine($"<div class=\"subtitle-channel\">{j.language}</div>");
|
||||
}
|
||||
w.WriteLine("</div>");
|
||||
}
|
||||
|
||||
w.WriteLine("</a></div>");
|
||||
}
|
||||
w.WriteLine("</div>");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user