mirror of
https://github.com/Crunchy-DL/Crunchy-Downloader.git
synced 2026-01-11 20:10:26 +00:00
Add - Added option to **download audio only as MP3**
Add - Added **File Name Whitespace Substitute** option in settings [#284](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/284) Add - Added **download mode toggle (video/audio/subs)** for seasons with the ability to switch between options [#281](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/281) Add - Added **download all** for only (video/audio/subs) for a season [#281](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/281) Chg - Changed to **display a message** when the calendar fails to load due to Cloudflare issues [#283](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/283) Chg - Adjusted **Calendar upcoming filter** for improved accuracy Fix - Fixed **duplicate/wrong Crunchyroll versions** appearing in downloads [#285](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/285) Fix - Fixed issue where **episodes with non-Japanese audio URLs** couldn't be added Fix - Fixed **calendar crash** on Cloudflare failure [#283](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/283) Fix - Fixed **audio-only downloads** [#279](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/279) Fix - Fixed **crash when no featured music** is present Fix - Fixed **"All" button not working** for music in the Add Downloads tab Fix - Fixed that an **empty File Name Whitespace Substitute** removed all whitespaces
This commit is contained in:
parent
d80f9b56a0
commit
67f3d7a84a
27 changed files with 787 additions and 113 deletions
|
|
@ -11,8 +11,10 @@ using CRD.Downloader.Crunchyroll;
|
|||
using CRD.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.Views;
|
||||
using HtmlAgilityPack;
|
||||
using Newtonsoft.Json;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace CRD.Downloader;
|
||||
|
||||
|
|
@ -75,6 +77,20 @@ public class CalendarManager{
|
|||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
if (!response.IsOk){
|
||||
if (response.ResponseContent.Contains("<title>Just a moment...</title>") ||
|
||||
response.ResponseContent.Contains("<title>Access denied</title>") ||
|
||||
response.ResponseContent.Contains("<title>Attention Required! | Cloudflare</title>") ||
|
||||
response.ResponseContent.Trim().Equals("error code: 1020") ||
|
||||
response.ResponseContent.IndexOf("<title>DDOS-GUARD</title>", StringComparison.OrdinalIgnoreCase) > -1){
|
||||
MessageBus.Current.SendMessage(new ToastMessage("Blocked by Cloudflare. Use the custom calendar.", ToastType.Error, 5));
|
||||
Console.Error.WriteLine($"Blocked by Cloudflare. Use the custom calendar.");
|
||||
} else{
|
||||
Console.Error.WriteLine($"Calendar request failed");
|
||||
}
|
||||
return new CalendarWeek();
|
||||
}
|
||||
|
||||
CalendarWeek week = new CalendarWeek();
|
||||
week.CalendarDays = new List<CalendarDay>();
|
||||
|
||||
|
|
@ -314,8 +330,8 @@ public class CalendarManager{
|
|||
if (ProgramManager.Instance.AnilistUpcoming.ContainsKey(calendarDay.DateTime.ToString("yyyy-MM-dd"))){
|
||||
var list = ProgramManager.Instance.AnilistUpcoming[calendarDay.DateTime.ToString("yyyy-MM-dd")];
|
||||
|
||||
foreach (var calendarEpisode in list.Where(calendarEpisode => calendarDay.DateTime.Date == calendarEpisode.DateTime.Date)
|
||||
.Where(calendarEpisode => calendarDay.CalendarEpisodes.All(ele => ele.CrSeriesID != calendarEpisode.CrSeriesID))){
|
||||
foreach (var calendarEpisode in list.Where(calendarEpisode => calendarDay.DateTime.Date.Day == calendarEpisode.DateTime.Date.Day)
|
||||
.Where(calendarEpisode => calendarDay.CalendarEpisodes.All(ele => ele.CrSeriesID != calendarEpisode.CrSeriesID && ele.SeasonName != calendarEpisode.SeasonName))){
|
||||
calendarDay.CalendarEpisodes.Add(calendarEpisode);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,24 @@ public class CrEpisode(){
|
|||
return null;
|
||||
}
|
||||
|
||||
if (epsidoe is{ Total: 1, Data: not null } &&
|
||||
(epsidoe.Data.First().Versions ?? [])
|
||||
.GroupBy(v => v.AudioLocale)
|
||||
.Any(g => g.Count() > 1)){
|
||||
Console.Error.WriteLine("Episode has Duplicate Audio Locales");
|
||||
var list = (epsidoe.Data.First().Versions ?? []).GroupBy(v => v.AudioLocale).Where(g => g.Count() > 1).ToList();
|
||||
//guid for episode id
|
||||
foreach (var episodeVersionse in list){
|
||||
foreach (var version in episodeVersionse){
|
||||
var checkRequest = HttpClientReq.CreateRequestMessage($"{ApiUrls.Cms}/episodes/{version.Guid}", HttpMethod.Get, true, true, query);
|
||||
var checkResponse = await HttpClientReq.Instance.SendHttpRequest(checkRequest,true);
|
||||
if (!checkResponse.IsOk){
|
||||
epsidoe.Data.First().Versions?.Remove(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (epsidoe.Total == 1 && epsidoe.Data != null){
|
||||
return epsidoe.Data.First();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public class CrMusic{
|
|||
public async Task<CrunchyMusicVideoList?> ParseFeaturedMusicVideoByIdAsync(string seriesId, string crLocale, bool forcedLang = false, bool updateHistory = false){
|
||||
var musicVideos = await FetchMediaListAsync($"{ApiUrls.Content}/music/featured/{seriesId}", crLocale, forcedLang);
|
||||
|
||||
if (musicVideos.Data is{ Count: > 0 } && updateHistory){
|
||||
if (musicVideos.Data is{ Count: > 0 } && updateHistory && crunInstance.CrunOptions.HistoryIncludeCrArtists){
|
||||
await crunInstance.History.UpdateWithMusicEpisodeList(musicVideos.Data);
|
||||
}
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ public class CrMusic{
|
|||
public async Task<CrunchyMusicVideo?> ParseMusicVideoByIdAsync(string id, string crLocale, bool forcedLang = false, bool updateHistory = false){
|
||||
var musicVideo = await ParseMediaByIdAsync(id, crLocale, forcedLang, "music/music_videos");
|
||||
|
||||
if (musicVideo != null && updateHistory){
|
||||
if (musicVideo != null && updateHistory && crunInstance.CrunOptions.HistoryIncludeCrArtists){
|
||||
await crunInstance.History.UpdateWithMusicEpisodeList([musicVideo]);
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ public class CrMusic{
|
|||
|
||||
if (concert != null){
|
||||
concert.EpisodeType = EpisodeType.Concert;
|
||||
if (updateHistory){
|
||||
if (updateHistory && crunInstance.CrunOptions.HistoryIncludeCrArtists){
|
||||
await crunInstance.History.UpdateWithMusicEpisodeList([concert]);
|
||||
}
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ public class CrMusic{
|
|||
public async Task<CrunchyMusicVideoList?> ParseArtistMusicVideosByIdAsync(string artistId, string crLocale, bool forcedLang = false, bool updateHistory = false){
|
||||
var musicVideos = await FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{artistId}/music_videos", crLocale, forcedLang);
|
||||
|
||||
if (updateHistory){
|
||||
if (updateHistory && crunInstance.CrunOptions.HistoryIncludeCrArtists){
|
||||
await crunInstance.History.UpdateWithMusicEpisodeList(musicVideos.Data);
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ public class CrMusic{
|
|||
}
|
||||
}
|
||||
|
||||
if (updateHistory){
|
||||
if (updateHistory && crunInstance.CrunOptions.HistoryIncludeCrArtists){
|
||||
await crunInstance.History.UpdateWithMusicEpisodeList(concerts.Data);
|
||||
}
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ public class CrMusic{
|
|||
musicVideos.Data.AddRange(concerts.Data);
|
||||
}
|
||||
|
||||
if (updateHistory){
|
||||
if (updateHistory && crunInstance.CrunOptions.HistoryIncludeCrArtists){
|
||||
await crunInstance.History.UpdateWithMusicEpisodeList(musicVideos.Data);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -337,6 +337,7 @@ public class CrunchyrollManager{
|
|||
SkipSubMux = options.SkipSubsMux,
|
||||
Output = fileNameAndPath + $".{keyValue.Value.First().Lang.Locale}",
|
||||
Mp4 = options.Mp4,
|
||||
Mp3 = options.AudioOnlyToMp3,
|
||||
MuxFonts = options.MuxFonts,
|
||||
VideoTitle = res.VideoTitle,
|
||||
Novids = options.Novids,
|
||||
|
|
@ -402,6 +403,7 @@ public class CrunchyrollManager{
|
|||
SkipSubMux = options.SkipSubsMux,
|
||||
Output = fileNameAndPath,
|
||||
Mp4 = options.Mp4,
|
||||
Mp3 = options.AudioOnlyToMp3,
|
||||
MuxFonts = options.MuxFonts,
|
||||
VideoTitle = res.VideoTitle,
|
||||
Novids = options.Novids,
|
||||
|
|
@ -599,8 +601,10 @@ public class CrunchyrollManager{
|
|||
|
||||
if (options.Novids == true || data.FindAll(a => a.Type == DownloadMediaType.Video).Count == 0){
|
||||
if (data.FindAll(a => a.Type == DownloadMediaType.Audio).Count > 0){
|
||||
Console.WriteLine("Mux to MP3");
|
||||
muxToMp3 = true;
|
||||
if (options.Mp3){
|
||||
Console.WriteLine("Mux to MP3");
|
||||
muxToMp3 = true;
|
||||
}
|
||||
} else{
|
||||
Console.WriteLine("Skip muxing since no videos are downloaded");
|
||||
return (null, false, false, "");
|
||||
|
|
@ -643,9 +647,9 @@ public class CrunchyrollManager{
|
|||
var merger = new Merger(new MergerOptions{
|
||||
DubLangList = options.DubLangList,
|
||||
SubLangList = options.SubLangList,
|
||||
OnlyVid = data.Where(a => a.Type == DownloadMediaType.Video).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList(),
|
||||
OnlyVid = data.Where(a => a.Type == DownloadMediaType.Video).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty ,Bitrate = a.bitrate}).ToList(),
|
||||
SkipSubMux = options.SkipSubMux,
|
||||
OnlyAudio = data.Where(a => a.Type == DownloadMediaType.Audio).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList(),
|
||||
OnlyAudio = data.Where(a => a.Type == DownloadMediaType.Audio).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty ,Bitrate = a.bitrate}).ToList(),
|
||||
Output = $"{filename}.{(muxToMp3 ? "mp3" : options.Mp4 ? "mp4" : "mkv")}",
|
||||
Subtitles = data.Where(a => a.Type == DownloadMediaType.Subtitle).Select(a => new SubtitleInput
|
||||
{ File = a.Path ?? string.Empty, Language = a.Language, ClosedCaption = a.Cc ?? false, Signs = a.Signs ?? false, RelatedVideoDownloadMedia = a.RelatedVideoDownloadMedia }).ToList(),
|
||||
|
|
@ -682,7 +686,7 @@ public class CrunchyrollManager{
|
|||
List<string> notSyncedDubs =[];
|
||||
|
||||
|
||||
if (options is{ SyncTiming: true, DlVideoOnce: true }){
|
||||
if (options is{ SyncTiming: true, DlVideoOnce: true } && merger.options.OnlyVid.Count > 0 && merger.options.OnlyAudio.Count > 0){
|
||||
crunchyEpMeta.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = true,
|
||||
Percent = 100,
|
||||
|
|
@ -916,7 +920,7 @@ public class CrunchyrollManager{
|
|||
if (mediaGuid.Contains(':')){
|
||||
mediaGuid = mediaGuid.Split(':')[1];
|
||||
}
|
||||
|
||||
|
||||
Console.WriteLine("MediaGuid: " + mediaId);
|
||||
|
||||
#region Chapters
|
||||
|
|
@ -1154,7 +1158,7 @@ public class CrunchyrollManager{
|
|||
Console.WriteLine("Downloading video...");
|
||||
curStream = streams[options.Kstream - 1];
|
||||
|
||||
Console.WriteLine($"Playlists URL: {curStream.Url} ({curStream.Type})");
|
||||
Console.WriteLine($"Playlists URL: {string.Join(", ",curStream.Url)} ({curStream.Type})");
|
||||
}
|
||||
|
||||
string tsFile = "";
|
||||
|
|
@ -1376,7 +1380,7 @@ public class CrunchyrollManager{
|
|||
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.FileNameWhitespaceSubstitute, options.Override).ToArray());
|
||||
|
||||
string onlyFileName = Path.GetFileName(fileName);
|
||||
int maxLength = 220;
|
||||
|
|
@ -1391,7 +1395,7 @@ public class CrunchyrollManager{
|
|||
|
||||
if (excessLength > 0 && ((string)titleVariable.ReplaceWith).Length > excessLength){
|
||||
titleVariable.ReplaceWith = ((string)titleVariable.ReplaceWith).Substring(0, ((string)titleVariable.ReplaceWith).Length - excessLength);
|
||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers,options.FileNameWhitespaceSubstitute, options.Override).ToArray());
|
||||
onlyFileName = Path.GetFileName(fileName);
|
||||
|
||||
if (onlyFileName.Length > maxLength){
|
||||
|
|
@ -1410,7 +1414,7 @@ public class CrunchyrollManager{
|
|||
string outFile = fileName + "." + (epMeta.Lang?.CrLocale ?? lang.CrLocale);
|
||||
|
||||
string tempFile = Path.Combine(FileNameManager
|
||||
.ParseFileName($"temp-{(!string.IsNullOrEmpty(currentVersion.Guid) ? currentVersion.Guid : currentMediaId)}", variables, options.Numbers, options.Override)
|
||||
.ParseFileName($"temp-{(!string.IsNullOrEmpty(currentVersion.Guid) ? currentVersion.Guid : currentMediaId)}", variables, options.Numbers,options.FileNameWhitespaceSubstitute, options.Override)
|
||||
.ToArray());
|
||||
string tempTsFile = Path.IsPathRooted(tempFile) ? tempFile : Path.Combine(fileDir, tempFile);
|
||||
|
||||
|
|
@ -1608,7 +1612,8 @@ public class CrunchyrollManager{
|
|||
Path = $"{tsFile}.video.m4s",
|
||||
Lang = lang,
|
||||
Language = lang,
|
||||
IsPrimary = isPrimary
|
||||
IsPrimary = isPrimary,
|
||||
bitrate = chosenVideoSegments.bandwidth / 1024
|
||||
};
|
||||
files.Add(videoDownloadMedia);
|
||||
data.downloadedFiles.Add($"{tsFile}.video.m4s");
|
||||
|
|
@ -1676,7 +1681,8 @@ public class CrunchyrollManager{
|
|||
Type = DownloadMediaType.Audio,
|
||||
Path = $"{tsFile}.audio.m4s",
|
||||
Lang = lang,
|
||||
IsPrimary = isPrimary
|
||||
IsPrimary = isPrimary,
|
||||
bitrate = chosenAudioSegments.bandwidth / 1000
|
||||
});
|
||||
data.downloadedFiles.Add($"{tsFile}.audio.m4s");
|
||||
} else{
|
||||
|
|
@ -1693,7 +1699,8 @@ public class CrunchyrollManager{
|
|||
Path = $"{tsFile}.video.m4s",
|
||||
Lang = lang,
|
||||
Language = lang,
|
||||
IsPrimary = isPrimary
|
||||
IsPrimary = isPrimary,
|
||||
bitrate = chosenVideoSegments.bandwidth / 1024
|
||||
};
|
||||
files.Add(videoDownloadMedia);
|
||||
data.downloadedFiles.Add($"{tsFile}.video.m4s");
|
||||
|
|
@ -1704,13 +1711,14 @@ public class CrunchyrollManager{
|
|||
Type = DownloadMediaType.Audio,
|
||||
Path = $"{tsFile}.audio.m4s",
|
||||
Lang = lang,
|
||||
IsPrimary = isPrimary
|
||||
IsPrimary = isPrimary,
|
||||
bitrate = chosenAudioSegments.bandwidth / 1000
|
||||
});
|
||||
data.downloadedFiles.Add($"{tsFile}.audio.m4s");
|
||||
}
|
||||
}
|
||||
} else if (options.Novids){
|
||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers,options.FileNameWhitespaceSubstitute, options.Override).ToArray());
|
||||
Console.WriteLine("Downloading skipped!");
|
||||
}
|
||||
}
|
||||
|
|
@ -1718,14 +1726,14 @@ public class CrunchyrollManager{
|
|||
variables.Add(new Variable("height", 360, false));
|
||||
variables.Add(new Variable("width", 640, false));
|
||||
|
||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers,options.FileNameWhitespaceSubstitute, options.Override).ToArray());
|
||||
}
|
||||
|
||||
if (compiledChapters.Count > 0 && options is not{ Novids: true, Noaudio: true }){
|
||||
try{
|
||||
// Parsing and constructing the file names
|
||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
||||
var outFile = Path.Combine(FileNameManager.ParseFileName(options.FileName + "." + (epMeta.Lang?.CrLocale), variables, options.Numbers, options.Override).ToArray());
|
||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers,options.FileNameWhitespaceSubstitute, options.Override).ToArray());
|
||||
var outFile = Path.Combine(FileNameManager.ParseFileName(options.FileName + "." + (epMeta.Lang?.CrLocale), variables, options.Numbers,options.FileNameWhitespaceSubstitute, options.Override).ToArray());
|
||||
if (Path.IsPathRooted(outFile)){
|
||||
tsFile = outFile;
|
||||
} else{
|
||||
|
|
@ -1847,7 +1855,7 @@ public class CrunchyrollManager{
|
|||
Error = dlFailed,
|
||||
FileName = fileName.Length > 0 ? fileName : "unknown - " + Guid.NewGuid(),
|
||||
ErrorText = "",
|
||||
VideoTitle = FileNameManager.ParseFileName(options.VideoTitle ?? "", variables, options.Numbers, options.Override).Last(),
|
||||
VideoTitle = FileNameManager.ParseFileName(options.VideoTitle ?? "", variables, options.Numbers,options.FileNameWhitespaceSubstitute, options.Override).Last(),
|
||||
FolderPath = fileDir,
|
||||
TempFolderPath = tempFolderPath
|
||||
};
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
private bool _muxToMp4;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _muxToMp3;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _muxFonts;
|
||||
|
|
@ -93,6 +96,9 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
private string _fileName = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _fileNameWhitespaceSubstitute = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _fileTitle = "";
|
||||
|
|
@ -347,11 +353,13 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
KeepDubsSeparate = options.KeepDubsSeperate;
|
||||
DownloadChapters = options.Chapters;
|
||||
MuxToMp4 = options.Mp4;
|
||||
MuxToMp3 = options.AudioOnlyToMp3;
|
||||
MuxFonts = options.MuxFonts;
|
||||
SyncTimings = options.SyncTiming;
|
||||
SkipSubMux = options.SkipSubsMux;
|
||||
LeadingNumbers = options.Numbers;
|
||||
FileName = options.FileName;
|
||||
FileNameWhitespaceSubstitute = options.FileNameWhitespaceSubstitute;
|
||||
SearchFetchFeaturedMusic = options.SearchFetchFeaturedMusic;
|
||||
|
||||
ComboBoxItem? qualityAudio = AudioQualityList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.QualityAudio) ?? null;
|
||||
|
|
@ -413,11 +421,13 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
CrunchyrollManager.Instance.CrunOptions.Chapters = DownloadChapters;
|
||||
CrunchyrollManager.Instance.CrunOptions.SkipMuxing = SkipMuxing;
|
||||
CrunchyrollManager.Instance.CrunOptions.Mp4 = MuxToMp4;
|
||||
CrunchyrollManager.Instance.CrunOptions.AudioOnlyToMp3 = MuxToMp3;
|
||||
CrunchyrollManager.Instance.CrunOptions.MuxFonts = MuxFonts;
|
||||
CrunchyrollManager.Instance.CrunOptions.SyncTiming = SyncTimings;
|
||||
CrunchyrollManager.Instance.CrunOptions.SkipSubsMux = SkipSubMux;
|
||||
CrunchyrollManager.Instance.CrunOptions.Numbers = Math.Clamp((int)(LeadingNumbers ?? 0), 0, 10);
|
||||
CrunchyrollManager.Instance.CrunOptions.FileName = FileName;
|
||||
CrunchyrollManager.Instance.CrunOptions.FileNameWhitespaceSubstitute = FileNameWhitespaceSubstitute;
|
||||
CrunchyrollManager.Instance.CrunOptions.IncludeSignsSubs = IncludeSignSubs;
|
||||
CrunchyrollManager.Instance.CrunOptions.IncludeCcSubs = IncludeCcSubs;
|
||||
CrunchyrollManager.Instance.CrunOptions.Partsize = Math.Clamp((int)(PartSize ?? 1), 1, 10000);
|
||||
|
|
|
|||
|
|
@ -319,6 +319,14 @@
|
|||
HorizontalAlignment="Stretch" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="FileName Whitespace Substitute"
|
||||
Description="Character used to replace whitespace in file name variables like ${seriesTitle}">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox HorizontalAlignment="Left" MinWidth="50"
|
||||
Text="{Binding FileNameWhitespaceSubstitute}" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Filename"
|
||||
Description="${seriesTitle} ${seasonTitle} ${title} ${season} ${episode} ${height} ${width} ${dubs} - Folder with \\">
|
||||
|
|
@ -345,11 +353,17 @@
|
|||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="MP4" Description="Outputs a mp4 instead of a mkv - not recommended to use this option">
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="MP4" Description="Outputs an MP4 instead of an MKV — not recommended to use this option">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding MuxToMp4}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="MP3" Description="Outputs an MP3 instead of an MKV/MP4 if only audio streams were downloaded">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding MuxToMp3}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Keep Subtitles separate">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
|
|
|
|||
|
|
@ -37,8 +37,12 @@ public class History{
|
|||
}
|
||||
}
|
||||
} else{
|
||||
foreach (var historyEpisode in historySeries.Seasons.First(historySeason => historySeason.SeasonId == seasonId).EpisodesList){
|
||||
historyEpisode.IsEpisodeAvailableOnStreamingService = false;
|
||||
var matchingSeason = historySeries.Seasons.FirstOrDefault(historySeason => historySeason.SeasonId == seasonId);
|
||||
|
||||
if (matchingSeason != null){
|
||||
foreach (var historyEpisode in matchingSeason.EpisodesList){
|
||||
historyEpisode.IsEpisodeAvailableOnStreamingService = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -165,6 +169,7 @@ public class History{
|
|||
EpisodeCrPremiumAirDate = historySource.GetAvailableDate(),
|
||||
EpisodeType = historySource.GetEpisodeType(),
|
||||
IsEpisodeAvailableOnStreamingService = true,
|
||||
ThumbnailImageUrl = historySource.GetImageUrl(),
|
||||
};
|
||||
|
||||
historySeason.EpisodesList.Add(newHistoryEpisode);
|
||||
|
|
@ -179,6 +184,7 @@ public class History{
|
|||
historyEpisode.EpisodeCrPremiumAirDate = historySource.GetAvailableDate();
|
||||
historyEpisode.EpisodeType = historySource.GetEpisodeType();
|
||||
historyEpisode.IsEpisodeAvailableOnStreamingService = true;
|
||||
historyEpisode.ThumbnailImageUrl = historySource.GetImageUrl();
|
||||
|
||||
historyEpisode.HistoryEpisodeAvailableDubLang = historySource.GetEpisodeAvailableDubLang();
|
||||
historyEpisode.HistoryEpisodeAvailableSoftSubs = historySource.GetEpisodeAvailableSoftSubs();
|
||||
|
|
@ -577,7 +583,8 @@ public class History{
|
|||
HistoryEpisodeAvailableSoftSubs = historySource.GetEpisodeAvailableSoftSubs(),
|
||||
EpisodeCrPremiumAirDate = historySource.GetAvailableDate(),
|
||||
EpisodeType = historySource.GetEpisodeType(),
|
||||
IsEpisodeAvailableOnStreamingService = true
|
||||
IsEpisodeAvailableOnStreamingService = true,
|
||||
ThumbnailImageUrl = historySource.GetImageUrl(),
|
||||
};
|
||||
|
||||
newSeason.EpisodesList.Add(newHistoryEpisode);
|
||||
|
|
@ -634,7 +641,7 @@ public class History{
|
|||
var historyEpisodesWithSonarrIds = allHistoryEpisodes
|
||||
.Where(e => !string.IsNullOrEmpty(e.SonarrEpisodeId))
|
||||
.ToList();
|
||||
|
||||
|
||||
Parallel.ForEach(historyEpisodesWithSonarrIds, historyEpisode => {
|
||||
var sonarrEpisode = episodes.FirstOrDefault(e => e.Id.ToString().Equals(historyEpisode.SonarrEpisodeId));
|
||||
|
||||
|
|
@ -644,9 +651,9 @@ public class History{
|
|||
});
|
||||
|
||||
var historyEpisodeIds = new HashSet<string>(historyEpisodesWithSonarrIds.Select(e => e.SonarrEpisodeId!));
|
||||
|
||||
|
||||
episodes.RemoveAll(e => historyEpisodeIds.Contains(e.Id.ToString()));
|
||||
|
||||
|
||||
allHistoryEpisodes = allHistoryEpisodes
|
||||
.Where(e => string.IsNullOrEmpty(e.SonarrEpisodeId))
|
||||
.ToList();
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ public partial class QueueManager : ObservableObject{
|
|||
}
|
||||
|
||||
|
||||
public async Task CrAddEpisodeToQueue(string epId, string crLocale, List<string> dubLang, bool updateHistory = false, bool onlySubs = false){
|
||||
public async Task CrAddEpisodeToQueue(string epId, string crLocale, List<string> dubLang, bool updateHistory = false, EpisodeDownloadMode episodeDownloadMode = EpisodeDownloadMode.Default){
|
||||
if (string.IsNullOrEmpty(epId)){
|
||||
return;
|
||||
}
|
||||
|
|
@ -158,7 +158,7 @@ public partial class QueueManager : ObservableObject{
|
|||
|
||||
selected.DownloadSubs = historyEpisode.sublist.Count > 0 ? historyEpisode.sublist : CrunchyrollManager.Instance.CrunOptions.DlSubs;
|
||||
|
||||
selected.OnlySubs = onlySubs;
|
||||
selected.OnlySubs = episodeDownloadMode == EpisodeDownloadMode.OnlySubs;
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub && selected.Data.Count > 1){
|
||||
var sortedMetaData = selected.Data
|
||||
|
|
@ -178,9 +178,24 @@ public partial class QueueManager : ObservableObject{
|
|||
|
||||
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
|
||||
|
||||
if (selected.OnlySubs){
|
||||
newOptions.Novids = true;
|
||||
newOptions.Noaudio = true;
|
||||
switch (episodeDownloadMode){
|
||||
case EpisodeDownloadMode.OnlyVideo:
|
||||
newOptions.Novids = false;
|
||||
newOptions.Noaudio = true;
|
||||
selected.DownloadSubs = ["none"];
|
||||
break;
|
||||
case EpisodeDownloadMode.OnlyAudio:
|
||||
newOptions.Novids = true;
|
||||
newOptions.Noaudio = false;
|
||||
selected.DownloadSubs = ["none"];
|
||||
break;
|
||||
case EpisodeDownloadMode.OnlySubs:
|
||||
newOptions.Novids = true;
|
||||
newOptions.Noaudio = true;
|
||||
break;
|
||||
case EpisodeDownloadMode.Default:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
newOptions.DubLang = dubLang;
|
||||
|
|
@ -227,13 +242,28 @@ public partial class QueueManager : ObservableObject{
|
|||
|
||||
if (movieMeta != null){
|
||||
movieMeta.DownloadSubs = CrunchyrollManager.Instance.CrunOptions.DlSubs;
|
||||
movieMeta.OnlySubs = onlySubs;
|
||||
movieMeta.OnlySubs = episodeDownloadMode == EpisodeDownloadMode.OnlySubs;
|
||||
|
||||
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
|
||||
|
||||
if (movieMeta.OnlySubs){
|
||||
newOptions.Novids = true;
|
||||
newOptions.Noaudio = true;
|
||||
switch (episodeDownloadMode){
|
||||
case EpisodeDownloadMode.OnlyVideo:
|
||||
newOptions.Novids = false;
|
||||
newOptions.Noaudio = true;
|
||||
movieMeta.DownloadSubs = ["none"];
|
||||
break;
|
||||
case EpisodeDownloadMode.OnlyAudio:
|
||||
newOptions.Novids = true;
|
||||
newOptions.Noaudio = false;
|
||||
movieMeta.DownloadSubs = ["none"];
|
||||
break;
|
||||
case EpisodeDownloadMode.OnlySubs:
|
||||
newOptions.Novids = true;
|
||||
newOptions.Noaudio = true;
|
||||
break;
|
||||
case EpisodeDownloadMode.Default:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
newOptions.DubLang = dubLang;
|
||||
|
|
|
|||
|
|
@ -251,6 +251,13 @@ public enum CrunchyUrlType{
|
|||
Unknown
|
||||
}
|
||||
|
||||
public enum EpisodeDownloadMode{
|
||||
Default,
|
||||
OnlyVideo,
|
||||
OnlyAudio,
|
||||
OnlySubs,
|
||||
}
|
||||
|
||||
public enum SonarrCoverType{
|
||||
Banner,
|
||||
FanArt,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ using CRD.Utils.Structs;
|
|||
namespace CRD.Utils.Files;
|
||||
|
||||
public class FileNameManager{
|
||||
public static List<string> ParseFileName(string input, List<Variable> variables, int numbers, List<string> @override){
|
||||
public static List<string> ParseFileName(string input, List<Variable> variables, int numbers,string whiteSpaceReplace, List<string> @override){
|
||||
Regex varRegex = new Regex(@"\${[A-Za-z1-9]+}");
|
||||
var matches = varRegex.Matches(input).Cast<Match>().Select(m => m.Value).ToList();
|
||||
var overriddenVars = ParseOverride(variables, @override);
|
||||
|
|
@ -27,7 +27,7 @@ public class FileNameManager{
|
|||
continue;
|
||||
}
|
||||
|
||||
string replacement = variable.ReplaceWith.ToString();
|
||||
string replacement = variable.ReplaceWith.ToString() ?? string.Empty;
|
||||
if (variable.Type == "int32"){
|
||||
int len = replacement.Length;
|
||||
replacement = len < numbers ? new string('0', numbers - len) + replacement : replacement;
|
||||
|
|
@ -38,6 +38,9 @@ public class FileNameManager{
|
|||
replacement = replacement.Replace(",", ".");
|
||||
} else if (variable.Sanitize){
|
||||
replacement = CleanupFilename(replacement);
|
||||
if (variable.Type == "string" && !string.IsNullOrEmpty(whiteSpaceReplace)){
|
||||
replacement = replacement.Replace(" ",whiteSpaceReplace);
|
||||
}
|
||||
}
|
||||
|
||||
input = input.Replace(match, replacement);
|
||||
|
|
|
|||
|
|
@ -131,15 +131,14 @@ public class Merger{
|
|||
}
|
||||
|
||||
|
||||
foreach (var aud in options.OnlyAudio){
|
||||
args.Add($"-i \"{aud.Path}\"");
|
||||
metaData.Add($"-map {index}");
|
||||
metaData.Add($"-metadata:s:a:{audioIndex} language={aud.Language.Code}");
|
||||
index++;
|
||||
audioIndex++;
|
||||
if (options.OnlyAudio.Count > 1){
|
||||
Console.Error.WriteLine("Multiple audio files detected. Only one audio file can be converted to MP3 at a time.");
|
||||
}
|
||||
|
||||
args.Add("-c:a copy");
|
||||
var audio = options.OnlyAudio.First();
|
||||
|
||||
args.Add($"-i \"{audio.Path}\"");
|
||||
args.Add("-c:a libmp3lame" + (audio.Bitrate > 0 ? $" -b:a {audio.Bitrate}k" : "") );
|
||||
args.Add($"\"{options.Output}\"");
|
||||
return string.Join(" ", args);
|
||||
}
|
||||
|
|
@ -443,6 +442,7 @@ public class MergerInput{
|
|||
public int? Duration{ get; set; }
|
||||
public int? Delay{ get; set; }
|
||||
public bool? IsPrimary{ get; set; }
|
||||
public int? Bitrate{ get; set; }
|
||||
}
|
||||
|
||||
public class SubtitleInput{
|
||||
|
|
@ -469,6 +469,7 @@ public class CrunchyMuxOptions{
|
|||
public bool? KeepAllVideos{ get; set; }
|
||||
public bool? Novids{ get; set; }
|
||||
public bool Mp4{ get; set; }
|
||||
public bool Mp3{ get; set; }
|
||||
public bool MuxFonts{ get; set; }
|
||||
public bool MuxDescription{ get; set; }
|
||||
public string ForceMuxer{ get; set; }
|
||||
|
|
|
|||
|
|
@ -155,6 +155,9 @@ public class CrDownloadOptions{
|
|||
[JsonProperty("quality_audio")]
|
||||
public string QualityAudio{ get; set; } = "";
|
||||
|
||||
[JsonProperty("file_name_whitespace_substitute")]
|
||||
public string FileNameWhitespaceSubstitute{ get; set; } = "";
|
||||
|
||||
[JsonProperty("file_name")]
|
||||
public string FileName{ get; set; } = "";
|
||||
|
||||
|
|
@ -194,6 +197,9 @@ public class CrDownloadOptions{
|
|||
[JsonProperty("mux_mp4")]
|
||||
public bool Mp4{ get; set; }
|
||||
|
||||
[JsonProperty("mux_audio_only_to_mp3")]
|
||||
public bool AudioOnlyToMp3 { get; set; }
|
||||
|
||||
[JsonProperty("mux_fonts")]
|
||||
public bool MuxFonts{ get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -289,6 +289,14 @@ public class CrunchyEpisode : IHistorySource{
|
|||
public EpisodeType GetEpisodeType(){
|
||||
return EpisodeType;
|
||||
}
|
||||
|
||||
public string GetImageUrl(){
|
||||
if (Images != null){
|
||||
return Images.Thumbnail?.First().First().Source ?? string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CRD.Utils.Structs.History;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
|
@ -170,6 +171,14 @@ public class CrunchyMusicVideo : IHistorySource{
|
|||
public EpisodeType GetEpisodeType(){
|
||||
return EpisodeType;
|
||||
}
|
||||
|
||||
public string GetImageUrl(){
|
||||
if (Images != null){
|
||||
return Images.Thumbnail.First().Source ?? string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,8 @@ public class DownloadedMedia : SxItem{
|
|||
public DownloadMediaType Type{ get; set; }
|
||||
public required LanguageItem Lang{ get; set; }
|
||||
public bool IsPrimary{ get; set; }
|
||||
|
||||
|
||||
public int bitrate{ get; set; }
|
||||
public bool? Cc{ get; set; }
|
||||
public bool? Signs{ get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media.Imaging;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Files;
|
||||
|
|
@ -34,19 +35,22 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
|||
|
||||
[JsonProperty("episode_special_episode")]
|
||||
public bool SpecialEpisode{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("episode_available_on_streaming_service")]
|
||||
public bool IsEpisodeAvailableOnStreamingService{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("episode_type")]
|
||||
public EpisodeType EpisodeType{ get; set; } = EpisodeType.Unknown;
|
||||
|
||||
[JsonProperty("episode_thumbnail_url")]
|
||||
public string? ThumbnailImageUrl{ get; set; }
|
||||
|
||||
[JsonProperty("sonarr_episode_id")]
|
||||
public string? SonarrEpisodeId{ get; set; }
|
||||
|
||||
[JsonProperty("sonarr_has_file")]
|
||||
public bool SonarrHasFile{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("sonarr_is_monitored")]
|
||||
public bool SonarrIsMonitored{ get; set; }
|
||||
|
||||
|
|
@ -70,12 +74,32 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
|||
return $"S{SonarrSeasonNumber}E{SonarrEpisodeNumber}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[JsonProperty("history_episode_available_soft_subs")]
|
||||
public List<string> HistoryEpisodeAvailableSoftSubs{ get; set; } =[];
|
||||
|
||||
[JsonProperty("history_episode_available_dub_lang")]
|
||||
public List<string> HistoryEpisodeAvailableDubLang{ get; set; } =[];
|
||||
|
||||
[JsonIgnore]
|
||||
public Bitmap? ThumbnailImage{ get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsImageLoaded{ get; private set; } = false;
|
||||
|
||||
public async Task LoadImage(){
|
||||
if (IsImageLoaded || string.IsNullOrEmpty(ThumbnailImageUrl))
|
||||
return;
|
||||
|
||||
try{
|
||||
ThumbnailImage = await Helpers.LoadImage(ThumbnailImageUrl);
|
||||
IsImageLoaded = true;
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ThumbnailImage)));
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine("Failed to load image: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string ReleaseDateFormated{
|
||||
|
|
@ -92,7 +116,7 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
|||
return string.Format("{0:00}.{1}.{2}", EpisodeCrPremiumAirDate.Value.Day, monthAbbreviation, EpisodeCrPremiumAirDate.Value.Year);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public void ToggleWasDownloaded(){
|
||||
|
|
@ -115,7 +139,11 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
|||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
public async Task DownloadEpisode(bool onlySubs = false){
|
||||
public async Task DownloadEpisodeDefault(){
|
||||
await DownloadEpisode();
|
||||
}
|
||||
|
||||
public async Task DownloadEpisode(EpisodeDownloadMode episodeDownloadMode = EpisodeDownloadMode.Default){
|
||||
switch (EpisodeType){
|
||||
case EpisodeType.MusicVideo:
|
||||
await QueueManager.Instance.CrAddMusicVideoToQueue(EpisodeId ?? string.Empty);
|
||||
|
|
@ -128,19 +156,19 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
|||
default:
|
||||
await QueueManager.Instance.CrAddEpisodeToQueue(EpisodeId ?? string.Empty,
|
||||
string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.HistoryLang) ? CrunchyrollManager.Instance.DefaultLocale : CrunchyrollManager.Instance.CrunOptions.HistoryLang,
|
||||
CrunchyrollManager.Instance.CrunOptions.DubLang, false, onlySubs);
|
||||
CrunchyrollManager.Instance.CrunOptions.DubLang, false, episodeDownloadMode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void AssignSonarrEpisodeData(SonarrEpisode episode) {
|
||||
|
||||
public void AssignSonarrEpisodeData(SonarrEpisode episode){
|
||||
SonarrEpisodeId = episode.Id.ToString();
|
||||
SonarrEpisodeNumber = episode.EpisodeNumber.ToString();
|
||||
SonarrHasFile = episode.HasFile;
|
||||
SonarrIsMonitored = episode.Monitored;
|
||||
SonarrAbsolutNumber = episode.AbsoluteEpisodeNumber.ToString();
|
||||
SonarrSeasonNumber = episode.SeasonNumber.ToString();
|
||||
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SonarrSeasonEpisodeText)));
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,8 @@ public interface IHistorySource{
|
|||
string GetSeasonNum();
|
||||
string GetSeasonId();
|
||||
|
||||
string GetImageUrl();
|
||||
|
||||
string GetEpisodeId();
|
||||
string GetEpisodeNumber();
|
||||
string GetEpisodeTitle();
|
||||
|
|
|
|||
26
CRD/Utils/UI/UiEnumToBoolConverter.cs
Normal file
26
CRD/Utils/UI/UiEnumToBoolConverter.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace CRD.Utils.UI;
|
||||
|
||||
public class UiEnumToBoolConverter : IValueConverter{
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture){
|
||||
if (value == null || parameter == null)
|
||||
return false;
|
||||
|
||||
string enumString = parameter.ToString();
|
||||
if (enumString == null)
|
||||
return false;
|
||||
|
||||
return value.ToString() == enumString;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object? parameter, CultureInfo culture){
|
||||
if ((bool)value && parameter != null){
|
||||
return Enum.Parse(targetType, parameter.ToString() ?? string.Empty);
|
||||
}
|
||||
|
||||
return Avalonia.Data.BindingOperations.DoNothing;
|
||||
}
|
||||
}
|
||||
|
|
@ -208,6 +208,11 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
QueueManager.Instance.CrAddMusicMetaToQueue(meta);
|
||||
}
|
||||
}
|
||||
} else if (AddAllEpisodes){
|
||||
var musicClass = CrunchyrollManager.Instance.CrMusic;
|
||||
foreach (var meta in currentMusicVideoList.Data.Select(crunchyMusicVideo => musicClass.EpisodeMeta(crunchyMusicVideo))){
|
||||
QueueManager.Instance.CrAddMusicMetaToQueue(meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -363,7 +368,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
private void PopulateItemsFromMusicVideoList(){
|
||||
if (currentMusicVideoList?.Data != null){
|
||||
if (currentMusicVideoList?.Data is{ Count: > 0 }){
|
||||
foreach (var episode in currentMusicVideoList.Data){
|
||||
string seasonKey;
|
||||
switch (episode.EpisodeType){
|
||||
|
|
@ -394,7 +399,9 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
}
|
||||
}
|
||||
|
||||
CurrentSelectedSeason = SeasonList.First();
|
||||
if (SeasonList.Count > 0){
|
||||
CurrentSelectedSeason = SeasonList.First();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
var refreshDate = DateTime.Now;
|
||||
if (currentWeek?.FirstDayOfWeek != null){
|
||||
if (currentWeek?.FirstDayOfWeek != null && currentWeek.FirstDayOfWeek != DateTime.MinValue){
|
||||
refreshDate = currentWeek.FirstDayOfWeek.AddDays(-1);
|
||||
}
|
||||
|
||||
|
|
@ -239,7 +239,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
var refreshDate = DateTime.Now;
|
||||
if (currentWeek?.FirstDayOfWeek != null){
|
||||
if (currentWeek?.FirstDayOfWeek != null && currentWeek.FirstDayOfWeek != DateTime.MinValue){
|
||||
refreshDate = currentWeek.FirstDayOfWeek.AddDays(13);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ using CRD.Utils.Structs;
|
|||
using CRD.Utils.Structs.History;
|
||||
using CRD.Views;
|
||||
using DynamicData;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
|
@ -115,6 +116,16 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
[ObservableProperty]
|
||||
private static string _progressText;
|
||||
|
||||
#region Table Mode
|
||||
|
||||
[ObservableProperty]
|
||||
private static EpisodeDownloadMode _selectedDownloadMode = EpisodeDownloadMode.OnlySubs;
|
||||
|
||||
[ObservableProperty]
|
||||
public Symbol _selectedDownloadIcon = Symbol.ClosedCaption;
|
||||
|
||||
#endregion
|
||||
|
||||
public Vector LastScrollOffset { get; set; } = Vector.Zero;
|
||||
|
||||
public HistoryPageViewModel(){
|
||||
|
|
@ -528,6 +539,26 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
await episode.DownloadEpisode();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DownloadEpisodeOnlyOptions(HistoryEpisode episode){
|
||||
var downloadMode = SelectedDownloadMode;
|
||||
|
||||
if (downloadMode != EpisodeDownloadMode.Default){
|
||||
await episode.DownloadEpisode(downloadMode);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DownloadSeasonAllOnlyOptions(HistorySeason season){
|
||||
var downloadMode = SelectedDownloadMode;
|
||||
|
||||
if (downloadMode != EpisodeDownloadMode.Default){
|
||||
foreach (var episode in season.EpisodesList){
|
||||
await episode.DownloadEpisode(downloadMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ToggleDownloadedMark(SeasonDialogArgs seriesArgs){
|
||||
|
|
@ -575,6 +606,15 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
public void ToggleInactive(){
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
partial void OnSelectedDownloadModeChanged(EpisodeDownloadMode value){
|
||||
SelectedDownloadIcon = SelectedDownloadMode switch{
|
||||
EpisodeDownloadMode.OnlyVideo => Symbol.Video,
|
||||
EpisodeDownloadMode.OnlyAudio => Symbol.Audio,
|
||||
EpisodeDownloadMode.OnlySubs => Symbol.ClosedCaption,
|
||||
_ => Symbol.ClosedCaption
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class HistoryPageProperties{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
|
|
@ -32,9 +33,18 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
[ObservableProperty]
|
||||
public static bool _showMonitoredBookmark;
|
||||
|
||||
[ObservableProperty]
|
||||
public static bool _showFeaturedMusicButton;
|
||||
|
||||
[ObservableProperty]
|
||||
public static bool _sonarrConnected;
|
||||
|
||||
[ObservableProperty]
|
||||
private static EpisodeDownloadMode _selectedDownloadMode = EpisodeDownloadMode.OnlySubs;
|
||||
|
||||
[ObservableProperty]
|
||||
public Symbol _selectedDownloadIcon = Symbol.ClosedCaption;
|
||||
|
||||
private IStorageProvider? _storageProvider;
|
||||
|
||||
public SeriesPageViewModel(){
|
||||
|
|
@ -63,6 +73,10 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
SelectedSeries.UpdateSeriesFolderPath();
|
||||
|
||||
if (SelectedSeries.SeriesStreamingService == StreamingService.Crunchyroll && SelectedSeries.SeriesType != SeriesType.Artist){
|
||||
ShowFeaturedMusicButton = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -95,6 +109,33 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
SelectedSeries.UpdateSeriesFolderPath();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OpenFeaturedMusicDialog(){
|
||||
if (SelectedSeries.SeriesStreamingService != StreamingService.Crunchyroll || SelectedSeries.SeriesType == SeriesType.Artist){
|
||||
return;
|
||||
}
|
||||
|
||||
var musicList = await CrunchyrollManager.Instance.CrMusic.ParseFeaturedMusicVideoByIdAsync(SelectedSeries.SeriesId ?? string.Empty,
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryLang ?? CrunchyrollManager.Instance.DefaultLocale, true, true);
|
||||
|
||||
if (musicList is{ Data.Count: > 0 }){
|
||||
var dialog = new CustomContentDialog(){
|
||||
Title = "Featured Music",
|
||||
CloseButtonText = "Close",
|
||||
FullSizeDesired = true
|
||||
};
|
||||
|
||||
var viewModel = new ContentDialogFeaturedMusicViewModel(dialog, musicList, CrunchyrollManager.Instance.CrunOptions.HistoryIncludeCrArtists);
|
||||
dialog.Content = new ContentDialogFeaturedMusicView(){
|
||||
DataContext = viewModel
|
||||
};
|
||||
|
||||
var dialogResult = await dialog.ShowAsync();
|
||||
} else{
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"No featured music found", ToastType.Warning, 3));
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task MatchSonarrSeries_Button(){
|
||||
var dialog = new ContentDialog(){
|
||||
|
|
@ -151,7 +192,7 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
|
||||
if (dialogResult == ContentDialogResult.Primary){
|
||||
var sonarrEpisode = viewModel.CurrentSonarrEpisode;
|
||||
|
||||
|
||||
foreach (var selectedSeriesSeason in SelectedSeries.Seasons){
|
||||
foreach (var historyEpisode in selectedSeriesSeason.EpisodesList.Where(historyEpisode => historyEpisode.SonarrEpisodeId == sonarrEpisode.Id.ToString())){
|
||||
historyEpisode.SonarrEpisodeId = string.Empty;
|
||||
|
|
@ -162,7 +203,7 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
historyEpisode.SonarrIsMonitored = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
episode.AssignSonarrEpisodeData(sonarrEpisode);
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
|
@ -190,6 +231,26 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DownloadEpisodeOnlyOptions(HistoryEpisode episode){
|
||||
var downloadMode = SelectedDownloadMode;
|
||||
|
||||
if (downloadMode != EpisodeDownloadMode.Default){
|
||||
await episode.DownloadEpisode(downloadMode);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DownloadSeasonAllOnlyOptions(HistorySeason season){
|
||||
var downloadMode = SelectedDownloadMode;
|
||||
|
||||
if (downloadMode != EpisodeDownloadMode.Default){
|
||||
foreach (var episode in season.EpisodesList){
|
||||
await episode.DownloadEpisode(downloadMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DownloadSeasonMissingSonarr(HistorySeason season){
|
||||
foreach (var episode in season.EpisodesList.Where(episode => !episode.SonarrHasFile)){
|
||||
|
|
@ -263,4 +324,14 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
Console.Error.WriteLine($"An error occurred while opening the folder: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
partial void OnSelectedDownloadModeChanged(EpisodeDownloadMode value){
|
||||
SelectedDownloadIcon = SelectedDownloadMode switch{
|
||||
EpisodeDownloadMode.OnlyVideo => Symbol.Video,
|
||||
EpisodeDownloadMode.OnlyAudio => Symbol.Audio,
|
||||
EpisodeDownloadMode.OnlySubs => Symbol.ClosedCaption,
|
||||
_ => Symbol.ClosedCaption
|
||||
};
|
||||
}
|
||||
}
|
||||
92
CRD/ViewModels/Utils/ContentDialogFeaturedMusicViewModel.cs
Normal file
92
CRD/ViewModels/Utils/ContentDialogFeaturedMusicViewModel.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Structs.Crunchyroll.Music;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.Utils.UI;
|
||||
using DynamicData;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace CRD.ViewModels.Utils;
|
||||
|
||||
public partial class ContentDialogFeaturedMusicViewModel : ViewModelBase{
|
||||
private readonly CustomContentDialog dialog;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<HistoryEpisode> _featuredMusicList = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _musicInHistory;
|
||||
|
||||
private CrunchyMusicVideoList featuredMusic;
|
||||
|
||||
public ContentDialogFeaturedMusicViewModel(CustomContentDialog contentDialog, CrunchyMusicVideoList featuredMusic, bool crunOptionsHistoryIncludeCrArtists){
|
||||
ArgumentNullException.ThrowIfNull(contentDialog);
|
||||
|
||||
this.featuredMusic = featuredMusic;
|
||||
|
||||
dialog = contentDialog;
|
||||
dialog.Closed += DialogOnClosed;
|
||||
dialog.PrimaryButtonClick += SaveButton;
|
||||
|
||||
if (crunOptionsHistoryIncludeCrArtists){
|
||||
var episodeList = featuredMusic.Data
|
||||
.Select(video =>
|
||||
CrunchyrollManager.Instance.HistoryList
|
||||
.FirstOrDefault(h => h.SeriesId == video.GetSeriesId())?
|
||||
.Seasons.FirstOrDefault(s => s.SeasonId == video.GetSeasonId())?
|
||||
.EpisodesList.FirstOrDefault(e => e.EpisodeId == video.GetEpisodeId()))
|
||||
.Where(episode => episode != null)
|
||||
.ToList();
|
||||
|
||||
if (episodeList.Count > 0){
|
||||
FeaturedMusicList.Clear();
|
||||
FeaturedMusicList!.AddRange(episodeList);
|
||||
}
|
||||
} else{
|
||||
List<HistoryEpisode> episodeList =[];
|
||||
|
||||
foreach (var crunchyMusicVideo in featuredMusic.Data){
|
||||
var newHistoryEpisode = new HistoryEpisode{
|
||||
EpisodeTitle = crunchyMusicVideo.GetEpisodeTitle(),
|
||||
EpisodeDescription = crunchyMusicVideo.GetEpisodeDescription(),
|
||||
EpisodeId = crunchyMusicVideo.GetEpisodeId(),
|
||||
Episode = crunchyMusicVideo.GetEpisodeNumber(),
|
||||
EpisodeSeasonNum = crunchyMusicVideo.GetSeasonNum(),
|
||||
SpecialEpisode = crunchyMusicVideo.IsSpecialEpisode(),
|
||||
HistoryEpisodeAvailableDubLang = crunchyMusicVideo.GetEpisodeAvailableDubLang(),
|
||||
HistoryEpisodeAvailableSoftSubs = crunchyMusicVideo.GetEpisodeAvailableSoftSubs(),
|
||||
EpisodeCrPremiumAirDate = crunchyMusicVideo.GetAvailableDate(),
|
||||
EpisodeType = crunchyMusicVideo.GetEpisodeType(),
|
||||
IsEpisodeAvailableOnStreamingService = true,
|
||||
ThumbnailImageUrl = crunchyMusicVideo.GetImageUrl(),
|
||||
};
|
||||
episodeList.Add(newHistoryEpisode);
|
||||
newHistoryEpisode.LoadImage();
|
||||
}
|
||||
|
||||
if (episodeList.Count > 0){
|
||||
FeaturedMusicList.Clear();
|
||||
FeaturedMusicList.AddRange(episodeList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void DownloadEpisode(HistoryEpisode episode){
|
||||
episode.DownloadEpisode();
|
||||
}
|
||||
|
||||
private void SaveButton(ContentDialog sender, ContentDialogButtonClickEventArgs args){
|
||||
dialog.PrimaryButtonClick -= SaveButton;
|
||||
}
|
||||
|
||||
private void DialogOnClosed(ContentDialog sender, ContentDialogClosedEventArgs args){
|
||||
dialog.Closed -= DialogOnClosed;
|
||||
}
|
||||
}
|
||||
|
|
@ -13,12 +13,13 @@
|
|||
Unloaded="OnUnloaded"
|
||||
Loaded="Control_OnLoaded">
|
||||
|
||||
<UserControl.Resources>
|
||||
<UserControl.Resources>
|
||||
<ui:UiIntToVisibilityConverter x:Key="UiIntToVisibilityConverter" />
|
||||
<ui:UiSonarrIdToVisibilityConverter x:Key="UiSonarrIdToVisibilityConverter" />
|
||||
<ui:UiListToStringConverter x:Key="UiListToStringConverter" />
|
||||
<ui:UiListHasElementsConverter x:Key="UiListHasElementsConverter" />
|
||||
<ui:UiSeriesSeasonConverter x:Key="UiSeriesSeasonConverter" />
|
||||
<ui:UiEnumToBoolConverter x:Key="EnumToBoolConverter" />
|
||||
</UserControl.Resources>
|
||||
|
||||
|
||||
|
|
@ -406,6 +407,7 @@
|
|||
<TextBlock Grid.Row="1" FontSize="15" Margin="0 0 0 5" TextWrapping="Wrap"
|
||||
Text="{Binding SeriesDescription}" MinWidth="200">
|
||||
</TextBlock>
|
||||
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" VerticalAlignment="Center"
|
||||
IsVisible="{Binding HistorySeriesAvailableDubLang, Converter={StaticResource UiListHasElementsConverter}}">
|
||||
<TextBlock FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="Available Dubs: "></TextBlock>
|
||||
|
|
@ -419,7 +421,8 @@
|
|||
FontSize="15"
|
||||
Opacity="0.8"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" VerticalAlignment="Center"
|
||||
|
|
@ -435,7 +438,8 @@
|
|||
FontSize="15"
|
||||
Opacity="0.8"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
|
|
@ -516,7 +520,7 @@
|
|||
|
||||
<ToggleButton Margin="0 0 5 10" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding IsInactive}"
|
||||
IsChecked="{Binding IsInactive}"
|
||||
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).ToggleInactive}">
|
||||
<ToolTip.Tip>
|
||||
<StackPanel Orientation="Vertical">
|
||||
|
|
@ -717,27 +721,41 @@
|
|||
<Grid VerticalAlignment="Center">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<ui:EpisodeHighlightTextBlock
|
||||
Series="{Binding $parent[ScrollViewer].((history:HistorySeries)DataContext)}"
|
||||
Season="{Binding $parent[controls:SettingsExpander].((history:HistorySeason)DataContext)}"
|
||||
Episode="{Binding .}"
|
||||
StreamingService="Crunchyroll"
|
||||
MinWidth="50"
|
||||
TextWrapping="NoWrap"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
|
||||
<controls:SymbolIcon Grid.Column="0" IsVisible="{Binding !IsEpisodeAvailableOnStreamingService}"
|
||||
Margin="0 0 5 0 "
|
||||
Symbol="AlertOn"
|
||||
FontSize="18"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Episode unavailable — it might not be available on the streaming service or got moved to a different season" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
|
||||
</controls:SymbolIcon>
|
||||
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
|
||||
<ui:EpisodeHighlightTextBlock
|
||||
Series="{Binding $parent[ScrollViewer].((history:HistorySeries)DataContext)}"
|
||||
Season="{Binding $parent[controls:SettingsExpander].((history:HistorySeason)DataContext)}"
|
||||
Episode="{Binding .}"
|
||||
StreamingService="Crunchyroll"
|
||||
MinWidth="50"
|
||||
TextWrapping="NoWrap"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
|
||||
IsVisible="{Binding HistoryEpisodeAvailableDubLang, Converter={StaticResource UiListHasElementsConverter}}">
|
||||
<TextBlock FontStyle="Italic"
|
||||
FontSize="12"
|
||||
Opacity="0.8" Text="Dubs: ">
|
||||
</TextBlock>
|
||||
|
||||
|
||||
<ui:HighlightingTextBlock
|
||||
Items="{Binding HistoryEpisodeAvailableDubLang}"
|
||||
Series="{Binding $parent[ScrollViewer].((history:HistorySeries)DataContext)}"
|
||||
|
|
@ -747,19 +765,20 @@
|
|||
FontSize="12"
|
||||
Opacity="0.8"
|
||||
FontStyle="Italic" />
|
||||
|
||||
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1"
|
||||
<StackPanel Grid.Column="2"
|
||||
Orientation="Horizontal"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<TextBlock Text="{Binding ReleaseDateFormated}" VerticalAlignment="Center" FontSize="15" Opacity="0.8" Margin="0 0 20 0"></TextBlock>
|
||||
|
||||
<StackPanel VerticalAlignment="Center"
|
||||
Margin="0 0 5 0"
|
||||
IsVisible="{Binding $parent[ScrollViewer].((history:HistorySeries)DataContext).SonarrSeriesId, Converter={StaticResource UiSonarrIdToVisibilityConverter}}">
|
||||
|
||||
|
||||
<controls:ImageIcon
|
||||
IsVisible="{Binding SonarrHasFile}"
|
||||
Source="../Assets/sonarr.png"
|
||||
|
|
@ -806,8 +825,7 @@
|
|||
<Button Margin="0 0 5 0" FontStyle="Italic"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding DownloadEpisode}"
|
||||
CommandParameter="false">
|
||||
Command="{Binding DownloadEpisodeDefault}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Download"
|
||||
FontSize="18" />
|
||||
|
|
@ -817,13 +835,14 @@
|
|||
<Button Margin="0 0 5 0" FontStyle="Italic" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding HistoryEpisodeAvailableSoftSubs, Converter={StaticResource UiListHasElementsConverter}}"
|
||||
Command="{Binding DownloadEpisode}"
|
||||
CommandParameter="true">
|
||||
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).DownloadEpisodeOnlyOptions}"
|
||||
CommandParameter="{Binding .}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="ClosedCaption" FontSize="18" />
|
||||
<controls:SymbolIcon Symbol="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).SelectedDownloadIcon}" FontSize="18" />
|
||||
|
||||
</StackPanel>
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Download Subs" FontSize="15" />
|
||||
<TextBlock Text="Download Only" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
</Button>
|
||||
|
||||
|
|
@ -933,6 +952,69 @@
|
|||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonOnlyDownload"
|
||||
Margin="10 0 0 0" FontStyle="Italic" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).SelectedDownloadIcon}" FontSize="18" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
IsOpen="{Binding IsChecked, ElementName=DropdownButtonOnlyDownload, Mode=TwoWay}"
|
||||
Placement="BottomEdgeAlignedRight"
|
||||
PlacementTarget="{Binding ElementName=DropdownButtonOnlyDownload}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
|
||||
<Button Margin="10 5"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="Download All"
|
||||
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).DownloadSeasonAllOnlyOptions}"
|
||||
CommandParameter="{Binding }">
|
||||
</Button>
|
||||
|
||||
|
||||
<Rectangle Height="1" Fill="Gray" Margin="0,8,0,8" />
|
||||
|
||||
<Grid Margin="8 0 5 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="Video" Grid.Column="0" VerticalAlignment="Center" />
|
||||
<ToggleSwitch OffContent="" OnContent="" Grid.Column="1" IsChecked="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).SelectedDownloadMode, Converter={StaticResource EnumToBoolConverter}, ConverterParameter=OnlyVideo}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="8 0 5 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="Audio" Grid.Column="0" VerticalAlignment="Center" />
|
||||
<ToggleSwitch OffContent="" OnContent="" Grid.Column="1" IsChecked="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).SelectedDownloadMode, Converter={StaticResource EnumToBoolConverter}, ConverterParameter=OnlyAudio}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="8 0 5 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="Sub" Grid.Column="0" VerticalAlignment="Center" />
|
||||
<ToggleSwitch OffContent="" OnContent="" Grid.Column="1" IsChecked="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).SelectedDownloadMode, Converter={StaticResource EnumToBoolConverter}, ConverterParameter=OnlySubs}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<ToggleButton x:Name="SeasonOverride" Margin="10 0 0 0" FontStyle="Italic"
|
||||
|
|
|
|||
|
|
@ -14,8 +14,10 @@
|
|||
<UserControl.Resources>
|
||||
<ui:UiListToStringConverter x:Key="UiListToStringConverter" />
|
||||
<ui:UiListHasElementsConverter x:Key="UiListHasElementsConverter" />
|
||||
<ui:UiEnumToBoolConverter x:Key="EnumToBoolConverter" />
|
||||
</UserControl.Resources>
|
||||
|
||||
|
||||
<Grid>
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
|
|
@ -137,6 +139,19 @@
|
|||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Command="{Binding UpdateData}" Margin="0 0 5 10">Refresh Series</Button>
|
||||
|
||||
<Button Margin="0 0 5 10" FontStyle="Italic"
|
||||
IsVisible="{Binding ShowFeaturedMusicButton}"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenFeaturedMusicDialog}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Featured Music" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Audio" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<ToggleButton IsChecked="{Binding EditMode}" Margin="0 0 5 10">Edit</ToggleButton>
|
||||
|
||||
<Button Margin="0 0 5 10" FontStyle="Italic"
|
||||
|
|
@ -324,7 +339,7 @@
|
|||
</StackPanel>
|
||||
|
||||
<StackPanel IsVisible="{Binding EditMode}">
|
||||
<Button Width="30" Height="30" Margin="0 0 5 0"
|
||||
<Button Width="30" Height="30" Margin="0 0 10 0"
|
||||
BorderThickness="0"
|
||||
IsVisible="{Binding SonarrConnected}"
|
||||
Command="{Binding MatchSonarrSeries_Button}">
|
||||
|
|
@ -336,20 +351,17 @@
|
|||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<StackPanel IsVisible="{Binding EditMode}">
|
||||
<Button Height="30" Margin="0 0 10 0"
|
||||
<Button Width="30" Height="30" Margin="0 0 10 0"
|
||||
BorderThickness="0"
|
||||
IsVisible="{Binding SonarrConnected}"
|
||||
Command="{Binding RefreshSonarrEpisodeMatch}">
|
||||
<Grid>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:ImageIcon Source="../Assets/sonarr.png" Width="25" Height="25" />
|
||||
<TextBlock Margin="5 0 0 0" Text="Rematch Episodes"></TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<controls:ImageIcon Source="../Assets/sonarr.png" Width="25" Height="25" />
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Rematch all Sonarr Episodes" FontSize="15" />
|
||||
<TextBlock Text="Refresh all Sonarr Episode matches" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
</Grid>
|
||||
</Button>
|
||||
|
|
@ -448,13 +460,12 @@
|
|||
|
||||
<TextBlock Text="{Binding ReleaseDateFormated}" VerticalAlignment="Center" FontSize="15" Opacity="0.8" Margin="0 0 20 0"></TextBlock>
|
||||
|
||||
|
||||
|
||||
|
||||
<StackPanel VerticalAlignment="Center" Margin="0 0 5 0" Orientation="Horizontal"
|
||||
IsVisible="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).SonarrAvailable}">
|
||||
|
||||
|
||||
<TextBlock Text="{Binding SonarrSeasonEpisodeText}" VerticalAlignment="Center" FontSize="15" Opacity="0.8" Margin="0 0 20 0"></TextBlock>
|
||||
|
||||
|
||||
|
||||
<StackPanel IsVisible="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).ShowMonitoredBookmark}"
|
||||
HorizontalAlignment="Center"
|
||||
|
|
@ -476,6 +487,7 @@
|
|||
</controls:SymbolIcon>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<Button Width="34" Height="34" Margin="0 0 10 0" Background="Transparent"
|
||||
BorderThickness="0" CornerRadius="50"
|
||||
Command="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).MatchSonarrEpisode_Button}"
|
||||
|
|
@ -488,7 +500,7 @@
|
|||
<controls:ImageIcon IsVisible="{Binding !SonarrHasFile}"
|
||||
Source="../Assets/sonarr_inactive.png" Width="25"
|
||||
Height="25" />
|
||||
|
||||
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
|
|
@ -520,8 +532,7 @@
|
|||
|
||||
<Button Margin="0 0 5 0" FontStyle="Italic" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding DownloadEpisode}"
|
||||
CommandParameter="false">
|
||||
Command="{Binding DownloadEpisodeDefault}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Download" FontSize="18" />
|
||||
</StackPanel>
|
||||
|
|
@ -533,13 +544,15 @@
|
|||
<Button Margin="0 0 5 0" FontStyle="Italic" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding HistoryEpisodeAvailableSoftSubs, Converter={StaticResource UiListHasElementsConverter}}"
|
||||
Command="{Binding DownloadEpisode}"
|
||||
CommandParameter="true">
|
||||
Command="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).DownloadEpisodeOnlyOptions}"
|
||||
CommandParameter="{Binding .}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="ClosedCaption" FontSize="18" />
|
||||
|
||||
<controls:SymbolIcon Symbol="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).SelectedDownloadIcon}" FontSize="18" />
|
||||
|
||||
</StackPanel>
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Download Subs" FontSize="15" />
|
||||
<TextBlock Text="Download Only" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
</Button>
|
||||
|
||||
|
|
@ -630,6 +643,70 @@
|
|||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonOnlyDownload"
|
||||
Margin="10 0 0 0" FontStyle="Italic" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).SelectedDownloadIcon}" FontSize="18" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
IsOpen="{Binding IsChecked, ElementName=DropdownButtonOnlyDownload, Mode=TwoWay}"
|
||||
Placement="BottomEdgeAlignedRight"
|
||||
PlacementTarget="{Binding ElementName=DropdownButtonOnlyDownload}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
|
||||
<Button Margin="10 5"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="Download All"
|
||||
Command="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).DownloadSeasonAllOnlyOptions}"
|
||||
CommandParameter="{Binding }">
|
||||
</Button>
|
||||
|
||||
|
||||
<Rectangle Height="1" Fill="Gray" Margin="0,8,0,8" />
|
||||
|
||||
<Grid Margin="8 0 5 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="Video" Grid.Column="0" VerticalAlignment="Center" />
|
||||
<ToggleSwitch OffContent="" OnContent="" Grid.Column="1" IsChecked="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).SelectedDownloadMode, Converter={StaticResource EnumToBoolConverter}, ConverterParameter=OnlyVideo}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="8 0 5 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="Audio" Grid.Column="0" VerticalAlignment="Center" />
|
||||
<ToggleSwitch OffContent="" OnContent="" Grid.Column="1" IsChecked="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).SelectedDownloadMode, Converter={StaticResource EnumToBoolConverter}, ConverterParameter=OnlyAudio}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="8 0 5 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="Sub" Grid.Column="0" VerticalAlignment="Center" />
|
||||
<ToggleSwitch OffContent="" OnContent="" Grid.Column="1" IsChecked="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).SelectedDownloadMode, Converter={StaticResource EnumToBoolConverter}, ConverterParameter=OnlySubs}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<ToggleButton x:Name="SeasonOverride" Margin="10 0 0 0" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
|
|
|
|||
99
CRD/Views/Utils/ContentDialogFeaturedMusicView.axaml
Normal file
99
CRD/Views/Utils/ContentDialogFeaturedMusicView.axaml
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<ui:CustomContentDialog xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="clr-namespace:CRD.ViewModels.Utils"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:ui="clr-namespace:CRD.Utils.UI"
|
||||
x:DataType="vm:ContentDialogFeaturedMusicViewModel"
|
||||
x:Class="CRD.Views.Utils.ContentDialogFeaturedMusicView">
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<ItemsControl ItemsSource="{Binding FeaturedMusicList}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Margin="10">
|
||||
<Grid>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" x:Name="RootGrid">
|
||||
<Image Width="320" Height="180" Stretch="Fill" HorizontalAlignment="Center" Source="/Assets/coming_soon_ep.jpg" />
|
||||
<Image Width="320" Height="180" Source="{Binding ThumbnailImage}" Stretch="UniformToFill" />
|
||||
|
||||
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Top" IsVisible="{Binding $parent[UserControl].((vm:ContentDialogFeaturedMusicViewModel)DataContext).MusicInHistory}">
|
||||
|
||||
<Button Width="34" Height="34" Margin="0 0 10 0" Background="Transparent"
|
||||
BorderThickness="0" CornerRadius="50"
|
||||
Padding="5"
|
||||
IsVisible="{Binding !WasDownloaded}">
|
||||
<Grid>
|
||||
<Ellipse Width="25" Height="25" Fill="Gray" />
|
||||
<controls:SymbolIcon Symbol="Checkmark" FontSize="18" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button Width="34" Height="34" Margin="0 0 10 0" Background="Transparent"
|
||||
BorderThickness="0" CornerRadius="50"
|
||||
Padding="5"
|
||||
IsVisible="{Binding WasDownloaded}">
|
||||
<Grid>
|
||||
<Ellipse Width="25" Height="25" Fill="#21a556" />
|
||||
<controls:SymbolIcon Symbol="Checkmark" FontSize="18" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<Border Grid.Row="0" Background="#80000000"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
IsVisible="{Binding #RootGrid.IsPointerOver}">
|
||||
<Button FontStyle="Italic"
|
||||
Background="Transparent"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{Binding $parent[UserControl].((vm:ContentDialogFeaturedMusicViewModel)DataContext).DownloadEpisode}"
|
||||
CommandParameter="{Binding .}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Download" FontSize="24" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
|
||||
<!-- 320 180 -->
|
||||
|
||||
<TextBlock Grid.Row="1" HorizontalAlignment="Center" TextAlignment="Center"
|
||||
Text="{Binding EpisodeTitle}"
|
||||
TextWrapping="Wrap"
|
||||
Width="310"
|
||||
FontSize="14"
|
||||
Height="35"
|
||||
Margin="4,0,4,0">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="{Binding EpisodeTitle}" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
</TextBlock>
|
||||
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
|
||||
</ui:CustomContentDialog>
|
||||
12
CRD/Views/Utils/ContentDialogFeaturedMusicView.axaml.cs
Normal file
12
CRD/Views/Utils/ContentDialogFeaturedMusicView.axaml.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using CRD.Utils.UI;
|
||||
|
||||
namespace CRD.Views.Utils;
|
||||
|
||||
public partial class ContentDialogFeaturedMusicView : UserControl{
|
||||
public ContentDialogFeaturedMusicView(){
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue