Compare commits
4 Commits
4c04378080
...
developmen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d20c116da | ||
|
|
a849b7524d | ||
|
|
bf957436f0 | ||
|
|
16343c9a56 |
@@ -3,6 +3,7 @@
|
||||
|
||||
@inject ISnackbar Snackbar
|
||||
@inject ClientService ClientService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<MudText>Video data</MudText>
|
||||
<MudStack Row Spacing="2">
|
||||
@@ -12,5 +13,5 @@
|
||||
<MudTextField Label="Video id" @bind-Value="@_videoId"/>
|
||||
</MudStack>
|
||||
<MudStack>
|
||||
<MudButton OnClick="GetDataAsync">Get data</MudButton>
|
||||
<MudButton OnClick="NavigateToVideo">Get data</MudButton>
|
||||
</MudStack>
|
||||
@@ -16,7 +16,7 @@ public partial class DevelopmentVideo : ComponentBase
|
||||
return !searchResults.IsSuccess ? [] : searchResults.Value;
|
||||
}
|
||||
|
||||
private async Task GetDataAsync(MouseEventArgs obj)
|
||||
private void NavigateToVideo(MouseEventArgs obj)
|
||||
{
|
||||
if (_selectedClient == null)
|
||||
{
|
||||
@@ -35,22 +35,6 @@ public partial class DevelopmentVideo : ComponentBase
|
||||
Snackbar.Add("Video ID needs to have an length of 11 chars!", Severity.Warning);
|
||||
}
|
||||
|
||||
var clientResult = await ClientService.LoadClientByIdAsync(_selectedClient.Id);
|
||||
if (!clientResult.IsSuccess)
|
||||
{
|
||||
Snackbar.Add(clientResult.Error?.Description ?? $"Failed to get client with id: {_selectedClient.Id}", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var ytClient = clientResult.Value;
|
||||
var videoResult = await ytClient.GetVideoByIdAsync(_videoId);
|
||||
if (!videoResult.IsSuccess)
|
||||
{
|
||||
Snackbar.Add(videoResult.Error?.Description ?? $"Failed to load video: {_videoId}", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var ytVideo = videoResult.Value;
|
||||
Snackbar.Add($"Loaded video {ytVideo.Title}", Severity.Success);
|
||||
NavigationManager.NavigateTo($"/video/{_videoId}?clientId={_selectedClient.Id}");
|
||||
}
|
||||
}
|
||||
244
Manager.App/Components/Pages/Video.razor
Normal file
244
Manager.App/Components/Pages/Video.razor
Normal file
@@ -0,0 +1,244 @@
|
||||
@page "/Video/{VideoId}"
|
||||
@using Manager.App.Services.System
|
||||
|
||||
@inject ISnackbar Snackbar
|
||||
@inject ClientService ClientService
|
||||
@inject CacheService Cache
|
||||
|
||||
<ForcedLoadingOverlay Visible="_loading"/>
|
||||
@if (!_loading && _video != null)
|
||||
{
|
||||
<MudStack Spacing="2">
|
||||
<MudCard>
|
||||
@{
|
||||
var thumbnailUrl = _video.Thumbnails.OrderByDescending(t => t.Width).FirstOrDefault()?.Url;
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(thumbnailUrl))
|
||||
{
|
||||
<MudCardMedia Image="@Cache.CreateCacheUrl(thumbnailUrl)" Height="500"/>
|
||||
}
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.h5">@_video.Title</MudText>
|
||||
<MudText Typo="Typo.body2">@_video.Description</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
<MudExpansionPanels MultiExpansion>
|
||||
<MudExpansionPanel Text="Info" Expanded>
|
||||
<MudStack Spacing="2" Row Wrap="Wrap.Wrap">
|
||||
@* Info *@
|
||||
<MudSimpleTable Bordered Dense Elevation="0" Outlined Square Hover>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Video ID:</td>
|
||||
<td>@_video.VideoId</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Title:</td>
|
||||
<td>@_video.Title</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Description:</td>
|
||||
<td>@_video.Description</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HashTags:</td>
|
||||
<td>@foreach (var hashtag in _video.HashTags)
|
||||
{
|
||||
<MudChip T="string" Variant="Variant.Text" Color="Color.Info">@hashtag</MudChip>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>View count:</td>
|
||||
<td>@_video.ViewCount</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Like count:</td>
|
||||
<td>@_video.LikeCount</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Channel ID:</td>
|
||||
<td>@_video.ChannelId</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Author:</td>
|
||||
<td>@_video.Author</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Playability status:</td>
|
||||
<td>@_video.PlayabilityStatus</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Length seconds:</td>
|
||||
<td>@_video.LengthSeconds</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Keywords:</td>
|
||||
<td>@foreach (var keyword in _video.Keywords)
|
||||
{
|
||||
<MudChip T="string" Variant="Variant.Text">@keyword</MudChip>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Publish date:</td>
|
||||
<td>@_video.PublishDate</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload date:</td>
|
||||
<td>@_video.UploadDate</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Category:</td>
|
||||
<td>@_video.Category</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
@* Boolean values *@
|
||||
<MudSimpleTable Bordered Dense Elevation="0" Outlined Square Hover>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Is owner viewing:</td>
|
||||
<td>@_video.IsOwnerViewing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Allow rating:</td>
|
||||
<td>@_video.AllowRating</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Is crawlable:</td>
|
||||
<td>@_video.IsCrawlable</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Is private:</td>
|
||||
<td>@_video.IsPrivate</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Is unplugged corpus:</td>
|
||||
<td>@_video.IsUnpluggedCorpus</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Is live:</td>
|
||||
<td>@_video.IsLive</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Is family save:</td>
|
||||
<td>@_video.IsFamilySave</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Is unlisted:</td>
|
||||
<td>@_video.IsUnlisted</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Has Ypc metadata:</td>
|
||||
<td>@_video.HasYpcMetadata</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Is shorts eligible:</td>
|
||||
<td>@_video.IsShortsEligible</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudStack>
|
||||
</MudExpansionPanel>
|
||||
<MudExpansionPanel Text="Streaming data">
|
||||
@if (_video.StreamingData == null)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">No streaming data available!</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudStack Spacing="2" Row Wrap="Wrap.Wrap">
|
||||
<MudStack>
|
||||
<MudText Typo="Typo.h5">Adaptive Formats</MudText>
|
||||
<MudTable Items="@_video.StreamingData.AdaptiveFormats">
|
||||
<HeaderContent>
|
||||
<MudTh>Id</MudTh>
|
||||
<MudTh>Mime type</MudTh>
|
||||
<MudTh>Bitrate</MudTh>
|
||||
<MudTh>Resolution</MudTh>
|
||||
<MudTh>Last modified (UNIX epoch)</MudTh>
|
||||
<MudTh>Quality</MudTh>
|
||||
<MudTh>FPS</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.Itag</MudTd>
|
||||
<MudTd>@context.MimeType</MudTd>
|
||||
<MudTd>@context.Bitrate</MudTd>
|
||||
<MudTd>@($"{context.Width}x{context.Height}")</MudTd>
|
||||
<MudTd>@context.LastModified</MudTd>
|
||||
<MudTd>@context.Quality</MudTd>
|
||||
<MudTd>@context.Fps</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudStack>
|
||||
<MudStack>
|
||||
<MudText Typo="Typo.h5">Formats</MudText>
|
||||
<MudTable Items="@_video.StreamingData.Formats">
|
||||
<HeaderContent>
|
||||
<MudTh>Id</MudTh>
|
||||
<MudTh>Mime type</MudTh>
|
||||
<MudTh>Bitrate</MudTh>
|
||||
<MudTh>Resolution</MudTh>
|
||||
<MudTh>Last modified (UNIX epoch)</MudTh>
|
||||
<MudTh>Quality</MudTh>
|
||||
<MudTh>FPS</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.Itag</MudTd>
|
||||
<MudTd>@context.MimeType</MudTd>
|
||||
<MudTd>@context.Bitrate</MudTd>
|
||||
<MudTd>@($"{context.Width}x{context.Height}")</MudTd>
|
||||
<MudTd>@context.LastModified</MudTd>
|
||||
<MudTd>@context.Quality</MudTd>
|
||||
<MudTd>@context.Fps</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
}
|
||||
</MudExpansionPanel>
|
||||
<MudExpansionPanel Text="Player config">
|
||||
@if (_video.PlayerConfig == null)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">No player config available!</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudSimpleTable Bordered Dense Elevation="0" Outlined Square Hover>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Audio loudness DB:</td>
|
||||
<td>@_video.PlayerConfig.AudioLoudnessDb</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Audio perceptual loudness DB:</td>
|
||||
<td>@_video.PlayerConfig.AudioPerceptualLoudnessDb</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Audio enable per format loudness:</td>
|
||||
<td>@_video.PlayerConfig.AudioLoudnessDb</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Max bitrate:</td>
|
||||
<td>@_video.PlayerConfig.MaxBitrate</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Max read ahead time MS:</td>
|
||||
<td>@_video.PlayerConfig.MaxReadAheadMediaTimeMs</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Min read ahead time MS:</td>
|
||||
<td>@_video.PlayerConfig.MinReadAheadMediaTimeMs</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Read ahead growth rate MS:</td>
|
||||
<td>@_video.PlayerConfig.ReadAheadGrowthRateMs</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
}
|
||||
</MudExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
</MudStack>
|
||||
}
|
||||
48
Manager.App/Components/Pages/Video.razor.cs
Normal file
48
Manager.App/Components/Pages/Video.razor.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Manager.YouTube.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Manager.App.Components.Pages;
|
||||
|
||||
public partial class Video : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public required string VideoId { get; set; }
|
||||
|
||||
[SupplyParameterFromQuery(Name = "clientId")]
|
||||
public string ClientId { get; set; } = "";
|
||||
|
||||
private bool _loading = true;
|
||||
private YouTubeVideo? _video;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(VideoId))
|
||||
{
|
||||
Snackbar.Add("Video id is null or empty!", Severity.Error);
|
||||
_loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var clientResult = await ClientService.LoadClientByIdAsync(ClientId);
|
||||
if (!clientResult.IsSuccess)
|
||||
{
|
||||
Snackbar.Add(clientResult.Error?.Description ?? "Failed to load client!", Severity.Error);
|
||||
_loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var client = clientResult.Value;
|
||||
|
||||
var videoResult = await client.GetVideoByIdAsync(VideoId);
|
||||
if (!videoResult.IsSuccess)
|
||||
{
|
||||
Snackbar.Add(videoResult.Error?.Description ?? "Failed to get video.", Severity.Error);
|
||||
_loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_video = videoResult.Value;
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
@@ -31,11 +31,10 @@ public class ClientService(IServiceScopeFactory scopeFactory, ILogger<ClientServ
|
||||
}
|
||||
}
|
||||
|
||||
private async void CancellationRequested()
|
||||
private void CancellationRequested()
|
||||
{
|
||||
foreach (var client in _loadedClients)
|
||||
{
|
||||
await SaveClientAsync(client);
|
||||
client.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ using DotBased.Monads;
|
||||
|
||||
namespace Manager.YouTube.Interpreter;
|
||||
|
||||
public class JavaScriptEngineManager
|
||||
public static class JavaScriptEngineManager
|
||||
{
|
||||
private readonly PlayerEngineCollection _engines = [];
|
||||
private static readonly PlayerEngineCollection Engines = [];
|
||||
|
||||
public async Task<Result<PlayerEngine>> GetPlayerEngine(string playerUrl)
|
||||
public static async Task<Result<PlayerEngine>> GetPlayerEngine(string playerUrl)
|
||||
{
|
||||
if (string.IsNullOrEmpty(playerUrl))
|
||||
{
|
||||
@@ -16,7 +16,7 @@ public class JavaScriptEngineManager
|
||||
|
||||
var version = GetScriptVersion(playerUrl);
|
||||
|
||||
if (_engines.TryGetValue(version, out var engine))
|
||||
if (Engines.TryGetValue(version, out var engine))
|
||||
{
|
||||
return engine;
|
||||
}
|
||||
|
||||
13
Manager.YouTube/Models/Playlist/PlaylistVideo.cs
Normal file
13
Manager.YouTube/Models/Playlist/PlaylistVideo.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Manager.YouTube.Models.Innertube;
|
||||
|
||||
namespace Manager.YouTube.Models.Playlist;
|
||||
|
||||
public class PlaylistVideo
|
||||
{
|
||||
public required string VideoId { get; set; }
|
||||
public List<WebImage> Thumbnails { get; set; } = [];
|
||||
public required string Title { get; set; }
|
||||
public required string Author { get; set; }
|
||||
public long LengthSeconds { get; set; }
|
||||
public bool IsPlayable { get; set; }
|
||||
}
|
||||
19
Manager.YouTube/Models/YouTubePlaylist.cs
Normal file
19
Manager.YouTube/Models/YouTubePlaylist.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Manager.YouTube.Models.Playlist;
|
||||
|
||||
namespace Manager.YouTube.Models;
|
||||
|
||||
public class YouTubePlaylist
|
||||
{
|
||||
public required string Id { get; set; }
|
||||
public required string Title { get; set; }
|
||||
public required string Description { get; set; }
|
||||
public required string Owner { get; set; }
|
||||
public required string OwnerId { get; set; }
|
||||
public bool NoIndex { get; set; }
|
||||
public bool Unlisted { get; set; }
|
||||
public bool CanReorder { get; set; }
|
||||
public bool IsEditable { get; set; }
|
||||
public List<PlaylistVideo> Videos { get; set; } = [];
|
||||
|
||||
public string? ContinuationToken { get; set; }
|
||||
}
|
||||
@@ -9,7 +9,6 @@ using Manager.YouTube.Models;
|
||||
using Manager.YouTube.Models.Innertube;
|
||||
using Manager.YouTube.Parsers;
|
||||
using Manager.YouTube.Parsers.Json;
|
||||
using Manager.YouTube.Util.Cipher;
|
||||
|
||||
namespace Manager.YouTube;
|
||||
|
||||
@@ -114,7 +113,7 @@ public sealed class YouTubeClient : IDisposable
|
||||
return videoParseResult;
|
||||
}
|
||||
|
||||
await DecipherSignatures(videoParseResult.Value, state);
|
||||
//await DecipherSignaturesAsync(videoParseResult.Value, state);
|
||||
|
||||
return videoParseResult.Value;
|
||||
}
|
||||
@@ -175,10 +174,10 @@ public sealed class YouTubeClient : IDisposable
|
||||
|
||||
public async Task<Result> RotateCookiesPageAsync(string origin = NetworkService.Origin, int ytPid = 1)
|
||||
{
|
||||
/*if (IsAnonymous)
|
||||
if (IsAnonymous)
|
||||
{
|
||||
return ResultError.Fail("Anonymous clients cannot rotate cookies!");
|
||||
}*/
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(origin))
|
||||
{
|
||||
@@ -386,7 +385,7 @@ public sealed class YouTubeClient : IDisposable
|
||||
return ResultError.Fail("Failed to get datasyncIds! Client not logged in.");
|
||||
}
|
||||
|
||||
private async Task DecipherSignatures(YouTubeVideo video, ClientState state)
|
||||
/*private async Task DecipherSignaturesAsync(YouTubeVideo video, ClientState state)
|
||||
{
|
||||
var streamingData = video.StreamingData;
|
||||
if (streamingData == null)
|
||||
@@ -394,25 +393,40 @@ public sealed class YouTubeClient : IDisposable
|
||||
_logger.Debug("No streaming data available, skipping decipher.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(state.PlayerJsUrl))
|
||||
{
|
||||
_logger.Warning("No player js url found.");
|
||||
}
|
||||
|
||||
var formatsWithCipher = streamingData.Formats.Concat(streamingData.AdaptiveFormats).Where(x => !string.IsNullOrWhiteSpace(x.SignatureCipher)).ToList();
|
||||
if (formatsWithCipher.Count == 0)
|
||||
{
|
||||
_logger.Debug("Skipping decipher, no signatures found to decipher.");
|
||||
_logger.Debug("Skipping signature decipher, no signatures found to decipher.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(streamingData.ServerAbrStreamingUrl))
|
||||
{
|
||||
_logger.Warning("No ABR streaming url available.");
|
||||
}
|
||||
|
||||
var abrStreamUri = new Uri(streamingData.ServerAbrStreamingUrl);
|
||||
var queries = HttpUtility.ParseQueryString(abrStreamUri.Query);
|
||||
var nSig = queries.Get("n");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(nSig))
|
||||
{
|
||||
_logger.Warning("No N signature found.");
|
||||
}
|
||||
|
||||
/*var jsEngineResult = await JavaScriptEngineManager.GetPlayerEngine(state.PlayerJsUrl ?? "");
|
||||
if (!jsEngineResult.IsSuccess)
|
||||
{
|
||||
_logger.Warning(jsEngineResult.Error?.Description ?? "Failed to get player script engine.");
|
||||
return;
|
||||
}
|
||||
|
||||
var decipherDecoderResult = await CipherManager.GetDecoderAsync(state, this);
|
||||
if (!decipherDecoderResult.IsSuccess)
|
||||
{
|
||||
_logger.Warning(decipherDecoderResult.Error?.Description ?? "Failed to get the cipher decoder!");
|
||||
return;
|
||||
}
|
||||
var decoder = decipherDecoderResult.Value;
|
||||
|
||||
foreach (var format in formatsWithCipher)
|
||||
{
|
||||
format.Url = decoder.Decipher(format.SignatureCipher);
|
||||
}
|
||||
}
|
||||
var engine = jsEngineResult.Value;
|
||||
engine.InitializePlayer();#1#
|
||||
}*/
|
||||
}
|
||||
Reference in New Issue
Block a user