Add - Added download path to download settings
Add - Added download path to history series and also to each season Fix - When adding an episode it always showed "Added to Queue" even if it wasn't added
This commit is contained in:
parent
cd4ceea38a
commit
3d74fa7667
|
@ -7,6 +7,7 @@ using System.Net.Http;
|
|||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Avalonia.Remote.Protocol.Input;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -14,10 +15,9 @@ using Newtonsoft.Json;
|
|||
namespace CRD.Downloader;
|
||||
|
||||
public class CrEpisode(){
|
||||
|
||||
private readonly Crunchyroll crunInstance = Crunchyroll.Instance;
|
||||
|
||||
public async Task<CrunchyEpisodeList?> ParseEpisodeById(string id,string locale){
|
||||
public async Task<CrunchyEpisode?> ParseEpisodeById(string id, string locale){
|
||||
if (crunInstance.CmsToken?.Cms == null){
|
||||
Console.Error.WriteLine("Missing CMS Access Token");
|
||||
return null;
|
||||
|
@ -43,217 +43,191 @@ public class CrEpisode(){
|
|||
return null;
|
||||
}
|
||||
|
||||
return epsidoe;
|
||||
if (epsidoe.Total == 1 && epsidoe.Data != null){
|
||||
return epsidoe.Data.First();
|
||||
}
|
||||
|
||||
Console.Error.WriteLine("Multiple episodes returned with one ID?");
|
||||
if (epsidoe.Data != null) return epsidoe.Data.First();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public async Task<CrunchySeriesList> EpisodeData(CrunchyEpisodeList dlEpisodes){
|
||||
public async Task<CrunchyRollEpisodeData> EpisodeData(CrunchyEpisode dlEpisode){
|
||||
bool serieshasversions = true;
|
||||
|
||||
Dictionary<string, EpisodeAndLanguage> episodes = new Dictionary<string, EpisodeAndLanguage>();
|
||||
// Dictionary<string, EpisodeAndLanguage> episodes = new Dictionary<string, EpisodeAndLanguage>();
|
||||
|
||||
if (dlEpisodes.Data != null){
|
||||
foreach (var episode in dlEpisodes.Data){
|
||||
CrunchyRollEpisodeData episode = new CrunchyRollEpisodeData();
|
||||
|
||||
if (crunInstance.CrunOptions.History){
|
||||
await crunInstance.CrHistory.UpdateWithEpisode(episode);
|
||||
}
|
||||
if (crunInstance.CrunOptions.History){
|
||||
await crunInstance.CrHistory.UpdateWithEpisode(dlEpisode);
|
||||
}
|
||||
|
||||
// Prepare the episode array
|
||||
EpisodeAndLanguage item;
|
||||
var seasonIdentifier = !string.IsNullOrEmpty(episode.Identifier) ? episode.Identifier.Split('|')[1] : $"S{episode.SeasonNumber}";
|
||||
var episodeKey = $"{seasonIdentifier}E{episode.Episode ?? (episode.EpisodeNumber + "")}";
|
||||
var seasonIdentifier = !string.IsNullOrEmpty(dlEpisode.Identifier) ? dlEpisode.Identifier.Split('|')[1] : $"S{dlEpisode.SeasonNumber}";
|
||||
episode.Key = $"{seasonIdentifier}E{dlEpisode.Episode ?? (dlEpisode.EpisodeNumber + "")}";
|
||||
episode.EpisodeAndLanguages = new EpisodeAndLanguage{
|
||||
Items = new List<CrunchyEpisode>(),
|
||||
Langs = new List<LanguageItem>()
|
||||
};
|
||||
|
||||
if (!episodes.ContainsKey(episodeKey)){
|
||||
item = new EpisodeAndLanguage{
|
||||
Items = new List<CrunchyEpisode>(),
|
||||
Langs = new List<LanguageItem>()
|
||||
};
|
||||
episodes[episodeKey] = item;
|
||||
} else{
|
||||
item = episodes[episodeKey];
|
||||
}
|
||||
|
||||
if (episode.Versions != null){
|
||||
foreach (var version in episode.Versions){
|
||||
// Ensure there is only one of the same language
|
||||
if (item.Langs.All(a => a.CrLocale != version.AudioLocale)){
|
||||
// Push to arrays if there are no duplicates of the same language
|
||||
item.Items.Add(episode);
|
||||
item.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == version.AudioLocale));
|
||||
}
|
||||
}
|
||||
} else{
|
||||
// Episode didn't have versions, mark it as such to be logged.
|
||||
serieshasversions = false;
|
||||
// Ensure there is only one of the same language
|
||||
if (item.Langs.All(a => a.CrLocale != episode.AudioLocale)){
|
||||
// Push to arrays if there are no duplicates of the same language
|
||||
item.Items.Add(episode);
|
||||
item.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == episode.AudioLocale));
|
||||
}
|
||||
if (dlEpisode.Versions != null){
|
||||
foreach (var version in dlEpisode.Versions){
|
||||
// Ensure there is only one of the same language
|
||||
if (episode.EpisodeAndLanguages.Langs.All(a => a.CrLocale != version.AudioLocale)){
|
||||
// Push to arrays if there are no duplicates of the same language
|
||||
episode.EpisodeAndLanguages.Items.Add(dlEpisode);
|
||||
episode.EpisodeAndLanguages.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == version.AudioLocale));
|
||||
}
|
||||
}
|
||||
} else{
|
||||
// Episode didn't have versions, mark it as such to be logged.
|
||||
serieshasversions = false;
|
||||
// Ensure there is only one of the same language
|
||||
if (episode.EpisodeAndLanguages.Langs.All(a => a.CrLocale != dlEpisode.AudioLocale)){
|
||||
// Push to arrays if there are no duplicates of the same language
|
||||
episode.EpisodeAndLanguages.Items.Add(dlEpisode);
|
||||
episode.EpisodeAndLanguages.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == dlEpisode.AudioLocale));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int specialIndex = 1;
|
||||
int epIndex = 1;
|
||||
|
||||
var keys = new List<string>(episodes.Keys); // Copying the keys to a new list to avoid modifying the collection while iterating.
|
||||
|
||||
foreach (var key in keys){
|
||||
EpisodeAndLanguage item = episodes[key];
|
||||
var isSpecial = !Regex.IsMatch(item.Items[0].Episode ?? string.Empty, @"^\d+$"); // Checking if the episode is not a number (i.e., special).
|
||||
string newKey;
|
||||
if (isSpecial && !string.IsNullOrEmpty(item.Items[0].Episode)){
|
||||
newKey = item.Items[0].Episode ?? "SP" + (specialIndex + " " + item.Items[0].Id);
|
||||
} else{
|
||||
newKey = $"{(isSpecial ? "SP" : 'E')}{(isSpecial ? (specialIndex + " " + item.Items[0].Id) : epIndex + "")}";
|
||||
}
|
||||
|
||||
episodes.Remove(key);
|
||||
episodes.Add(newKey, item);
|
||||
|
||||
if (isSpecial){
|
||||
specialIndex++;
|
||||
} else{
|
||||
epIndex++;
|
||||
}
|
||||
var isSpecial = !Regex.IsMatch(episode.EpisodeAndLanguages.Items[0].Episode ?? string.Empty, @"^\d+$"); // Checking if the episode is not a number (i.e., special).
|
||||
string newKey;
|
||||
if (isSpecial && !string.IsNullOrEmpty(episode.EpisodeAndLanguages.Items[0].Episode)){
|
||||
newKey = episode.EpisodeAndLanguages.Items[0].Episode ?? "SP" + (specialIndex + " " + episode.EpisodeAndLanguages.Items[0].Id);
|
||||
} else{
|
||||
newKey = $"{(isSpecial ? "SP" : 'E')}{(isSpecial ? (specialIndex + " " + episode.EpisodeAndLanguages.Items[0].Id) : episode.EpisodeAndLanguages.Items[0].Episode ?? epIndex + "")}";
|
||||
}
|
||||
|
||||
var specials = episodes.Where(e => e.Key.StartsWith("SP")).ToList();
|
||||
var normal = episodes.Where(e => e.Key.StartsWith("E")).ToList();
|
||||
episode.Key = newKey;
|
||||
|
||||
// Combining and sorting episodes with normal first, then specials.
|
||||
var sortedEpisodes = new Dictionary<string, EpisodeAndLanguage>(normal.Concat(specials));
|
||||
var seasonTitle = episode.EpisodeAndLanguages.Items.FirstOrDefault(a => !Regex.IsMatch(a.SeasonTitle, @"\(\w+ Dub\)")).SeasonTitle
|
||||
?? Regex.Replace(episode.EpisodeAndLanguages.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
||||
|
||||
foreach (var kvp in sortedEpisodes){
|
||||
var key = kvp.Key;
|
||||
var item = kvp.Value;
|
||||
var title = episode.EpisodeAndLanguages.Items[0].Title;
|
||||
var seasonNumber = Helpers.ExtractNumberAfterS(episode.EpisodeAndLanguages.Items[0].Identifier) ?? episode.EpisodeAndLanguages.Items[0].SeasonNumber.ToString();
|
||||
|
||||
var seasonTitle = item.Items.FirstOrDefault(a => !Regex.IsMatch(a.SeasonTitle, @"\(\w+ Dub\)")).SeasonTitle
|
||||
?? Regex.Replace(item.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
||||
var languages = episode.EpisodeAndLanguages.Items.Select((a, index) =>
|
||||
$"{(a.IsPremiumOnly ? "+ " : "")}{episode.EpisodeAndLanguages.Langs.ElementAtOrDefault(index).Name ?? "Unknown"}").ToArray(); //☆
|
||||
|
||||
var title = item.Items[0].Title;
|
||||
var seasonNumber = Helpers.ExtractNumberAfterS(item.Items[0].Identifier) ?? item.Items[0].SeasonNumber.ToString();
|
||||
Console.WriteLine($"[{episode.Key}] {seasonTitle} - Season {seasonNumber} - {title} [{string.Join(", ", languages)}]");
|
||||
|
||||
var languages = item.Items.Select((a, index) =>
|
||||
$"{(a.IsPremiumOnly ? "+ " : "")}{item.Langs.ElementAtOrDefault(index).Name ?? "Unknown"}").ToArray(); //☆
|
||||
|
||||
Console.WriteLine($"[{key}] {seasonTitle} - Season {seasonNumber} - {title} [{string.Join(", ", languages)}]");
|
||||
}
|
||||
|
||||
if (!serieshasversions){
|
||||
Console.WriteLine("Couldn\'t find versions on some episodes, fell back to old method.");
|
||||
Console.WriteLine("Couldn\'t find versions on episode, fell back to old method.");
|
||||
}
|
||||
|
||||
CrunchySeriesList crunchySeriesList = new CrunchySeriesList();
|
||||
crunchySeriesList.Data = sortedEpisodes;
|
||||
|
||||
crunchySeriesList.List = sortedEpisodes.Select(kvp => {
|
||||
var key = kvp.Key;
|
||||
var value = kvp.Value;
|
||||
var images = (value.Items[0].Images?.Thumbnail ?? new List<List<Image>>{ new List<Image>{ new Image{ Source = "/notFound.png" } } });
|
||||
var seconds = (int)Math.Floor(value.Items[0].DurationMs / 1000.0);
|
||||
return new Episode{
|
||||
E = key.StartsWith("E") ? key.Substring(1) : key,
|
||||
Lang = value.Langs.Select(a => a.Code).ToList(),
|
||||
Name = value.Items[0].Title,
|
||||
Season = Helpers.ExtractNumberAfterS(value.Items[0].Identifier) ?? value.Items[0].SeasonNumber.ToString(),
|
||||
SeriesTitle = Regex.Replace(value.Items[0].SeriesTitle, @"\(\w+ Dub\)", "").TrimEnd(),
|
||||
SeasonTitle = Regex.Replace(value.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd(),
|
||||
EpisodeNum = value.Items[0].EpisodeNumber?.ToString() ?? value.Items[0].Episode ?? "?",
|
||||
Id = value.Items[0].SeasonId,
|
||||
Img = images[images.Count / 2].FirstOrDefault().Source,
|
||||
Description = value.Items[0].Description,
|
||||
Time = $"{seconds / 60}:{seconds % 60:D2}" // Ensures two digits for seconds.
|
||||
};
|
||||
}).ToList();
|
||||
// crunchySeriesList.Data = sortedEpisodes;
|
||||
//
|
||||
//
|
||||
// var images = (episode.EpisodeAndLanguages.Items[0].Images?.Thumbnail ?? new List<List<Image>>{ new List<Image>{ new Image{ Source = "/notFound.png" } } });
|
||||
// var seconds = (int)Math.Floor(episode.EpisodeAndLanguages.Items[0].DurationMs / 1000.0);
|
||||
//
|
||||
// var newEpisode = new Episode{
|
||||
// E = episode.Key.StartsWith("E") ? episode.Key.Substring(1) : episode.Key,
|
||||
// Lang = episode.EpisodeAndLanguages.Langs.Select(a => a.Code).ToList(),
|
||||
// Name = episode.EpisodeAndLanguages.Items[0].Title,
|
||||
// Season = Helpers.ExtractNumberAfterS(episode.EpisodeAndLanguages.Items[0].Identifier) ?? episode.EpisodeAndLanguages.Items[0].SeasonNumber.ToString(),
|
||||
// SeriesTitle = Regex.Replace(episode.EpisodeAndLanguages.Items[0].SeriesTitle, @"\(\w+ Dub\)", "").TrimEnd(),
|
||||
// SeasonTitle = Regex.Replace(episode.EpisodeAndLanguages.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd(),
|
||||
// EpisodeNum = episode.EpisodeAndLanguages.Items[0].EpisodeNumber?.ToString() ?? episode.EpisodeAndLanguages.Items[0].Episode ?? "?",
|
||||
// Id = episode.EpisodeAndLanguages.Items[0].SeasonId,
|
||||
// Img = images[images.Count / 2].FirstOrDefault().Source,
|
||||
// Description = episode.EpisodeAndLanguages.Items[0].Description,
|
||||
// Time = $"{seconds / 60}:{seconds % 60:D2}" // Ensures two digits for seconds.
|
||||
// };
|
||||
//
|
||||
// CrunchySeriesList crunchySeriesList = new CrunchySeriesList();
|
||||
|
||||
return crunchySeriesList;
|
||||
return episode;
|
||||
}
|
||||
|
||||
public Dictionary<string, CrunchyEpMeta> EpisodeMeta(Dictionary<string, EpisodeAndLanguage> eps, List<string> dubLang){
|
||||
var ret = new Dictionary<string, CrunchyEpMeta>();
|
||||
public CrunchyEpMeta EpisodeMeta(CrunchyRollEpisodeData episodeP, List<string> dubLang){
|
||||
// var ret = new Dictionary<string, CrunchyEpMeta>();
|
||||
|
||||
var retMeta = new CrunchyEpMeta();
|
||||
|
||||
|
||||
foreach (var kvp in eps){
|
||||
var key = kvp.Key;
|
||||
var episode = kvp.Value;
|
||||
|
||||
for (int index = 0; index < episode.Items.Count; index++){
|
||||
var item = episode.Items[index];
|
||||
for (int index = 0; index < episodeP.EpisodeAndLanguages.Items.Count; index++){
|
||||
var item = episodeP.EpisodeAndLanguages.Items[index];
|
||||
|
||||
if (!dubLang.Contains(episode.Langs[index].CrLocale))
|
||||
continue;
|
||||
if (!dubLang.Contains(episodeP.EpisodeAndLanguages.Langs[index].CrLocale))
|
||||
continue;
|
||||
|
||||
item.HideSeasonTitle = true;
|
||||
if (string.IsNullOrEmpty(item.SeasonTitle) && !string.IsNullOrEmpty(item.SeriesTitle)){
|
||||
item.SeasonTitle = item.SeriesTitle;
|
||||
item.HideSeasonTitle = false;
|
||||
item.HideSeasonNumber = true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(item.SeasonTitle) && string.IsNullOrEmpty(item.SeriesTitle)){
|
||||
item.SeasonTitle = "NO_TITLE";
|
||||
item.SeriesTitle = "NO_TITLE";
|
||||
}
|
||||
|
||||
var epNum = key.StartsWith('E') ? key[1..] : key;
|
||||
var images = (item.Images?.Thumbnail ?? new List<List<Image>>{ new List<Image>{ new Image{ Source = "/notFound.png" } } });
|
||||
|
||||
Regex dubPattern = new Regex(@"\(\w+ Dub\)");
|
||||
|
||||
var epMeta = new CrunchyEpMeta();
|
||||
epMeta.Data = new List<CrunchyEpMetaData>{ new(){ MediaId = item.Id, Versions = item.Versions, IsSubbed = item.IsSubbed, IsDubbed = item.IsDubbed } };
|
||||
epMeta.SeriesTitle = episode.Items.FirstOrDefault(a => !dubPattern.IsMatch(a.SeriesTitle)).SeriesTitle ?? Regex.Replace(episode.Items[0].SeriesTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
||||
epMeta.SeasonTitle = episode.Items.FirstOrDefault(a => !dubPattern.IsMatch(a.SeasonTitle)).SeasonTitle ?? Regex.Replace(episode.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
||||
epMeta.EpisodeNumber = item.Episode;
|
||||
epMeta.EpisodeTitle = item.Title;
|
||||
epMeta.SeasonId = item.SeasonId;
|
||||
epMeta.Season = Helpers.ExtractNumberAfterS(item.Identifier) ?? item.SeasonNumber + "";
|
||||
epMeta.ShowId = item.SeriesId;
|
||||
epMeta.AbsolutEpisodeNumberE = epNum;
|
||||
epMeta.Image = images[images.Count / 2].FirstOrDefault().Source;
|
||||
epMeta.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = false,
|
||||
Done = false,
|
||||
Error = false,
|
||||
Percent = 0,
|
||||
Time = 0,
|
||||
DownloadSpeed = 0
|
||||
};
|
||||
epMeta.AvailableSubs = item.SubtitleLocales;
|
||||
if (episode.Langs.Count > 0){
|
||||
epMeta.SelectedDubs = dubLang
|
||||
.Where(language => episode.Langs.Any(epLang => epLang.CrLocale == language))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var epMetaData = epMeta.Data[0];
|
||||
if (!string.IsNullOrEmpty(item.StreamsLink)){
|
||||
epMetaData.Playback = item.StreamsLink;
|
||||
if (string.IsNullOrEmpty(item.Playback)){
|
||||
item.Playback = item.StreamsLink;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret.TryGetValue(key, out var epMe)){
|
||||
epMetaData.Lang = episode.Langs[index];
|
||||
epMe.Data?.Add(epMetaData);
|
||||
} else{
|
||||
epMetaData.Lang = episode.Langs[index];
|
||||
epMeta.Data[0] = epMetaData;
|
||||
ret.Add(key, epMeta);
|
||||
}
|
||||
|
||||
|
||||
// show ep
|
||||
item.SeqId = epNum;
|
||||
item.HideSeasonTitle = true;
|
||||
if (string.IsNullOrEmpty(item.SeasonTitle) && !string.IsNullOrEmpty(item.SeriesTitle)){
|
||||
item.SeasonTitle = item.SeriesTitle;
|
||||
item.HideSeasonTitle = false;
|
||||
item.HideSeasonNumber = true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(item.SeasonTitle) && string.IsNullOrEmpty(item.SeriesTitle)){
|
||||
item.SeasonTitle = "NO_TITLE";
|
||||
item.SeriesTitle = "NO_TITLE";
|
||||
}
|
||||
|
||||
var epNum = episodeP.Key.StartsWith('E') ? episodeP.Key[1..] : episodeP.Key;
|
||||
var images = (item.Images?.Thumbnail ?? new List<List<Image>>{ new List<Image>{ new Image{ Source = "/notFound.png" } } });
|
||||
|
||||
Regex dubPattern = new Regex(@"\(\w+ Dub\)");
|
||||
|
||||
var epMeta = new CrunchyEpMeta();
|
||||
epMeta.Data = new List<CrunchyEpMetaData>{ new(){ MediaId = item.Id, Versions = item.Versions, IsSubbed = item.IsSubbed, IsDubbed = item.IsDubbed } };
|
||||
epMeta.SeriesTitle = episodeP.EpisodeAndLanguages.Items.FirstOrDefault(a => !dubPattern.IsMatch(a.SeriesTitle)).SeriesTitle ?? Regex.Replace(episodeP.EpisodeAndLanguages.Items[0].SeriesTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
||||
epMeta.SeasonTitle = episodeP.EpisodeAndLanguages.Items.FirstOrDefault(a => !dubPattern.IsMatch(a.SeasonTitle)).SeasonTitle ?? Regex.Replace(episodeP.EpisodeAndLanguages.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
||||
epMeta.EpisodeNumber = item.Episode;
|
||||
epMeta.EpisodeTitle = item.Title;
|
||||
epMeta.SeasonId = item.SeasonId;
|
||||
epMeta.Season = Helpers.ExtractNumberAfterS(item.Identifier) ?? item.SeasonNumber + "";
|
||||
epMeta.ShowId = item.SeriesId;
|
||||
epMeta.AbsolutEpisodeNumberE = epNum;
|
||||
epMeta.Image = images[images.Count / 2].FirstOrDefault().Source;
|
||||
epMeta.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = false,
|
||||
Done = false,
|
||||
Error = false,
|
||||
Percent = 0,
|
||||
Time = 0,
|
||||
DownloadSpeed = 0
|
||||
};
|
||||
epMeta.AvailableSubs = item.SubtitleLocales;
|
||||
if (episodeP.EpisodeAndLanguages.Langs.Count > 0){
|
||||
epMeta.SelectedDubs = dubLang
|
||||
.Where(language => episodeP.EpisodeAndLanguages.Langs.Any(epLang => epLang.CrLocale == language))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var epMetaData = epMeta.Data[0];
|
||||
if (!string.IsNullOrEmpty(item.StreamsLink)){
|
||||
epMetaData.Playback = item.StreamsLink;
|
||||
if (string.IsNullOrEmpty(item.Playback)){
|
||||
item.Playback = item.StreamsLink;
|
||||
}
|
||||
}
|
||||
|
||||
if (retMeta.Data != null){
|
||||
epMetaData.Lang = episodeP.EpisodeAndLanguages.Langs[index];
|
||||
retMeta.Data.Add(epMetaData);
|
||||
|
||||
} else{
|
||||
epMetaData.Lang = episodeP.EpisodeAndLanguages.Langs[index];
|
||||
epMeta.Data[0] = epMetaData;
|
||||
retMeta = epMeta;
|
||||
}
|
||||
|
||||
|
||||
// show ep
|
||||
item.SeqId = epNum;
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
return retMeta;
|
||||
}
|
||||
}
|
|
@ -286,34 +286,48 @@ public class Crunchyroll{
|
|||
|
||||
|
||||
if (episodeL != null){
|
||||
if (episodeL.Value.Data != null && episodeL.Value.Data.First().IsPremiumOnly && !Profile.HasPremium){
|
||||
if (episodeL.Value.IsPremiumOnly && !Profile.HasPremium){
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Episode is a premium episode – make sure that you are signed in with an account that has an active premium subscription", ToastType.Error, 3));
|
||||
return;
|
||||
}
|
||||
|
||||
var sList = await CrEpisode.EpisodeData((CrunchyEpisodeList)episodeL);
|
||||
var selected = CrEpisode.EpisodeMeta(sList.Data, dubLang);
|
||||
var metas = selected.Values.ToList();
|
||||
var sList = await CrEpisode.EpisodeData((CrunchyEpisode)episodeL);
|
||||
var selected = CrEpisode.EpisodeMeta(sList, dubLang);
|
||||
|
||||
foreach (var crunchyEpMeta in metas){
|
||||
if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
|
||||
var historyEpisode = CrHistory.GetHistoryEpisode(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId);
|
||||
if (historyEpisode != null){
|
||||
if (!string.IsNullOrEmpty(historyEpisode.SonarrEpisodeNumber)){
|
||||
crunchyEpMeta.EpisodeNumber = historyEpisode.SonarrEpisodeNumber;
|
||||
}
|
||||
if (selected.Data is{ Count: > 0 }){
|
||||
if (CrunOptions.History){
|
||||
var historyEpisode = CrHistory.GetHistoryEpisodeWithDownloadDir(selected.ShowId, selected.SeasonId, selected.Data.First().MediaId);
|
||||
if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
|
||||
if (historyEpisode.historyEpisode != null){
|
||||
if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrEpisodeNumber)){
|
||||
selected.EpisodeNumber = historyEpisode.historyEpisode.SonarrEpisodeNumber;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(historyEpisode.SonarrSeasonNumber)){
|
||||
crunchyEpMeta.Season = historyEpisode.SonarrSeasonNumber;
|
||||
if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrSeasonNumber)){
|
||||
selected.Season = historyEpisode.historyEpisode.SonarrSeasonNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(historyEpisode.downloadDirPath)){
|
||||
selected.DownloadPath = historyEpisode.downloadDirPath;
|
||||
}
|
||||
}
|
||||
|
||||
Queue.Add(crunchyEpMeta);
|
||||
}
|
||||
Queue.Add(selected);
|
||||
|
||||
Console.WriteLine("Added Episode to Queue");
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue", ToastType.Information, 1));
|
||||
|
||||
if (selected.Data.Count < dubLang.Count){
|
||||
Console.WriteLine("Added Episode to Queue but couldn't find all selected dubs");
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue but couldn't find all selected dubs", ToastType.Warning, 2));
|
||||
} else{
|
||||
Console.WriteLine("Added Episode to Queue");
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue", ToastType.Information, 1));
|
||||
}
|
||||
} else{
|
||||
Console.WriteLine("Episode couldn't be added to Queue");
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't add episode to the queue with current dub settings", ToastType.Error, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,17 +338,23 @@ public class Crunchyroll{
|
|||
|
||||
foreach (var crunchyEpMeta in selected.Values.ToList()){
|
||||
if (crunchyEpMeta.Data?.First().Playback != null){
|
||||
if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
|
||||
var historyEpisode = CrHistory.GetHistoryEpisode(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId);
|
||||
if (historyEpisode != null){
|
||||
if (!string.IsNullOrEmpty(historyEpisode.SonarrEpisodeNumber)){
|
||||
crunchyEpMeta.EpisodeNumber = historyEpisode.SonarrEpisodeNumber;
|
||||
}
|
||||
if (CrunOptions.History){
|
||||
var historyEpisode = CrHistory.GetHistoryEpisodeWithDownloadDir(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId);
|
||||
if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
|
||||
if (historyEpisode.historyEpisode != null){
|
||||
if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrEpisodeNumber)){
|
||||
crunchyEpMeta.EpisodeNumber = historyEpisode.historyEpisode.SonarrEpisodeNumber;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(historyEpisode.SonarrSeasonNumber)){
|
||||
crunchyEpMeta.Season = historyEpisode.SonarrSeasonNumber;
|
||||
if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrSeasonNumber)){
|
||||
crunchyEpMeta.Season = historyEpisode.historyEpisode.SonarrSeasonNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(historyEpisode.downloadDirPath)){
|
||||
crunchyEpMeta.DownloadPath = historyEpisode.downloadDirPath;
|
||||
}
|
||||
}
|
||||
|
||||
Queue.Add(crunchyEpMeta);
|
||||
|
@ -596,7 +616,7 @@ public class Crunchyroll{
|
|||
return new DownloadResponse{
|
||||
Data = files,
|
||||
Error = true,
|
||||
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown",
|
||||
FileName = "./unknown",
|
||||
ErrorText = "Video Data not found"
|
||||
};
|
||||
}
|
||||
|
@ -604,11 +624,18 @@ public class Crunchyroll{
|
|||
|
||||
bool dlFailed = false;
|
||||
bool dlVideoOnce = false;
|
||||
string fileDir = CfgManager.PathVIDEOS_DIR;
|
||||
|
||||
if (data.Data != null){
|
||||
foreach (CrunchyEpMetaData epMeta in data.Data){
|
||||
Console.WriteLine($"Requesting: [{epMeta.MediaId}] {mediaName}");
|
||||
|
||||
fileDir = !string.IsNullOrEmpty(data.DownloadPath) ? data.DownloadPath : !string.IsNullOrEmpty(options.DownloadDirPath) ? options.DownloadDirPath : CfgManager.PathVIDEOS_DIR;
|
||||
|
||||
if (!Helpers.IsValidPath(fileDir)){
|
||||
fileDir = CfgManager.PathVIDEOS_DIR;
|
||||
}
|
||||
|
||||
string currentMediaId = (epMeta.MediaId.Contains(':') ? epMeta.MediaId.Split(':')[1] : epMeta.MediaId);
|
||||
|
||||
await CrAuth.RefreshToken(true);
|
||||
|
@ -964,12 +991,13 @@ public class Crunchyroll{
|
|||
Console.WriteLine($"\tServer: {selectedServer}");
|
||||
Console.WriteLine("Stream URL:" + chosenVideoSegments.segments[0].uri.Split(new[]{ ",.urlset" }, StringSplitOptions.None)[0]);
|
||||
|
||||
|
||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
||||
string outFile = Path.Combine(FileNameManager.ParseFileName(options.FileName + "." + (epMeta.Lang?.Name ?? lang.Value.Name), variables, options.Numbers, options.Override).ToArray());
|
||||
|
||||
string tempFile = Path.Combine(FileNameManager.ParseFileName($"temp-{(currentVersion.Guid != null ? currentVersion.Guid : currentMediaId)}", variables, options.Numbers, options.Override)
|
||||
.ToArray());
|
||||
string tempTsFile = Path.IsPathRooted(tempFile) ? tempFile : Path.Combine(CfgManager.PathVIDEOS_DIR, tempFile);
|
||||
string tempTsFile = Path.IsPathRooted(tempFile) ? tempFile : Path.Combine(fileDir, tempFile);
|
||||
|
||||
bool audioDownloaded = false, videoDownloaded = false;
|
||||
|
||||
|
@ -978,7 +1006,7 @@ public class Crunchyroll{
|
|||
} else if (options.Novids){
|
||||
Console.WriteLine("Skipping video download...");
|
||||
} else{
|
||||
var videoDownloadResult = await DownloadVideo(chosenVideoSegments, options, outFile, tsFile, tempTsFile, data);
|
||||
var videoDownloadResult = await DownloadVideo(chosenVideoSegments, options, outFile, tsFile, tempTsFile, data, fileDir);
|
||||
|
||||
tsFile = videoDownloadResult.tsFile;
|
||||
|
||||
|
@ -993,7 +1021,7 @@ public class Crunchyroll{
|
|||
|
||||
|
||||
if (chosenAudioSegments.segments.Count > 0 && !options.Noaudio && !dlFailed){
|
||||
var audioDownloadResult = await DownloadAudio(chosenAudioSegments, options, outFile, tsFile, tempTsFile, data);
|
||||
var audioDownloadResult = await DownloadAudio(chosenAudioSegments, options, outFile, tsFile, tempTsFile, data, fileDir);
|
||||
|
||||
tsFile = audioDownloadResult.tsFile;
|
||||
|
||||
|
@ -1011,7 +1039,7 @@ public class Crunchyroll{
|
|||
return new DownloadResponse{
|
||||
Data = files,
|
||||
Error = dlFailed,
|
||||
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown",
|
||||
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(fileDir, fileName)) : "./unknown",
|
||||
ErrorText = ""
|
||||
};
|
||||
}
|
||||
|
@ -1037,7 +1065,7 @@ public class Crunchyroll{
|
|||
return new DownloadResponse{
|
||||
Data = files,
|
||||
Error = dlFailed,
|
||||
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown",
|
||||
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(fileDir, fileName)) : "./unknown",
|
||||
ErrorText = "Decryption Needed but couldn't find CDM files"
|
||||
};
|
||||
}
|
||||
|
@ -1065,7 +1093,7 @@ public class Crunchyroll{
|
|||
return new DownloadResponse{
|
||||
Data = files,
|
||||
Error = dlFailed,
|
||||
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown",
|
||||
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(fileDir, fileName)) : "./unknown",
|
||||
ErrorText = "DRM Authentication failed"
|
||||
};
|
||||
}
|
||||
|
@ -1083,7 +1111,7 @@ public class Crunchyroll{
|
|||
return new DownloadResponse{
|
||||
Data = files,
|
||||
Error = dlFailed,
|
||||
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown",
|
||||
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(fileDir, fileName)) : "./unknown",
|
||||
ErrorText = "Couldn't get DRM encryption keys"
|
||||
};
|
||||
}
|
||||
|
@ -1249,7 +1277,7 @@ public class Crunchyroll{
|
|||
if (Path.IsPathRooted(outFile)){
|
||||
tsFile = outFile;
|
||||
} else{
|
||||
tsFile = Path.Combine(CfgManager.PathVIDEOS_DIR, outFile);
|
||||
tsFile = Path.Combine(fileDir, outFile);
|
||||
}
|
||||
|
||||
// Check if the path is absolute
|
||||
|
@ -1259,7 +1287,7 @@ public class Crunchyroll{
|
|||
string[] directories = Path.GetDirectoryName(outFile)?.Split(Path.DirectorySeparatorChar) ?? Array.Empty<string>();
|
||||
|
||||
// Initialize the cumulative path based on whether the original path is absolute or not
|
||||
string cumulativePath = isAbsolute ? "" : CfgManager.PathVIDEOS_DIR;
|
||||
string cumulativePath = isAbsolute ? "" : fileDir;
|
||||
for (int i = 0; i < directories.Length; i++){
|
||||
// Build the path incrementally
|
||||
cumulativePath = Path.Combine(cumulativePath, directories[i]);
|
||||
|
@ -1295,7 +1323,7 @@ public class Crunchyroll{
|
|||
}
|
||||
|
||||
if (!options.SkipSubs && options.DlSubs.IndexOf("none") == -1){
|
||||
await DownloadSubtitles(options, pbData, audDub, fileName, files);
|
||||
await DownloadSubtitles(options, pbData, audDub, fileName, files, fileDir);
|
||||
} else{
|
||||
Console.WriteLine("Subtitles downloading skipped!");
|
||||
}
|
||||
|
@ -1311,12 +1339,12 @@ public class Crunchyroll{
|
|||
return new DownloadResponse{
|
||||
Data = files,
|
||||
Error = dlFailed,
|
||||
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown",
|
||||
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(fileDir, fileName)) : "./unknown",
|
||||
ErrorText = ""
|
||||
};
|
||||
}
|
||||
|
||||
private static async Task DownloadSubtitles(CrDownloadOptions options, PlaybackData pbData, string audDub, string fileName, List<DownloadedMedia> files){
|
||||
private static async Task DownloadSubtitles(CrDownloadOptions options, PlaybackData pbData, string audDub, string fileName, List<DownloadedMedia> files, string fileDir){
|
||||
if (pbData.Meta != null && pbData.Meta.Subtitles != null && pbData.Meta.Subtitles.Count > 0){
|
||||
List<SubtitleInfo> subsData = pbData.Meta.Subtitles.Values.ToList();
|
||||
List<SubtitleInfo> capsData = pbData.Meta.ClosedCaptions?.Values.ToList() ?? new List<SubtitleInfo>();
|
||||
|
@ -1354,26 +1382,9 @@ public class Crunchyroll{
|
|||
var isSigns = langItem.Code == audDub && !subsItem.isCC;
|
||||
var isCc = subsItem.isCC;
|
||||
sxData.File = Languages.SubsFile(fileName, index + "", langItem, isCc, options.CcTag, isSigns, subsItem.format);
|
||||
sxData.Path = Path.Combine(CfgManager.PathVIDEOS_DIR, sxData.File);
|
||||
sxData.Path = Path.Combine(fileDir, sxData.File);
|
||||
|
||||
// Check if the path is absolute
|
||||
bool isAbsolute = Path.IsPathRooted(sxData.Path);
|
||||
|
||||
// Get all directory parts of the path except the last segment (assuming it's a file)
|
||||
string[] directories = Path.GetDirectoryName(sxData.Path)?.Split(Path.DirectorySeparatorChar) ?? Array.Empty<string>();
|
||||
|
||||
// Initialize the cumulative path based on whether the original path is absolute or not
|
||||
string cumulativePath = isAbsolute ? "" : CfgManager.PathVIDEOS_DIR;
|
||||
for (int i = 0; i < directories.Length; i++){
|
||||
// Build the path incrementally
|
||||
cumulativePath = Path.Combine(cumulativePath, directories[i]);
|
||||
|
||||
// Check if the directory exists and create it if it does not
|
||||
if (!Directory.Exists(cumulativePath)){
|
||||
Directory.CreateDirectory(cumulativePath);
|
||||
Console.WriteLine($"Created directory: {cumulativePath}");
|
||||
}
|
||||
}
|
||||
Helpers.EnsureDirectoriesExist(sxData.Path);
|
||||
|
||||
// Check if any file matches the specified conditions
|
||||
if (files.Any(a => a.Type == DownloadMediaType.Subtitle &&
|
||||
|
@ -1437,7 +1448,8 @@ public class Crunchyroll{
|
|||
}
|
||||
}
|
||||
|
||||
private async Task<(bool Ok, PartsData Parts, string tsFile)> DownloadVideo(VideoItem chosenVideoSegments, CrDownloadOptions options, string outFile, string tsFile, string tempTsFile, CrunchyEpMeta data){
|
||||
private async Task<(bool Ok, PartsData Parts, string tsFile)> DownloadVideo(VideoItem chosenVideoSegments, CrDownloadOptions options, string outFile, string tsFile, string tempTsFile, CrunchyEpMeta data,
|
||||
string fileDir){
|
||||
// Prepare for video download
|
||||
int totalParts = chosenVideoSegments.segments.Count;
|
||||
int mathParts = (int)Math.Ceiling((double)totalParts / options.Partsize);
|
||||
|
@ -1447,28 +1459,10 @@ public class Crunchyroll{
|
|||
if (Path.IsPathRooted(outFile)){
|
||||
tsFile = outFile;
|
||||
} else{
|
||||
tsFile = Path.Combine(CfgManager.PathVIDEOS_DIR, outFile);
|
||||
tsFile = Path.Combine(fileDir, outFile);
|
||||
}
|
||||
|
||||
// var split = outFile.Split(Path.DirectorySeparatorChar).AsSpan().Slice(0, -1).ToArray();
|
||||
// Check if the path is absolute
|
||||
bool isAbsolute = Path.IsPathRooted(outFile);
|
||||
|
||||
// Get all directory parts of the path except the last segment (assuming it's a file)
|
||||
string[] directories = Path.GetDirectoryName(outFile)?.Split(Path.DirectorySeparatorChar) ?? Array.Empty<string>();
|
||||
|
||||
// Initialize the cumulative path based on whether the original path is absolute or not
|
||||
string cumulativePath = isAbsolute ? "" : CfgManager.PathVIDEOS_DIR;
|
||||
for (int i = 0; i < directories.Length; i++){
|
||||
// Build the path incrementally
|
||||
cumulativePath = Path.Combine(cumulativePath, directories[i]);
|
||||
|
||||
// Check if the directory exists and create it if it does not
|
||||
if (!Directory.Exists(cumulativePath)){
|
||||
Directory.CreateDirectory(cumulativePath);
|
||||
Console.WriteLine($"Created directory: {cumulativePath}");
|
||||
}
|
||||
}
|
||||
Helpers.EnsureDirectoriesExist(outFile);
|
||||
|
||||
M3U8Json videoJson = new M3U8Json{
|
||||
Segments = chosenVideoSegments.segments.Cast<dynamic>().ToList()
|
||||
|
@ -1489,7 +1483,8 @@ public class Crunchyroll{
|
|||
return (videoDownloadResult.Ok, videoDownloadResult.Parts, tsFile);
|
||||
}
|
||||
|
||||
private async Task<(bool Ok, PartsData Parts, string tsFile)> DownloadAudio(AudioItem chosenAudioSegments, CrDownloadOptions options, string outFile, string tsFile, string tempTsFile, CrunchyEpMeta data){
|
||||
private async Task<(bool Ok, PartsData Parts, string tsFile)> DownloadAudio(AudioItem chosenAudioSegments, CrDownloadOptions options, string outFile, string tsFile, string tempTsFile, CrunchyEpMeta data,
|
||||
string fileDir){
|
||||
// Prepare for audio download
|
||||
int totalParts = chosenAudioSegments.segments.Count;
|
||||
int mathParts = (int)Math.Ceiling((double)totalParts / options.Partsize);
|
||||
|
@ -1499,7 +1494,7 @@ public class Crunchyroll{
|
|||
if (Path.IsPathRooted(outFile)){
|
||||
tsFile = outFile;
|
||||
} else{
|
||||
tsFile = Path.Combine(CfgManager.PathVIDEOS_DIR, outFile);
|
||||
tsFile = Path.Combine(fileDir, outFile);
|
||||
}
|
||||
|
||||
// Check if the path is absolute
|
||||
|
@ -1509,7 +1504,7 @@ public class Crunchyroll{
|
|||
string[] directories = Path.GetDirectoryName(outFile)?.Split(Path.DirectorySeparatorChar) ?? Array.Empty<string>();
|
||||
|
||||
// Initialize the cumulative path based on whether the original path is absolute or not
|
||||
string cumulativePath = isAbsolute ? "" : CfgManager.PathVIDEOS_DIR;
|
||||
string cumulativePath = isAbsolute ? "" : fileDir;
|
||||
for (int i = 0; i < directories.Length; i++){
|
||||
// Build the path incrementally
|
||||
cumulativePath = Path.Combine(cumulativePath, directories[i]);
|
||||
|
|
|
@ -90,7 +90,6 @@ public class History(){
|
|||
var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == episodeId);
|
||||
|
||||
if (historyEpisode != null){
|
||||
|
||||
return historyEpisode;
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +98,32 @@ public class History(){
|
|||
return null;
|
||||
}
|
||||
|
||||
public (HistoryEpisode? historyEpisode, string downloadDirPath) GetHistoryEpisodeWithDownloadDir(string? seriesId, string? seasonId, string episodeId){
|
||||
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
||||
|
||||
var downloadDirPath = "";
|
||||
|
||||
if (historySeries != null){
|
||||
var historySeason = historySeries.Seasons.Find(s => s.SeasonId == seasonId);
|
||||
if (!string.IsNullOrEmpty(historySeries.SeriesDownloadPath)){
|
||||
downloadDirPath = historySeries.SeriesDownloadPath;
|
||||
}
|
||||
|
||||
if (historySeason != null){
|
||||
var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == episodeId);
|
||||
if (!string.IsNullOrEmpty(historySeason.SeasonDownloadPath)){
|
||||
downloadDirPath = historySeason.SeasonDownloadPath;
|
||||
}
|
||||
|
||||
if (historyEpisode != null){
|
||||
return (historyEpisode, downloadDirPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (null, downloadDirPath);
|
||||
}
|
||||
|
||||
|
||||
public async Task UpdateWithEpisode(CrunchyEpisode episodeParam){
|
||||
var episode = episodeParam;
|
||||
|
@ -106,14 +131,12 @@ public class History(){
|
|||
if (episode.Versions != null){
|
||||
var version = episode.Versions.Find(a => a.Original);
|
||||
if (version.AudioLocale != episode.AudioLocale){
|
||||
var episodeById = await crunInstance.CrEpisode.ParseEpisodeById(version.Guid, "");
|
||||
if (episodeById?.Data != null){
|
||||
if (episodeById.Value.Total != 1){
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't update download History", ToastType.Warning, 1));
|
||||
return;
|
||||
}
|
||||
|
||||
episode = episodeById.Value.Data.First();
|
||||
var crEpisode = await crunInstance.CrEpisode.ParseEpisodeById(version.Guid, "");
|
||||
if (crEpisode != null){
|
||||
episode = crEpisode.Value;
|
||||
} else{
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't update download History", ToastType.Warning, 1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,7 +205,7 @@ public class History(){
|
|||
}
|
||||
|
||||
MatchHistorySeriesWithSonarr(false);
|
||||
await MatchHistoryEpisodesWithSonarr(false,historySeries);
|
||||
await MatchHistoryEpisodesWithSonarr(false, historySeries);
|
||||
UpdateHistoryFile();
|
||||
}
|
||||
|
||||
|
@ -262,6 +285,7 @@ public class History(){
|
|||
|
||||
historySeries.UpdateNewEpisodes();
|
||||
}
|
||||
|
||||
var sortedList = crunInstance.HistoryList.OrderBy(item => item.SeriesTitle).ToList();
|
||||
crunInstance.HistoryList.Clear();
|
||||
foreach (var item in sortedList){
|
||||
|
@ -269,7 +293,7 @@ public class History(){
|
|||
}
|
||||
|
||||
MatchHistorySeriesWithSonarr(false);
|
||||
await MatchHistoryEpisodesWithSonarr(false,historySeries);
|
||||
await MatchHistoryEpisodesWithSonarr(false, historySeries);
|
||||
UpdateHistoryFile();
|
||||
}
|
||||
}
|
||||
|
@ -341,7 +365,6 @@ public class History(){
|
|||
}
|
||||
|
||||
public void MatchHistorySeriesWithSonarr(bool updateAll){
|
||||
|
||||
if (crunInstance.CrunOptions.SonarrProperties is{ SonarrEnabled: false }){
|
||||
return;
|
||||
}
|
||||
|
@ -551,6 +574,9 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
[JsonProperty("series_season_list")]
|
||||
public required List<HistorySeason> Seasons{ get; set; }
|
||||
|
||||
[JsonProperty("series_download_path")]
|
||||
public string? SeriesDownloadPath{ get; set; }
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
[JsonIgnore]
|
||||
|
@ -636,7 +662,7 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
await Crunchyroll.Instance.CrHistory.UpdateSeries(SeriesId, seasonId);
|
||||
FetchingData = false;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
||||
Crunchyroll.Instance.CrHistory.MatchHistoryEpisodesWithSonarr(false,this);
|
||||
Crunchyroll.Instance.CrHistory.MatchHistoryEpisodesWithSonarr(false, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -662,6 +688,9 @@ public class HistorySeason : INotifyPropertyChanged{
|
|||
[JsonProperty("season_episode_list")]
|
||||
public required List<HistoryEpisode> EpisodesList{ get; set; }
|
||||
|
||||
[JsonProperty("series_download_path")]
|
||||
public string? SeasonDownloadPath{ get; set; }
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public void UpdateDownloaded(string? EpisodeId){
|
||||
|
|
|
@ -27,6 +27,64 @@ public class Helpers{
|
|||
}
|
||||
}
|
||||
|
||||
public static void EnsureDirectoriesExist(string path){
|
||||
// Check if the path is absolute
|
||||
bool isAbsolute = Path.IsPathRooted(path);
|
||||
|
||||
// Get all directory parts of the path except the last segment (assuming it's a file)
|
||||
string directoryPath = Path.GetDirectoryName(path);
|
||||
|
||||
if (string.IsNullOrEmpty(directoryPath)){
|
||||
Console.WriteLine("The provided path does not contain any directory information.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the cumulative path based on whether the original path is absolute or not
|
||||
string cumulativePath = isAbsolute ? Path.GetPathRoot(directoryPath) : Environment.CurrentDirectory;
|
||||
|
||||
// Get all directory parts
|
||||
string[] directories = directoryPath.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
|
||||
// Start the loop from the correct initial index
|
||||
int startIndex = isAbsolute && directories.Length > 0 && string.IsNullOrEmpty(directories[0]) ? 2 : 0;
|
||||
|
||||
for (int i = startIndex; i < directories.Length; i++){
|
||||
// Skip empty parts (which can occur with UNC paths)
|
||||
if (string.IsNullOrEmpty(directories[i])){
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build the path incrementally
|
||||
cumulativePath = Path.Combine(cumulativePath, directories[i]);
|
||||
|
||||
// Check if the directory exists and create it if it does not
|
||||
if (!Directory.Exists(cumulativePath)){
|
||||
Directory.CreateDirectory(cumulativePath);
|
||||
Console.WriteLine($"Created directory: {cumulativePath}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsValidPath(string path){
|
||||
char[] invalidChars = Path.GetInvalidPathChars();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(path)){
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path.Any(ch => invalidChars.Contains(ch))){
|
||||
return false;
|
||||
}
|
||||
|
||||
try{
|
||||
// Use Path.GetFullPath to ensure that the path can be fully qualified
|
||||
string fullPath = Path.GetFullPath(path);
|
||||
return true;
|
||||
} catch (Exception){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Locale ConvertStringToLocale(string? value){
|
||||
foreach (Locale locale in Enum.GetValues(typeof(Locale))){
|
||||
var type = typeof(Locale);
|
||||
|
|
|
@ -125,4 +125,7 @@ public class CrDownloadOptions{
|
|||
[YamlMember(Alias = "stream_endpoint", ApplyNamingConventions = false)]
|
||||
public string? StreamEndpoint{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "download_dir_path", ApplyNamingConventions = false)]
|
||||
public string? DownloadDirPath{ get; set; }
|
||||
|
||||
}
|
|
@ -247,6 +247,9 @@ public class CrunchyEpMeta{
|
|||
public List<string>? SelectedDubs{ get; set; }
|
||||
|
||||
public List<string>? AvailableSubs{ get; set; }
|
||||
|
||||
public string? DownloadPath{ get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class DownloadProgress{
|
||||
|
@ -267,4 +270,10 @@ public struct CrunchyEpMetaData{
|
|||
public List<EpisodeVersion>? Versions{ get; set; }
|
||||
public bool IsSubbed{ get; set; }
|
||||
public bool IsDubbed{ get; set; }
|
||||
|
||||
}
|
||||
|
||||
public struct CrunchyRollEpisodeData{
|
||||
public string Key{ get; set; }
|
||||
public EpisodeAndLanguage EpisodeAndLanguages{ get; set; }
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Platform.Storage;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
|
@ -21,6 +22,8 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
[ObservableProperty]
|
||||
public static bool _sonarrAvailable;
|
||||
|
||||
private IStorageProvider _storageProvider;
|
||||
|
||||
public SeriesPageViewModel(){
|
||||
_selectedSeries = Crunchyroll.Instance.SelectedSeries;
|
||||
|
||||
|
@ -38,6 +41,38 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OpenFolderDialogAsync(HistorySeason? season){
|
||||
if (_storageProvider == null){
|
||||
Console.Error.WriteLine("StorageProvider must be set before using the dialog.");
|
||||
throw new InvalidOperationException("StorageProvider must be set before using the dialog.");
|
||||
}
|
||||
|
||||
|
||||
var result = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions{
|
||||
Title = "Select Folder"
|
||||
});
|
||||
|
||||
if (result.Count > 0){
|
||||
var selectedFolder = result[0];
|
||||
// Do something with the selected folder path
|
||||
Console.WriteLine($"Selected folder: {selectedFolder.Path.LocalPath}");
|
||||
|
||||
if (season != null){
|
||||
season.SeasonDownloadPath = selectedFolder.Path.LocalPath;
|
||||
} else{
|
||||
SelectedSeries.SeriesDownloadPath = selectedFolder.Path.LocalPath;
|
||||
}
|
||||
|
||||
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetStorageProvider(IStorageProvider storageProvider){
|
||||
_storageProvider = storageProvider ?? throw new ArgumentNullException(nameof(storageProvider));
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public void OpenSonarrPage(){
|
||||
var sonarrProp = Crunchyroll.Instance.CrunOptions.SonarrProperties;
|
||||
|
|
|
@ -6,9 +6,11 @@ using System.ComponentModel;
|
|||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Styling;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
@ -249,15 +251,19 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
new ComboBoxItem(){ Content = "tv/samsung" },
|
||||
};
|
||||
|
||||
[ObservableProperty]
|
||||
private string _downloadDirPath;
|
||||
|
||||
private readonly FluentAvaloniaTheme _faTheme;
|
||||
|
||||
private bool settingsLoaded;
|
||||
|
||||
private IStorageProvider _storageProvider;
|
||||
|
||||
public SettingsPageViewModel(){
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
_currentVersion = $"{version?.Major}.{version?.Minor}.{version?.Build}";
|
||||
|
||||
|
||||
_faTheme = App.Current.Styles[0] as FluentAvaloniaTheme;
|
||||
|
||||
foreach (var languageItem in Languages.languages){
|
||||
|
@ -270,6 +276,8 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
|
||||
CrDownloadOptions options = Crunchyroll.Instance.CrunOptions;
|
||||
|
||||
DownloadDirPath = string.IsNullOrEmpty(options.DownloadDirPath) ? CfgManager.PathVIDEOS_DIR : options.DownloadDirPath;
|
||||
|
||||
ComboBoxItem? hsLang = HardSubLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.Hslang) ?? null;
|
||||
SelectedHSLang = hsLang ?? HardSubLangList[0];
|
||||
|
||||
|
@ -524,6 +532,32 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
RaisePropertyChanged(nameof(FfmpegOptions));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OpenFolderDialogAsync(){
|
||||
if (_storageProvider == null){
|
||||
Console.Error.WriteLine("StorageProvider must be set before using the dialog.");
|
||||
throw new InvalidOperationException("StorageProvider must be set before using the dialog.");
|
||||
}
|
||||
|
||||
|
||||
var result = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions{
|
||||
Title = "Select Folder"
|
||||
});
|
||||
|
||||
if (result.Count > 0){
|
||||
var selectedFolder = result[0];
|
||||
// Do something with the selected folder path
|
||||
Console.WriteLine($"Selected folder: {selectedFolder.Path.LocalPath}");
|
||||
Crunchyroll.Instance.CrunOptions.DownloadDirPath = selectedFolder.Path.LocalPath;
|
||||
DownloadDirPath = string.IsNullOrEmpty(Crunchyroll.Instance.CrunOptions.DownloadDirPath) ? CfgManager.PathVIDEOS_DIR : Crunchyroll.Instance.CrunOptions.DownloadDirPath;
|
||||
CfgManager.WriteSettingsToFile();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetStorageProvider(IStorageProvider storageProvider){
|
||||
_storageProvider = storageProvider ?? throw new ArgumentNullException(nameof(storageProvider));
|
||||
}
|
||||
|
||||
partial void OnCurrentAppThemeChanged(ComboBoxItem? value){
|
||||
if (value?.Content?.ToString() == "System"){
|
||||
_faTheme.PreferSystemTheme = true;
|
||||
|
|
|
@ -58,10 +58,16 @@ public partial class MainWindow : AppWindow{
|
|||
if (message.Refresh){
|
||||
navigationStack.Pop();
|
||||
var viewModel = Activator.CreateInstance(message.ViewModelType);
|
||||
if (viewModel is SeriesPageViewModel){
|
||||
((SeriesPageViewModel)viewModel).SetStorageProvider(StorageProvider);
|
||||
}
|
||||
navigationStack.Push(viewModel);
|
||||
nv.Content = viewModel;
|
||||
} else if (!message.Back && message.ViewModelType != null){
|
||||
var viewModel = Activator.CreateInstance(message.ViewModelType);
|
||||
if (viewModel is SeriesPageViewModel){
|
||||
((SeriesPageViewModel)viewModel).SetStorageProvider(StorageProvider);
|
||||
}
|
||||
navigationStack.Push(viewModel);
|
||||
nv.Content = viewModel;
|
||||
} else{
|
||||
|
@ -119,7 +125,9 @@ public partial class MainWindow : AppWindow{
|
|||
selectedNavVieItem = selectedItem;
|
||||
break;
|
||||
case "Settings":
|
||||
navView.Content = Activator.CreateInstance(typeof(SettingsPageViewModel));
|
||||
var viewModel = (SettingsPageViewModel)Activator.CreateInstance(typeof(SettingsPageViewModel));
|
||||
viewModel.SetStorageProvider(StorageProvider);
|
||||
navView.Content = viewModel;
|
||||
selectedNavVieItem = selectedItem;
|
||||
break;
|
||||
case "UpdateAvailable":
|
||||
|
|
|
@ -66,7 +66,19 @@
|
|||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Command="{Binding UpdateData}" Margin="0 0 5 10">Fetch Series</Button>
|
||||
<ToggleButton IsChecked="{Binding EditMode}" Margin="0 0 0 10">Edit</ToggleButton>
|
||||
<ToggleButton IsChecked="{Binding EditMode}" Margin="0 0 5 10">Edit</ToggleButton>
|
||||
|
||||
<Button Margin="0 0 5 10" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenFolderDialogAsync}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="{Binding SelectedSeries.SeriesDownloadPath}" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Folder" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
|
@ -111,10 +123,12 @@
|
|||
IsVisible="{Binding $parent[ItemsControl].((vm:SeriesPageViewModel)DataContext).SonarrAvailable}">
|
||||
|
||||
|
||||
<controls:ImageIcon IsVisible="{Binding SonarrHasFile}" Source="../Assets/sonarr.png" Width="25"
|
||||
<controls:ImageIcon IsVisible="{Binding SonarrHasFile}"
|
||||
Source="../Assets/sonarr.png" Width="25"
|
||||
Height="25" />
|
||||
|
||||
<controls:ImageIcon IsVisible="{Binding !SonarrHasFile}" Source="../Assets/sonarr_inactive.png" Width="25"
|
||||
<controls:ImageIcon IsVisible="{Binding !SonarrHasFile}"
|
||||
Source="../Assets/sonarr_inactive.png" Width="25"
|
||||
Height="25" />
|
||||
|
||||
|
||||
|
@ -171,6 +185,19 @@
|
|||
<controls:SymbolIcon Symbol="Refresh" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Margin="10 0 0 0" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).OpenFolderDialogAsync}"
|
||||
CommandParameter="{Binding .}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="{Binding SeasonDownloadPath}" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Folder" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Margin="10 0 0 0" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).RemoveSeason}"
|
||||
|
|
|
@ -145,6 +145,25 @@
|
|||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Download Folder">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock FontSize="15" Opacity="0.8" TextWrapping="NoWrap" Text="{Binding DownloadDirPath, Mode=OneWay}" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" />
|
||||
<Button Margin="10 0 0 0" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenFolderDialogAsync}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Set Download Directory" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Folder" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Simultaneous Downloads">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<controls:NumberBox Minimum="0" Maximum="5"
|
||||
|
|
|
@ -19,5 +19,4 @@ public partial class SettingsPageView : UserControl{
|
|||
Crunchyroll.Instance.RefreshSonarr();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue