Crunchy-Downloader/CRD/Downloader/Crunchyroll/CrMusic.cs
Elwador d9813191ad - Added **Global Pause button** for the download queue [#418](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/418)
- Added **fallback for sync failures** [#407](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/407)
- Added **history setting** to remove non-existent series/episodes on refresh [#420](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/420)
- Added **movies to history**
- Added **queue persistence**
- Changed **download item state handling**
- Changed **download item removal processing**
- Made small changes to **font detection**
- Changed **rate limit error handling**
- Fixed issue where **files were not always cleaned up** for removed downloads
- Fixed **crash when the queue list was modified**
- Fixed **changelog parsing** not handling versions like `vX.X.X.X`, which caused changes to be re-added on every restart
2026-04-20 15:40:58 +02:00

199 lines
8.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using CRD.Utils;
using CRD.Utils.Http;
using CRD.Utils.Structs;
using CRD.Utils.Structs.Crunchyroll.Music;
namespace CRD.Downloader.Crunchyroll;
public class CrMusic{
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
public async Task<CrunchyMusicVideoList?> ParseFeaturedMusicVideoByIdAsync(string seriesId, string crLocale, bool forcedLang = false, bool updateHistory = false){
var musicVideos = await FetchMediaListAsync($"{ApiUrls.Content}/music/featured/{seriesId}", crLocale, forcedLang);
if (musicVideos.Data is{ Count: > 0 } && updateHistory && crunInstance.CrunOptions.HistoryIncludeCrArtists){
await crunInstance.History.UpdateWithMusicEpisodeList(musicVideos.Data);
}
return musicVideos;
}
public async Task<CrunchyMusicVideo?> ParseMusicVideoByIdAsync(string id, string crLocale, bool forcedLang = false, bool updateHistory = false){
var musicVideo = await ParseMediaByIdAsync(id, crLocale, forcedLang, "music/music_videos");
if (musicVideo != null && updateHistory && crunInstance.CrunOptions.HistoryIncludeCrArtists){
await crunInstance.History.UpdateWithMusicEpisodeList([musicVideo]);
}
return musicVideo;
}
public async Task<CrunchyMusicVideo?> ParseConcertByIdAsync(string id, string crLocale, bool forcedLang = false, bool updateHistory = false){
var concert = await ParseMediaByIdAsync(id, crLocale, forcedLang, "music/concerts");
if (concert != null){
concert.EpisodeType = EpisodeType.Concert;
if (updateHistory && crunInstance.CrunOptions.HistoryIncludeCrArtists){
await crunInstance.History.UpdateWithMusicEpisodeList([concert]);
}
}
return concert;
}
public async Task<CrunchyMusicVideoList?> ParseArtistMusicVideosByIdAsync(string artistId, string crLocale, bool forcedLang = false, bool updateHistory = false){
var musicVideos = await FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{artistId}/music_videos", crLocale, forcedLang);
if (updateHistory && crunInstance.CrunOptions.HistoryIncludeCrArtists){
await crunInstance.History.UpdateWithMusicEpisodeList(musicVideos.Data);
}
return musicVideos;
}
public async Task<CrunchyMusicVideoList?> ParseArtistConcertVideosByIdAsync(string artistId, string crLocale, bool forcedLang = false, bool updateHistory = false){
var concerts = await FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{artistId}/concerts", crLocale, forcedLang);
if (concerts.Data.Count > 0){
foreach (var crunchyConcertVideo in concerts.Data){
crunchyConcertVideo.EpisodeType = EpisodeType.Concert;
}
}
if (updateHistory && crunInstance.CrunOptions.HistoryIncludeCrArtists){
await crunInstance.History.UpdateWithMusicEpisodeList(concerts.Data);
}
return concerts;
}
public async Task<CrunchyMusicVideoList?> ParseArtistVideosByIdAsync(string? artistId, string crLocale, bool forcedLang = false, bool updateHistory = false){
if (string.IsNullOrEmpty(artistId)){
return new CrunchyMusicVideoList();
}
var musicVideosTask = FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{artistId}/music_videos", crLocale, forcedLang);
var concertsTask = FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{artistId}/concerts", crLocale, forcedLang);
await Task.WhenAll(musicVideosTask, concertsTask);
var musicVideos = await musicVideosTask;
var concerts = await concertsTask;
musicVideos.Total += concerts.Total;
if (concerts.Data.Count > 0){
foreach (var crunchyConcertVideo in concerts.Data){
crunchyConcertVideo.EpisodeType = EpisodeType.Concert;
}
musicVideos.Data.AddRange(concerts.Data);
}
if (updateHistory && crunInstance.CrunOptions.HistoryIncludeCrArtists){
await crunInstance.History.UpdateWithMusicEpisodeList(musicVideos.Data);
}
return musicVideos;
}
public async Task<CrArtist> ParseArtistByIdAsync(string id, string crLocale, bool forcedLang = false){
await crunInstance.CrAuthGuest.RefreshToken(true);
var query = CreateQueryParameters(crLocale, forcedLang);
var request = HttpClientReq.CreateRequestMessage($"{ApiUrls.Content}/music/artists/{id}", HttpMethod.Get, true, crunInstance.CrAuthGuest.Token?.access_token, query);
var response = await HttpClientReq.Instance.SendHttpRequest(request);
if (!response.IsOk){
Console.Error.WriteLine($"Request to {ApiUrls.Content}/music/artists/{id} failed");
return new CrArtist();
}
var artistList = Helpers.Deserialize<CrunchyArtistList>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings) ?? new CrunchyArtistList();
return artistList.Data.FirstOrDefault() ?? new CrArtist();
}
private async Task<CrunchyMusicVideo?> ParseMediaByIdAsync(string id, string crLocale, bool forcedLang, string endpoint){
var mediaList = await FetchMediaListAsync($"{ApiUrls.Content}/{endpoint}/{id}", crLocale, forcedLang);
switch (mediaList.Total){
case < 1:
return null;
case 1 when mediaList.Data.Count > 0:
return mediaList.Data.First();
default:
Console.Error.WriteLine($"Multiple items returned for endpoint {endpoint} with ID {id}");
return mediaList.Data.First();
}
}
private async Task<CrunchyMusicVideoList> FetchMediaListAsync(string url, string crLocale, bool forcedLang){
await crunInstance.CrAuthGuest.RefreshToken(true);
var query = CreateQueryParameters(crLocale, forcedLang);
var request = HttpClientReq.CreateRequestMessage(url, HttpMethod.Get, true, crunInstance.CrAuthGuest.Token?.access_token, query);
var response = await HttpClientReq.Instance.SendHttpRequest(request);
if (!response.IsOk){
Console.Error.WriteLine($"Request to {url} failed");
return new CrunchyMusicVideoList();
}
return Helpers.Deserialize<CrunchyMusicVideoList>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings) ?? new CrunchyMusicVideoList();
}
private NameValueCollection CreateQueryParameters(string crLocale, bool forcedLang){
var query = HttpUtility.ParseQueryString(string.Empty);
query["preferred_audio_language"] = "ja-JP";
if (!string.IsNullOrEmpty(crLocale)){
query["locale"] = crLocale;
if (forcedLang){
query["force_locale"] = crLocale;
}
}
return query;
}
public CrunchyEpMeta EpisodeMeta(CrunchyMusicVideo episodeP){
var images = (episodeP.Images?.Thumbnail ??[new Image{ Source = "/notFound.jpg" }]);
var epMeta = new CrunchyEpMeta();
epMeta.Data = new List<CrunchyEpMetaData>{ new(){ MediaId = episodeP.Id, Versions = null } };
epMeta.SeriesTitle = episodeP.GetSeriesTitle();
epMeta.SeasonTitle = episodeP.GetSeasonTitle();
epMeta.EpisodeNumber = episodeP.SequenceNumber + "";
epMeta.EpisodeTitle = episodeP.GetEpisodeTitle();
epMeta.SeasonId = episodeP.GetSeasonId();
epMeta.Season = "";
epMeta.SeriesId = episodeP.GetSeriesId();
epMeta.AbsolutEpisodeNumberE = "";
epMeta.Image = images.FirstOrDefault()?.Source ?? string.Empty;
epMeta.ImageBig = images.FirstOrDefault()?.Source ?? string.Empty;
epMeta.DownloadProgress = new DownloadProgress(){
State = DownloadState.Queued,
Percent = 0,
Time = 0,
DownloadSpeedBytes = 0
};
epMeta.AvailableSubs = new List<string>();
epMeta.Description = episodeP.Description;
epMeta.Music = true;
epMeta.Hslang = CrunchyrollManager.Instance.CrunOptions.Hslang;
return epMeta;
}
}