Stahovani i filmu, zprehledneni tabulky s kvalitami streamu. Oprava odkazu na obrazky.

This commit is contained in:
2024-11-10 11:32:24 +01:00
parent c2b2898452
commit e90ceea9c6
4 changed files with 136 additions and 64 deletions

View File

@@ -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;

View File

@@ -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%; }");

View File

@@ -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);
}
}
}

View File

@@ -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>");
}
}