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:
Elwador 2024-06-21 03:49:44 +02:00
parent cd4ceea38a
commit 3d74fa7667
12 changed files with 500 additions and 310 deletions

View File

@ -7,6 +7,7 @@ using System.Net.Http;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
using Avalonia.Remote.Protocol.Input;
using CRD.Utils; using CRD.Utils;
using CRD.Utils.Structs; using CRD.Utils.Structs;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -14,10 +15,9 @@ using Newtonsoft.Json;
namespace CRD.Downloader; namespace CRD.Downloader;
public class CrEpisode(){ public class CrEpisode(){
private readonly Crunchyroll crunInstance = Crunchyroll.Instance; 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){ if (crunInstance.CmsToken?.Cms == null){
Console.Error.WriteLine("Missing CMS Access Token"); Console.Error.WriteLine("Missing CMS Access Token");
return null; return null;
@ -43,148 +43,122 @@ public class CrEpisode(){
return null; 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; bool serieshasversions = true;
Dictionary<string, EpisodeAndLanguage> episodes = new Dictionary<string, EpisodeAndLanguage>(); // Dictionary<string, EpisodeAndLanguage> episodes = new Dictionary<string, EpisodeAndLanguage>();
if (dlEpisodes.Data != null){ CrunchyRollEpisodeData episode = new CrunchyRollEpisodeData();
foreach (var episode in dlEpisodes.Data){
if (crunInstance.CrunOptions.History){ if (crunInstance.CrunOptions.History){
await crunInstance.CrHistory.UpdateWithEpisode(episode); await crunInstance.CrHistory.UpdateWithEpisode(dlEpisode);
} }
// Prepare the episode array var seasonIdentifier = !string.IsNullOrEmpty(dlEpisode.Identifier) ? dlEpisode.Identifier.Split('|')[1] : $"S{dlEpisode.SeasonNumber}";
EpisodeAndLanguage item; episode.Key = $"{seasonIdentifier}E{dlEpisode.Episode ?? (dlEpisode.EpisodeNumber + "")}";
var seasonIdentifier = !string.IsNullOrEmpty(episode.Identifier) ? episode.Identifier.Split('|')[1] : $"S{episode.SeasonNumber}"; episode.EpisodeAndLanguages = new EpisodeAndLanguage{
var episodeKey = $"{seasonIdentifier}E{episode.Episode ?? (episode.EpisodeNumber + "")}";
if (!episodes.ContainsKey(episodeKey)){
item = new EpisodeAndLanguage{
Items = new List<CrunchyEpisode>(), Items = new List<CrunchyEpisode>(),
Langs = new List<LanguageItem>() Langs = new List<LanguageItem>()
}; };
episodes[episodeKey] = item;
} else{
item = episodes[episodeKey];
}
if (episode.Versions != null){ if (dlEpisode.Versions != null){
foreach (var version in episode.Versions){ foreach (var version in dlEpisode.Versions){
// Ensure there is only one of the same language // Ensure there is only one of the same language
if (item.Langs.All(a => a.CrLocale != version.AudioLocale)){ if (episode.EpisodeAndLanguages.Langs.All(a => a.CrLocale != version.AudioLocale)){
// Push to arrays if there are no duplicates of the same language // Push to arrays if there are no duplicates of the same language
item.Items.Add(episode); episode.EpisodeAndLanguages.Items.Add(dlEpisode);
item.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == version.AudioLocale)); episode.EpisodeAndLanguages.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == version.AudioLocale));
} }
} }
} else{ } else{
// Episode didn't have versions, mark it as such to be logged. // Episode didn't have versions, mark it as such to be logged.
serieshasversions = false; serieshasversions = false;
// Ensure there is only one of the same language // Ensure there is only one of the same language
if (item.Langs.All(a => a.CrLocale != episode.AudioLocale)){ if (episode.EpisodeAndLanguages.Langs.All(a => a.CrLocale != dlEpisode.AudioLocale)){
// Push to arrays if there are no duplicates of the same language // Push to arrays if there are no duplicates of the same language
item.Items.Add(episode); episode.EpisodeAndLanguages.Items.Add(dlEpisode);
item.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == episode.AudioLocale)); episode.EpisodeAndLanguages.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == dlEpisode.AudioLocale));
}
}
} }
} }
int specialIndex = 1; int specialIndex = 1;
int epIndex = 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){ var isSpecial = !Regex.IsMatch(episode.EpisodeAndLanguages.Items[0].Episode ?? string.Empty, @"^\d+$"); // Checking if the episode is not a number (i.e., special).
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; string newKey;
if (isSpecial && !string.IsNullOrEmpty(item.Items[0].Episode)){ if (isSpecial && !string.IsNullOrEmpty(episode.EpisodeAndLanguages.Items[0].Episode)){
newKey = item.Items[0].Episode ?? "SP" + (specialIndex + " " + item.Items[0].Id); newKey = episode.EpisodeAndLanguages.Items[0].Episode ?? "SP" + (specialIndex + " " + episode.EpisodeAndLanguages.Items[0].Id);
} else{ } else{
newKey = $"{(isSpecial ? "SP" : 'E')}{(isSpecial ? (specialIndex + " " + item.Items[0].Id) : epIndex + "")}"; newKey = $"{(isSpecial ? "SP" : 'E')}{(isSpecial ? (specialIndex + " " + episode.EpisodeAndLanguages.Items[0].Id) : episode.EpisodeAndLanguages.Items[0].Episode ?? epIndex + "")}";
} }
episodes.Remove(key); episode.Key = newKey;
episodes.Add(newKey, item);
if (isSpecial){ var seasonTitle = episode.EpisodeAndLanguages.Items.FirstOrDefault(a => !Regex.IsMatch(a.SeasonTitle, @"\(\w+ Dub\)")).SeasonTitle
specialIndex++; ?? Regex.Replace(episode.EpisodeAndLanguages.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd();
} else{
epIndex++;
}
}
var specials = episodes.Where(e => e.Key.StartsWith("SP")).ToList(); var title = episode.EpisodeAndLanguages.Items[0].Title;
var normal = episodes.Where(e => e.Key.StartsWith("E")).ToList(); var seasonNumber = Helpers.ExtractNumberAfterS(episode.EpisodeAndLanguages.Items[0].Identifier) ?? episode.EpisodeAndLanguages.Items[0].SeasonNumber.ToString();
// Combining and sorting episodes with normal first, then specials. var languages = episode.EpisodeAndLanguages.Items.Select((a, index) =>
var sortedEpisodes = new Dictionary<string, EpisodeAndLanguage>(normal.Concat(specials)); $"{(a.IsPremiumOnly ? "+ " : "")}{episode.EpisodeAndLanguages.Langs.ElementAtOrDefault(index).Name ?? "Unknown"}").ToArray(); //☆
foreach (var kvp in sortedEpisodes){ Console.WriteLine($"[{episode.Key}] {seasonTitle} - Season {seasonNumber} - {title} [{string.Join(", ", languages)}]");
var key = kvp.Key;
var item = kvp.Value;
var seasonTitle = item.Items.FirstOrDefault(a => !Regex.IsMatch(a.SeasonTitle, @"\(\w+ Dub\)")).SeasonTitle
?? Regex.Replace(item.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd();
var title = item.Items[0].Title;
var seasonNumber = Helpers.ExtractNumberAfterS(item.Items[0].Identifier) ?? item.Items[0].SeasonNumber.ToString();
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){ 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 => { // crunchySeriesList.Data = sortedEpisodes;
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 images = (episode.EpisodeAndLanguages.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); // var seconds = (int)Math.Floor(episode.EpisodeAndLanguages.Items[0].DurationMs / 1000.0);
return new Episode{ //
E = key.StartsWith("E") ? key.Substring(1) : key, // var newEpisode = new Episode{
Lang = value.Langs.Select(a => a.Code).ToList(), // E = episode.Key.StartsWith("E") ? episode.Key.Substring(1) : episode.Key,
Name = value.Items[0].Title, // Lang = episode.EpisodeAndLanguages.Langs.Select(a => a.Code).ToList(),
Season = Helpers.ExtractNumberAfterS(value.Items[0].Identifier) ?? value.Items[0].SeasonNumber.ToString(), // Name = episode.EpisodeAndLanguages.Items[0].Title,
SeriesTitle = Regex.Replace(value.Items[0].SeriesTitle, @"\(\w+ Dub\)", "").TrimEnd(), // Season = Helpers.ExtractNumberAfterS(episode.EpisodeAndLanguages.Items[0].Identifier) ?? episode.EpisodeAndLanguages.Items[0].SeasonNumber.ToString(),
SeasonTitle = Regex.Replace(value.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd(), // SeriesTitle = Regex.Replace(episode.EpisodeAndLanguages.Items[0].SeriesTitle, @"\(\w+ Dub\)", "").TrimEnd(),
EpisodeNum = value.Items[0].EpisodeNumber?.ToString() ?? value.Items[0].Episode ?? "?", // SeasonTitle = Regex.Replace(episode.EpisodeAndLanguages.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd(),
Id = value.Items[0].SeasonId, // EpisodeNum = episode.EpisodeAndLanguages.Items[0].EpisodeNumber?.ToString() ?? episode.EpisodeAndLanguages.Items[0].Episode ?? "?",
Img = images[images.Count / 2].FirstOrDefault().Source, // Id = episode.EpisodeAndLanguages.Items[0].SeasonId,
Description = value.Items[0].Description, // Img = images[images.Count / 2].FirstOrDefault().Source,
Time = $"{seconds / 60}:{seconds % 60:D2}" // Ensures two digits for seconds. // Description = episode.EpisodeAndLanguages.Items[0].Description,
}; // Time = $"{seconds / 60}:{seconds % 60:D2}" // Ensures two digits for seconds.
}).ToList(); // };
//
// CrunchySeriesList crunchySeriesList = new CrunchySeriesList();
return crunchySeriesList; return episode;
} }
public Dictionary<string, CrunchyEpMeta> EpisodeMeta(Dictionary<string, EpisodeAndLanguage> eps, List<string> dubLang){ public CrunchyEpMeta EpisodeMeta(CrunchyRollEpisodeData episodeP, List<string> dubLang){
var ret = new Dictionary<string, CrunchyEpMeta>(); // 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++){ for (int index = 0; index < episodeP.EpisodeAndLanguages.Items.Count; index++){
var item = episode.Items[index]; var item = episodeP.EpisodeAndLanguages.Items[index];
if (!dubLang.Contains(episode.Langs[index].CrLocale)) if (!dubLang.Contains(episodeP.EpisodeAndLanguages.Langs[index].CrLocale))
continue; continue;
item.HideSeasonTitle = true; item.HideSeasonTitle = true;
@ -199,15 +173,15 @@ public class CrEpisode(){
item.SeriesTitle = "NO_TITLE"; item.SeriesTitle = "NO_TITLE";
} }
var epNum = key.StartsWith('E') ? key[1..] : key; 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" } } }); var images = (item.Images?.Thumbnail ?? new List<List<Image>>{ new List<Image>{ new Image{ Source = "/notFound.png" } } });
Regex dubPattern = new Regex(@"\(\w+ Dub\)"); Regex dubPattern = new Regex(@"\(\w+ Dub\)");
var epMeta = new CrunchyEpMeta(); var epMeta = new CrunchyEpMeta();
epMeta.Data = new List<CrunchyEpMetaData>{ new(){ MediaId = item.Id, Versions = item.Versions, IsSubbed = item.IsSubbed, IsDubbed = item.IsDubbed } }; 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.SeriesTitle = episodeP.EpisodeAndLanguages.Items.FirstOrDefault(a => !dubPattern.IsMatch(a.SeriesTitle)).SeriesTitle ?? Regex.Replace(episodeP.EpisodeAndLanguages.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.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.EpisodeNumber = item.Episode;
epMeta.EpisodeTitle = item.Title; epMeta.EpisodeTitle = item.Title;
epMeta.SeasonId = item.SeasonId; epMeta.SeasonId = item.SeasonId;
@ -224,9 +198,9 @@ public class CrEpisode(){
DownloadSpeed = 0 DownloadSpeed = 0
}; };
epMeta.AvailableSubs = item.SubtitleLocales; epMeta.AvailableSubs = item.SubtitleLocales;
if (episode.Langs.Count > 0){ if (episodeP.EpisodeAndLanguages.Langs.Count > 0){
epMeta.SelectedDubs = dubLang epMeta.SelectedDubs = dubLang
.Where(language => episode.Langs.Any(epLang => epLang.CrLocale == language)) .Where(language => episodeP.EpisodeAndLanguages.Langs.Any(epLang => epLang.CrLocale == language))
.ToList(); .ToList();
} }
@ -238,22 +212,22 @@ public class CrEpisode(){
} }
} }
if (ret.TryGetValue(key, out var epMe)){ if (retMeta.Data != null){
epMetaData.Lang = episode.Langs[index]; epMetaData.Lang = episodeP.EpisodeAndLanguages.Langs[index];
epMe.Data?.Add(epMetaData); retMeta.Data.Add(epMetaData);
} else{ } else{
epMetaData.Lang = episode.Langs[index]; epMetaData.Lang = episodeP.EpisodeAndLanguages.Langs[index];
epMeta.Data[0] = epMetaData; epMeta.Data[0] = epMetaData;
ret.Add(key, epMeta); retMeta = epMeta;
} }
// show ep // show ep
item.SeqId = epNum; item.SeqId = epNum;
} }
}
return ret; return retMeta;
} }
} }

View File

@ -286,35 +286,49 @@ public class Crunchyroll{
if (episodeL != null){ 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)); 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; return;
} }
var sList = await CrEpisode.EpisodeData((CrunchyEpisodeList)episodeL); var sList = await CrEpisode.EpisodeData((CrunchyEpisode)episodeL);
var selected = CrEpisode.EpisodeMeta(sList.Data, dubLang); var selected = CrEpisode.EpisodeMeta(sList, dubLang);
var metas = selected.Values.ToList();
foreach (var crunchyEpMeta in metas){ 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 (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
var historyEpisode = CrHistory.GetHistoryEpisode(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId); if (historyEpisode.historyEpisode != null){
if (historyEpisode != null){ if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrEpisodeNumber)){
if (!string.IsNullOrEmpty(historyEpisode.SonarrEpisodeNumber)){ selected.EpisodeNumber = historyEpisode.historyEpisode.SonarrEpisodeNumber;
crunchyEpMeta.EpisodeNumber = historyEpisode.SonarrEpisodeNumber;
} }
if (!string.IsNullOrEmpty(historyEpisode.SonarrSeasonNumber)){ if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrSeasonNumber)){
crunchyEpMeta.Season = historyEpisode.SonarrSeasonNumber; selected.Season = historyEpisode.historyEpisode.SonarrSeasonNumber;
} }
} }
} }
Queue.Add(crunchyEpMeta); if (!string.IsNullOrEmpty(historyEpisode.downloadDirPath)){
selected.DownloadPath = historyEpisode.downloadDirPath;
}
} }
Queue.Add(selected);
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"); Console.WriteLine("Added Episode to Queue");
MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue", ToastType.Information, 1)); 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));
}
}
} }
public void AddSeriesToQueue(CrunchySeriesList list, CrunchyMultiDownload data){ public void AddSeriesToQueue(CrunchySeriesList list, CrunchyMultiDownload data){
@ -324,19 +338,25 @@ public class Crunchyroll{
foreach (var crunchyEpMeta in selected.Values.ToList()){ foreach (var crunchyEpMeta in selected.Values.ToList()){
if (crunchyEpMeta.Data?.First().Playback != null){ if (crunchyEpMeta.Data?.First().Playback != null){
if (CrunOptions.History){
var historyEpisode = CrHistory.GetHistoryEpisodeWithDownloadDir(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId);
if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){ if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
var historyEpisode = CrHistory.GetHistoryEpisode(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId); if (historyEpisode.historyEpisode != null){
if (historyEpisode != null){ if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrEpisodeNumber)){
if (!string.IsNullOrEmpty(historyEpisode.SonarrEpisodeNumber)){ crunchyEpMeta.EpisodeNumber = historyEpisode.historyEpisode.SonarrEpisodeNumber;
crunchyEpMeta.EpisodeNumber = historyEpisode.SonarrEpisodeNumber;
} }
if (!string.IsNullOrEmpty(historyEpisode.SonarrSeasonNumber)){ if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrSeasonNumber)){
crunchyEpMeta.Season = historyEpisode.SonarrSeasonNumber; crunchyEpMeta.Season = historyEpisode.historyEpisode.SonarrSeasonNumber;
} }
} }
} }
if (!string.IsNullOrEmpty(historyEpisode.downloadDirPath)){
crunchyEpMeta.DownloadPath = historyEpisode.downloadDirPath;
}
}
Queue.Add(crunchyEpMeta); Queue.Add(crunchyEpMeta);
} else{ } else{
failed = true; failed = true;
@ -596,7 +616,7 @@ public class Crunchyroll{
return new DownloadResponse{ return new DownloadResponse{
Data = files, Data = files,
Error = true, Error = true,
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown", FileName = "./unknown",
ErrorText = "Video Data not found" ErrorText = "Video Data not found"
}; };
} }
@ -604,11 +624,18 @@ public class Crunchyroll{
bool dlFailed = false; bool dlFailed = false;
bool dlVideoOnce = false; bool dlVideoOnce = false;
string fileDir = CfgManager.PathVIDEOS_DIR;
if (data.Data != null){ if (data.Data != null){
foreach (CrunchyEpMetaData epMeta in data.Data){ foreach (CrunchyEpMetaData epMeta in data.Data){
Console.WriteLine($"Requesting: [{epMeta.MediaId}] {mediaName}"); 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); string currentMediaId = (epMeta.MediaId.Contains(':') ? epMeta.MediaId.Split(':')[1] : epMeta.MediaId);
await CrAuth.RefreshToken(true); await CrAuth.RefreshToken(true);
@ -964,12 +991,13 @@ public class Crunchyroll{
Console.WriteLine($"\tServer: {selectedServer}"); Console.WriteLine($"\tServer: {selectedServer}");
Console.WriteLine("Stream URL:" + chosenVideoSegments.segments[0].uri.Split(new[]{ ",.urlset" }, StringSplitOptions.None)[0]); 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()); 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 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) string tempFile = Path.Combine(FileNameManager.ParseFileName($"temp-{(currentVersion.Guid != null ? currentVersion.Guid : currentMediaId)}", variables, options.Numbers, options.Override)
.ToArray()); .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; bool audioDownloaded = false, videoDownloaded = false;
@ -978,7 +1006,7 @@ public class Crunchyroll{
} else if (options.Novids){ } else if (options.Novids){
Console.WriteLine("Skipping video download..."); Console.WriteLine("Skipping video download...");
} else{ } 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; tsFile = videoDownloadResult.tsFile;
@ -993,7 +1021,7 @@ public class Crunchyroll{
if (chosenAudioSegments.segments.Count > 0 && !options.Noaudio && !dlFailed){ 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; tsFile = audioDownloadResult.tsFile;
@ -1011,7 +1039,7 @@ public class Crunchyroll{
return new DownloadResponse{ return new DownloadResponse{
Data = files, Data = files,
Error = dlFailed, 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 = "" ErrorText = ""
}; };
} }
@ -1037,7 +1065,7 @@ public class Crunchyroll{
return new DownloadResponse{ return new DownloadResponse{
Data = files, Data = files,
Error = dlFailed, 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" ErrorText = "Decryption Needed but couldn't find CDM files"
}; };
} }
@ -1065,7 +1093,7 @@ public class Crunchyroll{
return new DownloadResponse{ return new DownloadResponse{
Data = files, Data = files,
Error = dlFailed, 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" ErrorText = "DRM Authentication failed"
}; };
} }
@ -1083,7 +1111,7 @@ public class Crunchyroll{
return new DownloadResponse{ return new DownloadResponse{
Data = files, Data = files,
Error = dlFailed, 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" ErrorText = "Couldn't get DRM encryption keys"
}; };
} }
@ -1249,7 +1277,7 @@ public class Crunchyroll{
if (Path.IsPathRooted(outFile)){ if (Path.IsPathRooted(outFile)){
tsFile = outFile; tsFile = outFile;
} else{ } else{
tsFile = Path.Combine(CfgManager.PathVIDEOS_DIR, outFile); tsFile = Path.Combine(fileDir, outFile);
} }
// Check if the path is absolute // Check if the path is absolute
@ -1259,7 +1287,7 @@ public class Crunchyroll{
string[] directories = Path.GetDirectoryName(outFile)?.Split(Path.DirectorySeparatorChar) ?? Array.Empty<string>(); 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 // 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++){ for (int i = 0; i < directories.Length; i++){
// Build the path incrementally // Build the path incrementally
cumulativePath = Path.Combine(cumulativePath, directories[i]); cumulativePath = Path.Combine(cumulativePath, directories[i]);
@ -1295,7 +1323,7 @@ public class Crunchyroll{
} }
if (!options.SkipSubs && options.DlSubs.IndexOf("none") == -1){ if (!options.SkipSubs && options.DlSubs.IndexOf("none") == -1){
await DownloadSubtitles(options, pbData, audDub, fileName, files); await DownloadSubtitles(options, pbData, audDub, fileName, files, fileDir);
} else{ } else{
Console.WriteLine("Subtitles downloading skipped!"); Console.WriteLine("Subtitles downloading skipped!");
} }
@ -1311,12 +1339,12 @@ public class Crunchyroll{
return new DownloadResponse{ return new DownloadResponse{
Data = files, Data = files,
Error = dlFailed, 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 = "" 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){ if (pbData.Meta != null && pbData.Meta.Subtitles != null && pbData.Meta.Subtitles.Count > 0){
List<SubtitleInfo> subsData = pbData.Meta.Subtitles.Values.ToList(); List<SubtitleInfo> subsData = pbData.Meta.Subtitles.Values.ToList();
List<SubtitleInfo> capsData = pbData.Meta.ClosedCaptions?.Values.ToList() ?? new List<SubtitleInfo>(); 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 isSigns = langItem.Code == audDub && !subsItem.isCC;
var isCc = subsItem.isCC; var isCc = subsItem.isCC;
sxData.File = Languages.SubsFile(fileName, index + "", langItem, isCc, options.CcTag, isSigns, subsItem.format); 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 Helpers.EnsureDirectoriesExist(sxData.Path);
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}");
}
}
// Check if any file matches the specified conditions // Check if any file matches the specified conditions
if (files.Any(a => a.Type == DownloadMediaType.Subtitle && 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 // Prepare for video download
int totalParts = chosenVideoSegments.segments.Count; int totalParts = chosenVideoSegments.segments.Count;
int mathParts = (int)Math.Ceiling((double)totalParts / options.Partsize); int mathParts = (int)Math.Ceiling((double)totalParts / options.Partsize);
@ -1447,28 +1459,10 @@ public class Crunchyroll{
if (Path.IsPathRooted(outFile)){ if (Path.IsPathRooted(outFile)){
tsFile = outFile; tsFile = outFile;
} else{ } else{
tsFile = Path.Combine(CfgManager.PathVIDEOS_DIR, outFile); tsFile = Path.Combine(fileDir, outFile);
} }
// var split = outFile.Split(Path.DirectorySeparatorChar).AsSpan().Slice(0, -1).ToArray(); Helpers.EnsureDirectoriesExist(outFile);
// 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}");
}
}
M3U8Json videoJson = new M3U8Json{ M3U8Json videoJson = new M3U8Json{
Segments = chosenVideoSegments.segments.Cast<dynamic>().ToList() Segments = chosenVideoSegments.segments.Cast<dynamic>().ToList()
@ -1489,7 +1483,8 @@ public class Crunchyroll{
return (videoDownloadResult.Ok, videoDownloadResult.Parts, tsFile); 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 // Prepare for audio download
int totalParts = chosenAudioSegments.segments.Count; int totalParts = chosenAudioSegments.segments.Count;
int mathParts = (int)Math.Ceiling((double)totalParts / options.Partsize); int mathParts = (int)Math.Ceiling((double)totalParts / options.Partsize);
@ -1499,7 +1494,7 @@ public class Crunchyroll{
if (Path.IsPathRooted(outFile)){ if (Path.IsPathRooted(outFile)){
tsFile = outFile; tsFile = outFile;
} else{ } else{
tsFile = Path.Combine(CfgManager.PathVIDEOS_DIR, outFile); tsFile = Path.Combine(fileDir, outFile);
} }
// Check if the path is absolute // Check if the path is absolute
@ -1509,7 +1504,7 @@ public class Crunchyroll{
string[] directories = Path.GetDirectoryName(outFile)?.Split(Path.DirectorySeparatorChar) ?? Array.Empty<string>(); 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 // 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++){ for (int i = 0; i < directories.Length; i++){
// Build the path incrementally // Build the path incrementally
cumulativePath = Path.Combine(cumulativePath, directories[i]); cumulativePath = Path.Combine(cumulativePath, directories[i]);

View File

@ -90,7 +90,6 @@ public class History(){
var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == episodeId); var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == episodeId);
if (historyEpisode != null){ if (historyEpisode != null){
return historyEpisode; return historyEpisode;
} }
} }
@ -99,6 +98,32 @@ public class History(){
return null; 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){ public async Task UpdateWithEpisode(CrunchyEpisode episodeParam){
var episode = episodeParam; var episode = episodeParam;
@ -106,15 +131,13 @@ public class History(){
if (episode.Versions != null){ if (episode.Versions != null){
var version = episode.Versions.Find(a => a.Original); var version = episode.Versions.Find(a => a.Original);
if (version.AudioLocale != episode.AudioLocale){ if (version.AudioLocale != episode.AudioLocale){
var episodeById = await crunInstance.CrEpisode.ParseEpisodeById(version.Guid, ""); var crEpisode = await crunInstance.CrEpisode.ParseEpisodeById(version.Guid, "");
if (episodeById?.Data != null){ if (crEpisode != null){
if (episodeById.Value.Total != 1){ episode = crEpisode.Value;
} else{
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't update download History", ToastType.Warning, 1)); MessageBus.Current.SendMessage(new ToastMessage($"Couldn't update download History", ToastType.Warning, 1));
return; return;
} }
episode = episodeById.Value.Data.First();
}
} }
} }
@ -262,6 +285,7 @@ public class History(){
historySeries.UpdateNewEpisodes(); historySeries.UpdateNewEpisodes();
} }
var sortedList = crunInstance.HistoryList.OrderBy(item => item.SeriesTitle).ToList(); var sortedList = crunInstance.HistoryList.OrderBy(item => item.SeriesTitle).ToList();
crunInstance.HistoryList.Clear(); crunInstance.HistoryList.Clear();
foreach (var item in sortedList){ foreach (var item in sortedList){
@ -341,7 +365,6 @@ public class History(){
} }
public void MatchHistorySeriesWithSonarr(bool updateAll){ public void MatchHistorySeriesWithSonarr(bool updateAll){
if (crunInstance.CrunOptions.SonarrProperties is{ SonarrEnabled: false }){ if (crunInstance.CrunOptions.SonarrProperties is{ SonarrEnabled: false }){
return; return;
} }
@ -551,6 +574,9 @@ public class HistorySeries : INotifyPropertyChanged{
[JsonProperty("series_season_list")] [JsonProperty("series_season_list")]
public required List<HistorySeason> Seasons{ get; set; } public required List<HistorySeason> Seasons{ get; set; }
[JsonProperty("series_download_path")]
public string? SeriesDownloadPath{ get; set; }
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
[JsonIgnore] [JsonIgnore]
@ -662,6 +688,9 @@ public class HistorySeason : INotifyPropertyChanged{
[JsonProperty("season_episode_list")] [JsonProperty("season_episode_list")]
public required List<HistoryEpisode> EpisodesList{ get; set; } public required List<HistoryEpisode> EpisodesList{ get; set; }
[JsonProperty("series_download_path")]
public string? SeasonDownloadPath{ get; set; }
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
public void UpdateDownloaded(string? EpisodeId){ public void UpdateDownloaded(string? EpisodeId){

View File

@ -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){ public static Locale ConvertStringToLocale(string? value){
foreach (Locale locale in Enum.GetValues(typeof(Locale))){ foreach (Locale locale in Enum.GetValues(typeof(Locale))){
var type = typeof(Locale); var type = typeof(Locale);

View File

@ -125,4 +125,7 @@ public class CrDownloadOptions{
[YamlMember(Alias = "stream_endpoint", ApplyNamingConventions = false)] [YamlMember(Alias = "stream_endpoint", ApplyNamingConventions = false)]
public string? StreamEndpoint{ get; set; } public string? StreamEndpoint{ get; set; }
[YamlMember(Alias = "download_dir_path", ApplyNamingConventions = false)]
public string? DownloadDirPath{ get; set; }
} }

View File

@ -247,6 +247,9 @@ public class CrunchyEpMeta{
public List<string>? SelectedDubs{ get; set; } public List<string>? SelectedDubs{ get; set; }
public List<string>? AvailableSubs{ get; set; } public List<string>? AvailableSubs{ get; set; }
public string? DownloadPath{ get; set; }
} }
public class DownloadProgress{ public class DownloadProgress{
@ -267,4 +270,10 @@ public struct CrunchyEpMetaData{
public List<EpisodeVersion>? Versions{ get; set; } public List<EpisodeVersion>? Versions{ get; set; }
public bool IsSubbed{ get; set; } public bool IsSubbed{ get; set; }
public bool IsDubbed{ get; set; } public bool IsDubbed{ get; set; }
}
public struct CrunchyRollEpisodeData{
public string Key{ get; set; }
public EpisodeAndLanguage EpisodeAndLanguages{ get; set; }
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using CRD.Downloader; using CRD.Downloader;
@ -21,6 +22,8 @@ public partial class SeriesPageViewModel : ViewModelBase{
[ObservableProperty] [ObservableProperty]
public static bool _sonarrAvailable; public static bool _sonarrAvailable;
private IStorageProvider _storageProvider;
public SeriesPageViewModel(){ public SeriesPageViewModel(){
_selectedSeries = Crunchyroll.Instance.SelectedSeries; _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] [RelayCommand]
public void OpenSonarrPage(){ public void OpenSonarrPage(){
var sonarrProp = Crunchyroll.Instance.CrunOptions.SonarrProperties; var sonarrProp = Crunchyroll.Instance.CrunOptions.SonarrProperties;

View File

@ -6,9 +6,11 @@ using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Net.Mime; using System.Net.Mime;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform.Storage;
using Avalonia.Styling; using Avalonia.Styling;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
@ -249,15 +251,19 @@ public partial class SettingsPageViewModel : ViewModelBase{
new ComboBoxItem(){ Content = "tv/samsung" }, new ComboBoxItem(){ Content = "tv/samsung" },
}; };
[ObservableProperty]
private string _downloadDirPath;
private readonly FluentAvaloniaTheme _faTheme; private readonly FluentAvaloniaTheme _faTheme;
private bool settingsLoaded; private bool settingsLoaded;
private IStorageProvider _storageProvider;
public SettingsPageViewModel(){ public SettingsPageViewModel(){
var version = Assembly.GetExecutingAssembly().GetName().Version; var version = Assembly.GetExecutingAssembly().GetName().Version;
_currentVersion = $"{version?.Major}.{version?.Minor}.{version?.Build}"; _currentVersion = $"{version?.Major}.{version?.Minor}.{version?.Build}";
_faTheme = App.Current.Styles[0] as FluentAvaloniaTheme; _faTheme = App.Current.Styles[0] as FluentAvaloniaTheme;
foreach (var languageItem in Languages.languages){ foreach (var languageItem in Languages.languages){
@ -270,6 +276,8 @@ public partial class SettingsPageViewModel : ViewModelBase{
CrDownloadOptions options = Crunchyroll.Instance.CrunOptions; 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; ComboBoxItem? hsLang = HardSubLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.Hslang) ?? null;
SelectedHSLang = hsLang ?? HardSubLangList[0]; SelectedHSLang = hsLang ?? HardSubLangList[0];
@ -524,6 +532,32 @@ public partial class SettingsPageViewModel : ViewModelBase{
RaisePropertyChanged(nameof(FfmpegOptions)); 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){ partial void OnCurrentAppThemeChanged(ComboBoxItem? value){
if (value?.Content?.ToString() == "System"){ if (value?.Content?.ToString() == "System"){
_faTheme.PreferSystemTheme = true; _faTheme.PreferSystemTheme = true;

View File

@ -58,10 +58,16 @@ public partial class MainWindow : AppWindow{
if (message.Refresh){ if (message.Refresh){
navigationStack.Pop(); navigationStack.Pop();
var viewModel = Activator.CreateInstance(message.ViewModelType); var viewModel = Activator.CreateInstance(message.ViewModelType);
if (viewModel is SeriesPageViewModel){
((SeriesPageViewModel)viewModel).SetStorageProvider(StorageProvider);
}
navigationStack.Push(viewModel); navigationStack.Push(viewModel);
nv.Content = viewModel; nv.Content = viewModel;
} else if (!message.Back && message.ViewModelType != null){ } else if (!message.Back && message.ViewModelType != null){
var viewModel = Activator.CreateInstance(message.ViewModelType); var viewModel = Activator.CreateInstance(message.ViewModelType);
if (viewModel is SeriesPageViewModel){
((SeriesPageViewModel)viewModel).SetStorageProvider(StorageProvider);
}
navigationStack.Push(viewModel); navigationStack.Push(viewModel);
nv.Content = viewModel; nv.Content = viewModel;
} else{ } else{
@ -119,7 +125,9 @@ public partial class MainWindow : AppWindow{
selectedNavVieItem = selectedItem; selectedNavVieItem = selectedItem;
break; break;
case "Settings": case "Settings":
navView.Content = Activator.CreateInstance(typeof(SettingsPageViewModel)); var viewModel = (SettingsPageViewModel)Activator.CreateInstance(typeof(SettingsPageViewModel));
viewModel.SetStorageProvider(StorageProvider);
navView.Content = viewModel;
selectedNavVieItem = selectedItem; selectedNavVieItem = selectedItem;
break; break;
case "UpdateAvailable": case "UpdateAvailable":

View File

@ -66,7 +66,19 @@
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Button Command="{Binding UpdateData}" Margin="0 0 5 10">Fetch Series</Button> <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> </StackPanel>
@ -111,10 +123,12 @@
IsVisible="{Binding $parent[ItemsControl].((vm:SeriesPageViewModel)DataContext).SonarrAvailable}"> 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" /> 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" /> Height="25" />
@ -171,6 +185,19 @@
<controls:SymbolIcon Symbol="Refresh" FontSize="18" /> <controls:SymbolIcon Symbol="Refresh" FontSize="18" />
</StackPanel> </StackPanel>
</Button> </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" <Button Margin="10 0 0 0" FontStyle="Italic"
VerticalAlignment="Center" VerticalAlignment="Center"
Command="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).RemoveSeason}" Command="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).RemoveSeason}"

View File

@ -145,6 +145,25 @@
</controls:SettingsExpanderItem.Footer> </controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem> </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 Content="Simultaneous Downloads">
<controls:SettingsExpanderItem.Footer> <controls:SettingsExpanderItem.Footer>
<controls:NumberBox Minimum="0" Maximum="5" <controls:NumberBox Minimum="0" Maximum="5"

View File

@ -19,5 +19,4 @@ public partial class SettingsPageView : UserControl{
Crunchyroll.Instance.RefreshSonarr(); Crunchyroll.Instance.RefreshSonarr();
} }
} }
} }