Add - Added Dub & Sub override to history series and seasons
Add - Timing Sync - to sync dubs to the video Chg - History Lang is now default language for episode title, description... Chg - Renamed "Fetch Series" to "Refresh Series" to prevent confusion Fix - Fixed Crash with search when episodes had X.X numbering
This commit is contained in:
parent
53158b261d
commit
90ee2221cb
|
@ -1,51 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<ApplicationIcon>Assets\app_icon.ico</ApplicationIcon>
|
||||
<Version>1.5.2.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.1.0-beta2" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.1.0-beta2" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.0-beta2" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.1.0-beta2" />
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.3.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
<PackageReference Include="ExtendedXmlSerializer" Version="3.7.18" />
|
||||
<PackageReference Include="FluentAvaloniaUI" Version="2.1.0-preview5" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.60" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="protobuf-net" Version="3.2.30" />
|
||||
<PackageReference Include="YamlDotNet" Version="15.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Remove="Styling\ControlsGalleryStyles.axaml" />
|
||||
<UpToDateCheckInput Remove="Styling\ControlThemes.axaml" />
|
||||
<UpToDateCheckInput Remove="Views\Utils\ErrorWindow.axaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Views\Utils\ContentDialogUpdateView.axaml.cs">
|
||||
<DependentUpon>ContentDialogInputLoginView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Views\Utils\ContentDialogInputLoginView.axaml.cs">
|
||||
<DependentUpon>ContentDialogInputLoginView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
|
@ -68,6 +68,12 @@ public class CrEpisode(){
|
|||
|
||||
if (crunInstance.CrunOptions.History && updateHistory){
|
||||
await crunInstance.CrHistory.UpdateWithEpisode(dlEpisode);
|
||||
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == dlEpisode.SeriesId);
|
||||
if (historySeries != null){
|
||||
Crunchyroll.Instance.CrHistory.MatchHistorySeriesWithSonarr(false);
|
||||
await Crunchyroll.Instance.CrHistory.MatchHistoryEpisodesWithSonarr(false, historySeries);
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
}
|
||||
|
||||
var seasonIdentifier = !string.IsNullOrEmpty(dlEpisode.Identifier) ? dlEpisode.Identifier.Split('|')[1] : $"S{dlEpisode.SeasonNumber}";
|
||||
|
@ -102,7 +108,7 @@ public class CrEpisode(){
|
|||
int epIndex = 1;
|
||||
|
||||
|
||||
var isSpecial = !Regex.IsMatch(episode.EpisodeAndLanguages.Items[0].Episode ?? string.Empty, @"^\d+$"); // Checking if the episode is not a number (i.e., special).
|
||||
var isSpecial = !Regex.IsMatch(episode.EpisodeAndLanguages.Items[0].Episode ?? string.Empty, @"^\d+(\.\d+)?$"); // Checking if the episode is not a number (i.e., special).
|
||||
string newKey;
|
||||
if (isSpecial && !string.IsNullOrEmpty(episode.EpisodeAndLanguages.Items[0].Episode)){
|
||||
newKey = episode.EpisodeAndLanguages.Items[0].Episode ?? "SP" + (specialIndex + " " + episode.EpisodeAndLanguages.Items[0].Id);
|
||||
|
@ -239,7 +245,7 @@ public class CrEpisode(){
|
|||
return retMeta;
|
||||
}
|
||||
|
||||
public async Task<CrBrowseEpisodeBase?> GetNewEpisodes(string? crLocale, int requestAmount){
|
||||
public async Task<CrBrowseEpisodeBase?> GetNewEpisodes(string? crLocale, int requestAmount , bool forcedLang = false){
|
||||
CrBrowseEpisodeBase? complete = new CrBrowseEpisodeBase();
|
||||
complete.Data =[];
|
||||
|
||||
|
@ -250,6 +256,9 @@ public class CrEpisode(){
|
|||
|
||||
if (!string.IsNullOrEmpty(crLocale)){
|
||||
query["locale"] = crLocale;
|
||||
if (forcedLang){
|
||||
query["force_locale"] = crLocale;
|
||||
}
|
||||
}
|
||||
|
||||
query["start"] = i + "";
|
||||
|
|
|
@ -56,6 +56,13 @@ public class CrSeries(){
|
|||
continue;
|
||||
}
|
||||
|
||||
if (crunInstance.CrunOptions.History){
|
||||
var dubLangList = crunInstance.CrHistory.GetDubList(item.SeriesId, item.SeasonId);
|
||||
if (dubLangList.Count > 0){
|
||||
dubLang = dubLangList;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dubLang.Contains(episode.Langs[index].CrLocale))
|
||||
continue;
|
||||
|
||||
|
@ -157,7 +164,7 @@ public class CrSeries(){
|
|||
var seasonData = await GetSeasonDataById(s.Id, "");
|
||||
if (seasonData.Data != null){
|
||||
if (crunInstance.CrunOptions.History){
|
||||
crunInstance.CrHistory.UpdateWithSeasonData(seasonData);
|
||||
crunInstance.CrHistory.UpdateWithSeasonData(seasonData,false);
|
||||
}
|
||||
|
||||
foreach (var episode in seasonData.Data){
|
||||
|
@ -204,6 +211,15 @@ public class CrSeries(){
|
|||
}
|
||||
}
|
||||
|
||||
if (crunInstance.CrunOptions.History){
|
||||
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == id);
|
||||
if (historySeries != null){
|
||||
crunInstance.CrHistory.MatchHistorySeriesWithSonarr(false);
|
||||
await crunInstance.CrHistory.MatchHistoryEpisodesWithSonarr(false, historySeries);
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
}
|
||||
|
||||
int specialIndex = 1;
|
||||
int epIndex = 1;
|
||||
|
||||
|
@ -212,7 +228,7 @@ public class CrSeries(){
|
|||
foreach (var key in keys){
|
||||
EpisodeAndLanguage item = episodes[key];
|
||||
var episode = item.Items[0].Episode;
|
||||
var isSpecial = episode != null && !Regex.IsMatch(episode, @"^\d+$"); // Checking if the episode is not a number (i.e., special).
|
||||
var isSpecial = episode != null && !Regex.IsMatch(episode, @"^\d+(\.\d+)?$"); // Checking if the episode is not a number (i.e., special).
|
||||
// var newKey = $"{(isSpecial ? 'S' : 'E')}{(isSpecial ? specialIndex : epIndex).ToString()}";
|
||||
|
||||
string newKey;
|
||||
|
@ -431,13 +447,13 @@ public class CrSeries(){
|
|||
}
|
||||
|
||||
|
||||
public async Task<CrSearchSeriesBase?> Search(string searchString,string? crLocale){
|
||||
|
||||
public async Task<CrSearchSeriesBase?> Search(string searchString, string? crLocale){
|
||||
NameValueCollection query = HttpUtility.ParseQueryString(new UriBuilder().Query);
|
||||
|
||||
if (!string.IsNullOrEmpty(crLocale)){
|
||||
query["locale"] = crLocale;
|
||||
}
|
||||
|
||||
query["q"] = searchString;
|
||||
query["n"] = "6";
|
||||
query["type"] = "top_results";
|
||||
|
@ -452,7 +468,7 @@ public class CrSeries(){
|
|||
}
|
||||
|
||||
CrSearchSeriesBase? series = Helpers.Deserialize<CrSearchSeriesBase>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings);
|
||||
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
|
|
|
@ -81,6 +81,7 @@ public class Crunchyroll{
|
|||
|
||||
#endregion
|
||||
|
||||
|
||||
public string DefaultLocale = "en-US";
|
||||
|
||||
public JsonSerializerSettings? SettingsJsonSerializerSettings = new(){
|
||||
|
@ -120,24 +121,7 @@ public class Crunchyroll{
|
|||
Queue.CollectionChanged += UpdateItemListOnRemove;
|
||||
}
|
||||
|
||||
public async Task Init(){
|
||||
_widevine = Widevine.Instance;
|
||||
|
||||
CrAuth = new CrAuth();
|
||||
CrEpisode = new CrEpisode();
|
||||
CrSeries = new CrSeries();
|
||||
CrHistory = new History();
|
||||
|
||||
Profile = new CrProfile{
|
||||
Username = "???",
|
||||
Avatar = "003-cr-hime-excited.png",
|
||||
PreferredContentAudioLanguage = "ja-JP",
|
||||
PreferredContentSubtitleLanguage = "de-DE",
|
||||
HasPremium = false,
|
||||
};
|
||||
|
||||
Console.WriteLine($"Can Decrypt: {_widevine.canDecrypt}");
|
||||
|
||||
public void InitOptions(){
|
||||
CrunOptions.AutoDownload = false;
|
||||
CrunOptions.RemoveFinishedDownload = false;
|
||||
CrunOptions.Chapters = true;
|
||||
|
@ -164,12 +148,32 @@ public class Crunchyroll{
|
|||
CrunOptions.DlVideoOnce = true;
|
||||
CrunOptions.StreamEndpoint = "web/firefox";
|
||||
CrunOptions.SubsAddScaledBorder = ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
|
||||
CrunOptions.HistoryLang = "";
|
||||
|
||||
CrunOptions.HistoryLang = DefaultLocale;
|
||||
|
||||
CrunOptions.History = true;
|
||||
|
||||
CfgManager.UpdateSettingsFromFile();
|
||||
|
||||
_widevine = Widevine.Instance;
|
||||
|
||||
CrAuth = new CrAuth();
|
||||
CrEpisode = new CrEpisode();
|
||||
CrSeries = new CrSeries();
|
||||
CrHistory = new History();
|
||||
|
||||
Profile = new CrProfile{
|
||||
Username = "???",
|
||||
Avatar = "003-cr-hime-excited.png",
|
||||
PreferredContentAudioLanguage = "ja-JP",
|
||||
PreferredContentSubtitleLanguage = "de-DE",
|
||||
HasPremium = false,
|
||||
};
|
||||
|
||||
Console.WriteLine($"Can Decrypt: {_widevine.canDecrypt}");
|
||||
}
|
||||
|
||||
public async Task Init(){
|
||||
|
||||
|
||||
if (CrunOptions.LogMode){
|
||||
CfgManager.EnableLogMode();
|
||||
|
@ -186,10 +190,22 @@ public class Crunchyroll{
|
|||
|
||||
if (CrunOptions.History){
|
||||
if (File.Exists(CfgManager.PathCrHistory)){
|
||||
HistoryList = JsonConvert.DeserializeObject<ObservableCollection<HistorySeries>>(File.ReadAllText(CfgManager.PathCrHistory)) ??[];
|
||||
}
|
||||
var decompressedJson = CfgManager.DecompressJsonFile(CfgManager.PathCrHistory);
|
||||
if (!string.IsNullOrEmpty(decompressedJson)){
|
||||
HistoryList = JsonConvert.DeserializeObject<ObservableCollection<HistorySeries>>(decompressedJson) ?? new ObservableCollection<HistorySeries>();
|
||||
|
||||
RefreshSonarr();
|
||||
foreach (var historySeries in HistoryList){
|
||||
historySeries.Init();
|
||||
foreach (var historySeriesSeason in historySeries.Seasons){
|
||||
historySeriesSeason.Init();
|
||||
}
|
||||
}
|
||||
} else{
|
||||
HistoryList =[];
|
||||
}
|
||||
|
||||
RefreshSonarr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -340,6 +356,18 @@ public class Crunchyroll{
|
|||
}
|
||||
|
||||
var sList = await CrEpisode.EpisodeData((CrunchyEpisode)episodeL, updateHistory);
|
||||
|
||||
(HistoryEpisode? historyEpisode, List<string> dublist, string downloadDirPath) historyEpisode = (null, [], "");
|
||||
|
||||
if (CrunOptions.History){
|
||||
var episode = sList.EpisodeAndLanguages.Items.First();
|
||||
historyEpisode = CrHistory.GetHistoryEpisodeWithDubListAndDownloadDir(episode.SeriesId, episode.SeasonId, episode.Id);
|
||||
if (historyEpisode.dublist.Count > 0){
|
||||
dubLang = historyEpisode.dublist;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var selected = CrEpisode.EpisodeMeta(sList, dubLang);
|
||||
|
||||
if (CrunOptions.IncludeVideoDescription){
|
||||
|
@ -351,7 +379,7 @@ public class Crunchyroll{
|
|||
|
||||
if (selected.Data is{ Count: > 0 }){
|
||||
if (CrunOptions.History){
|
||||
var historyEpisode = CrHistory.GetHistoryEpisodeWithDownloadDir(selected.ShowId, selected.SeasonId, selected.Data.First().MediaId);
|
||||
// var historyEpisode = CrHistory.GetHistoryEpisodeWithDownloadDir(selected.ShowId, selected.SeasonId, selected.Data.First().MediaId);
|
||||
if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
|
||||
if (historyEpisode.historyEpisode != null){
|
||||
if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrEpisodeNumber)){
|
||||
|
@ -369,6 +397,7 @@ public class Crunchyroll{
|
|||
}
|
||||
}
|
||||
|
||||
selected.DownloadSubs = CrunOptions.DlSubs;
|
||||
Queue.Add(selected);
|
||||
|
||||
|
||||
|
@ -419,6 +448,7 @@ public class Crunchyroll{
|
|||
}
|
||||
}
|
||||
|
||||
crunchyEpMeta.DownloadSubs = CrunOptions.DlSubs;
|
||||
Queue.Add(crunchyEpMeta);
|
||||
} else{
|
||||
failed = true;
|
||||
|
@ -569,7 +599,8 @@ public class Crunchyroll{
|
|||
SkipSubMux = options.SkipSubMux,
|
||||
OnlyAudio = data.Where(a => a.Type == DownloadMediaType.Audio).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).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, Signs = a.Signs }).ToList(),
|
||||
Subtitles = data.Where(a => a.Type == DownloadMediaType.Subtitle).Select(a => new SubtitleInput
|
||||
{ File = a.Path ?? string.Empty, Language = a.Language, ClosedCaption = a.Cc, Signs = a.Signs, RelatedVideoDownloadMedia = a.RelatedVideoDownloadMedia }).ToList(),
|
||||
KeepAllVideos = options.KeepAllVideos,
|
||||
Fonts = FontsManager.Instance.MakeFontsList(CfgManager.PathFONTS_DIR, subsList), // Assuming MakeFontsList is properly defined
|
||||
Chapters = data.Where(a => a.Type == DownloadMediaType.Chapters).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList(),
|
||||
|
@ -584,7 +615,7 @@ public class Crunchyroll{
|
|||
},
|
||||
CcTag = options.CcTag,
|
||||
mp3 = muxToMp3,
|
||||
Description = data.Where(a => a.Type == DownloadMediaType.Description).Select(a => new MergerInput{ Path = a.Path ?? string.Empty }).ToList(),
|
||||
Description = muxDesc ? data.Where(a => a.Type == DownloadMediaType.Description).Select(a => new MergerInput{ Path = a.Path ?? string.Empty }).ToList() :[],
|
||||
});
|
||||
|
||||
if (!File.Exists(CfgManager.PathFFMPEG)){
|
||||
|
@ -597,9 +628,31 @@ public class Crunchyroll{
|
|||
|
||||
bool isMuxed;
|
||||
|
||||
// if (options.SyncTiming){
|
||||
// await Merger.CreateDelays();
|
||||
// }
|
||||
if (options.SyncTiming && CrunOptions.DlVideoOnce){
|
||||
var basePath = merger.options.OnlyVid.First().Path;
|
||||
var syncVideosList = data.Where(a => a.Type == DownloadMediaType.SyncVideo).ToList();
|
||||
|
||||
if (!string.IsNullOrEmpty(basePath) && syncVideosList.Count > 0){
|
||||
foreach (var syncVideo in syncVideosList){
|
||||
if (!string.IsNullOrEmpty(syncVideo.Path)){
|
||||
var delay = await merger.ProcessVideo(basePath, syncVideo.Path);
|
||||
var audio = merger.options.OnlyAudio.FirstOrDefault(audio => audio.Language.CrLocale == syncVideo.Lang.CrLocale);
|
||||
if (audio != null){
|
||||
audio.Delay = (int)delay * 1000;
|
||||
}
|
||||
|
||||
var subtitles = merger.options.Subtitles.Where(a => a.RelatedVideoDownloadMedia == syncVideo).ToList();
|
||||
if (subtitles.Count > 0){
|
||||
foreach (var subMergerInput in subtitles){
|
||||
subMergerInput.Delay = (int)delay * 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
syncVideosList.ForEach(syncVideo => Helpers.DeleteFile(syncVideo.Path));
|
||||
}
|
||||
|
||||
if (!options.Mp4 && !muxToMp3){
|
||||
await merger.Merge("mkvmerge", CfgManager.PathMKVMERGE);
|
||||
|
@ -726,8 +779,8 @@ public class Crunchyroll{
|
|||
if (epMeta.Versions != null){
|
||||
if (epMeta.Lang != null){
|
||||
currentVersion = epMeta.Versions.Find(a => a.AudioLocale == epMeta.Lang?.CrLocale);
|
||||
} else if (options.DubLang.Count == 1){
|
||||
LanguageItem lang = Array.Find(Languages.languages, a => a.CrLocale == options.DubLang[0]);
|
||||
} else if (data.SelectedDubs is{ Count: 1 }){
|
||||
LanguageItem lang = Array.Find(Languages.languages, a => a.CrLocale == data.SelectedDubs[0]);
|
||||
currentVersion = epMeta.Versions.Find(a => a.AudioLocale == lang.CrLocale);
|
||||
} else if (epMeta.Versions.Count == 1){
|
||||
currentVersion = epMeta.Versions[0];
|
||||
|
@ -804,7 +857,8 @@ public class Crunchyroll{
|
|||
var streams = new List<StreamDetailsPop>();
|
||||
|
||||
variables.Add(new Variable("title", data.EpisodeTitle ?? string.Empty, true));
|
||||
variables.Add(new Variable("episode", (int.TryParse(data.EpisodeNumber, out int episodeNum) ? (object)episodeNum : data.AbsolutEpisodeNumberE) ?? string.Empty, false));
|
||||
variables.Add(new Variable("episode",
|
||||
(double.TryParse(data.EpisodeNumber, NumberStyles.Any, CultureInfo.InvariantCulture, out double episodeNum) ? (object)Math.Round(episodeNum, 1) : data.AbsolutEpisodeNumberE) ?? string.Empty, false));
|
||||
variables.Add(new Variable("seriesTitle", data.SeriesTitle ?? string.Empty, true));
|
||||
variables.Add(new Variable("showTitle", data.SeasonTitle ?? string.Empty, true));
|
||||
variables.Add(new Variable("season", data.Season != null ? Math.Round(double.Parse(data.Season, CultureInfo.InvariantCulture), 1) : 0, false));
|
||||
|
@ -915,6 +969,7 @@ public class Crunchyroll{
|
|||
}
|
||||
|
||||
string tsFile = "";
|
||||
var videoDownloadMedia = new DownloadedMedia();
|
||||
|
||||
if (!dlFailed && curStream != null && options is not{ Novids: true, Noaudio: true }){
|
||||
var streamPlaylistsReq = HttpClientReq.CreateRequestMessage(curStream.Url ?? string.Empty, HttpMethod.Get, true, true, null);
|
||||
|
@ -985,7 +1040,9 @@ public class Crunchyroll{
|
|||
audios.Sort((a, b) => a.bandwidth.CompareTo(b.bandwidth));
|
||||
|
||||
int chosenVideoQuality;
|
||||
if (options.QualityVideo == "best"){
|
||||
if (options.DlVideoOnce && dlVideoOnce && options.SyncTiming){
|
||||
chosenVideoQuality = 1;
|
||||
} else if (options.QualityVideo == "best"){
|
||||
chosenVideoQuality = videos.Count;
|
||||
} else if (options.QualityVideo == "worst"){
|
||||
chosenVideoQuality = 1;
|
||||
|
@ -1075,9 +1132,10 @@ public class Crunchyroll{
|
|||
.ToArray());
|
||||
string tempTsFile = Path.IsPathRooted(tempFile) ? tempFile : Path.Combine(fileDir, tempFile);
|
||||
|
||||
bool audioDownloaded = false, videoDownloaded = false;
|
||||
bool audioDownloaded = false, videoDownloaded = false, syncTimingDownload = false;
|
||||
|
||||
if (options.DlVideoOnce && dlVideoOnce){
|
||||
|
||||
if (options.DlVideoOnce && dlVideoOnce && !options.SyncTiming){
|
||||
Console.WriteLine("Already downloaded video, skipping video download...");
|
||||
} else if (options.Novids){
|
||||
Console.WriteLine("Skipping video download...");
|
||||
|
@ -1091,6 +1149,10 @@ public class Crunchyroll{
|
|||
dlFailed = true;
|
||||
}
|
||||
|
||||
if (options.DlVideoOnce && dlVideoOnce && options.SyncTiming){
|
||||
syncTimingDownload = true;
|
||||
}
|
||||
|
||||
dlVideoOnce = true;
|
||||
videoDownloaded = true;
|
||||
}
|
||||
|
@ -1256,12 +1318,13 @@ public class Crunchyroll{
|
|||
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||
}
|
||||
|
||||
files.Add(new DownloadedMedia{
|
||||
Type = DownloadMediaType.Video,
|
||||
videoDownloadMedia = new DownloadedMedia{
|
||||
Type = syncTimingDownload ? DownloadMediaType.SyncVideo : DownloadMediaType.Video,
|
||||
Path = $"{tsFile}.video.m4s",
|
||||
Lang = lang.Value,
|
||||
IsPrimary = isPrimary
|
||||
});
|
||||
};
|
||||
files.Add(videoDownloadMedia);
|
||||
} else{
|
||||
Console.WriteLine("No Video downloaded");
|
||||
}
|
||||
|
@ -1335,12 +1398,13 @@ public class Crunchyroll{
|
|||
}
|
||||
} else{
|
||||
if (videoDownloaded){
|
||||
files.Add(new DownloadedMedia{
|
||||
Type = DownloadMediaType.Video,
|
||||
videoDownloadMedia = new DownloadedMedia{
|
||||
Type = syncTimingDownload ? DownloadMediaType.SyncVideo : DownloadMediaType.Video,
|
||||
Path = $"{tsFile}.video.m4s",
|
||||
Lang = lang.Value,
|
||||
IsPrimary = isPrimary
|
||||
});
|
||||
};
|
||||
files.Add(videoDownloadMedia);
|
||||
}
|
||||
|
||||
if (audioDownloaded){
|
||||
|
@ -1405,8 +1469,8 @@ public class Crunchyroll{
|
|||
}
|
||||
}
|
||||
|
||||
if (options.DlSubs.IndexOf("all") > -1){
|
||||
options.DlSubs = new List<string>{ "all" };
|
||||
if (data.DownloadSubs.IndexOf("all") > -1){
|
||||
data.DownloadSubs = new List<string>{ "all" };
|
||||
}
|
||||
|
||||
if (options.Hslang != "none"){
|
||||
|
@ -1414,8 +1478,8 @@ public class Crunchyroll{
|
|||
options.SkipSubs = true;
|
||||
}
|
||||
|
||||
if (!options.SkipSubs && options.DlSubs.IndexOf("none") == -1){
|
||||
await DownloadSubtitles(options, pbData, audDub, fileName, files, fileDir);
|
||||
if (!options.SkipSubs && data.DownloadSubs.IndexOf("none") == -1){
|
||||
await DownloadSubtitles(options, pbData, audDub, fileName, files, fileDir, data, (options.DlVideoOnce && dlVideoOnce && options.SyncTiming), videoDownloadMedia);
|
||||
} else{
|
||||
Console.WriteLine("Subtitles downloading skipped!");
|
||||
}
|
||||
|
@ -1472,7 +1536,8 @@ public class Crunchyroll{
|
|||
};
|
||||
}
|
||||
|
||||
private static async Task DownloadSubtitles(CrDownloadOptions options, PlaybackData pbData, string audDub, string fileName, List<DownloadedMedia> files, string fileDir){
|
||||
private static async Task DownloadSubtitles(CrDownloadOptions options, PlaybackData pbData, string audDub, string fileName, List<DownloadedMedia> files, string fileDir, CrunchyEpMeta data, bool needsDelay,
|
||||
DownloadedMedia videoDownloadMedia){
|
||||
if (pbData.Meta != null && pbData.Meta.Subtitles != null && pbData.Meta.Subtitles.Count > 0){
|
||||
List<SubtitleInfo> subsData = pbData.Meta.Subtitles.Values.ToList();
|
||||
List<SubtitleInfo> capsData = pbData.Meta.ClosedCaptions?.Values.ToList() ?? new List<SubtitleInfo>();
|
||||
|
@ -1510,7 +1575,7 @@ public class Crunchyroll{
|
|||
var isSigns = langItem.Code == audDub && !subsItem.isCC;
|
||||
var isCc = subsItem.isCC;
|
||||
|
||||
sxData.File = Languages.SubsFile(fileName, index + "", langItem, isCc, options.CcTag, isSigns, subsItem.format, !(options.DlSubs.Count == 1 && !options.DlSubs.Contains("all")));
|
||||
sxData.File = Languages.SubsFile(fileName, index + "", langItem, isCc, options.CcTag, isSigns, subsItem.format, !(data.DownloadSubs.Count == 1 && !data.DownloadSubs.Contains("all")));
|
||||
sxData.Path = Path.Combine(fileDir, sxData.File);
|
||||
|
||||
Helpers.EnsureDirectoriesExist(sxData.Path);
|
||||
|
@ -1523,7 +1588,7 @@ public class Crunchyroll{
|
|||
continue;
|
||||
}
|
||||
|
||||
if (options.DlSubs.Contains("all") || options.DlSubs.Contains(langItem.CrLocale)){
|
||||
if (data.DownloadSubs.Contains("all") || data.DownloadSubs.Contains(langItem.CrLocale)){
|
||||
var subsAssReq = HttpClientReq.CreateRequestMessage(subsItem.url ?? string.Empty, HttpMethod.Get, false, false, null);
|
||||
|
||||
var subsAssReqResponse = await HttpClientReq.Instance.SendHttpRequest(subsAssReq);
|
||||
|
@ -1567,7 +1632,8 @@ public class Crunchyroll{
|
|||
Title = sxData.Title,
|
||||
Fonts = sxData.Fonts,
|
||||
Language = sxData.Language,
|
||||
Lang = sxData.Language
|
||||
Lang = sxData.Language,
|
||||
RelatedVideoDownloadMedia = videoDownloadMedia
|
||||
});
|
||||
} else{
|
||||
Console.WriteLine($"Failed to download subtitle: ${sxData.File}");
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media.Imaging;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Sonarr.Models;
|
||||
|
@ -17,7 +11,6 @@ using CRD.Utils.Structs;
|
|||
using CRD.Utils.Structs.History;
|
||||
using CRD.Views;
|
||||
using DynamicData;
|
||||
using Newtonsoft.Json;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace CRD.Downloader;
|
||||
|
@ -60,11 +53,17 @@ public class History(){
|
|||
await UpdateWithSeasonData(seasonData);
|
||||
}
|
||||
}
|
||||
|
||||
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
||||
|
||||
if (historySeries != null){
|
||||
MatchHistorySeriesWithSonarr(false);
|
||||
await MatchHistoryEpisodesWithSonarr(false, historySeries);
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHistoryFile(){
|
||||
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, crunInstance.HistoryList);
|
||||
}
|
||||
|
||||
|
||||
public void SetAsDownloaded(string? seriesId, string? seasonId, string episodeId){
|
||||
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
||||
|
@ -129,6 +128,59 @@ public class History(){
|
|||
|
||||
return (null, downloadDirPath);
|
||||
}
|
||||
|
||||
public (HistoryEpisode? historyEpisode, List<string> dublist, string downloadDirPath) GetHistoryEpisodeWithDubListAndDownloadDir(string? seriesId, string? seasonId, string episodeId){
|
||||
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
||||
|
||||
var downloadDirPath = "";
|
||||
List<string> dublist = [];
|
||||
|
||||
if (historySeries != null){
|
||||
var historySeason = historySeries.Seasons.FirstOrDefault(s => s.SeasonId == seasonId);
|
||||
if (historySeries.HistorySeriesDubLangOverride.Count > 0){
|
||||
dublist = historySeries.HistorySeriesDubLangOverride;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(historySeries.SeriesDownloadPath)){
|
||||
downloadDirPath = historySeries.SeriesDownloadPath;
|
||||
}
|
||||
|
||||
if (historySeason != null){
|
||||
var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == episodeId);
|
||||
if (historySeason.HistorySeasonDubLangOverride.Count > 0){
|
||||
dublist = historySeason.HistorySeasonDubLangOverride;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(historySeason.SeasonDownloadPath)){
|
||||
downloadDirPath = historySeason.SeasonDownloadPath;
|
||||
}
|
||||
|
||||
if (historyEpisode != null){
|
||||
return (historyEpisode, dublist,downloadDirPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (null, dublist,downloadDirPath);
|
||||
}
|
||||
|
||||
public List<string> GetDubList(string? seriesId, string? seasonId){
|
||||
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
||||
|
||||
List<string> dublist = [];
|
||||
|
||||
if (historySeries != null){
|
||||
var historySeason = historySeries.Seasons.FirstOrDefault(s => s.SeasonId == seasonId);
|
||||
if (historySeries.HistorySeriesDubLangOverride.Count > 0){
|
||||
dublist = historySeries.HistorySeriesDubLangOverride;
|
||||
}
|
||||
|
||||
if (historySeason is{ HistorySeasonDubLangOverride.Count: > 0 }){
|
||||
dublist = historySeason.HistorySeasonDubLangOverride;
|
||||
}
|
||||
}
|
||||
|
||||
return dublist;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task UpdateWithEpisode(CrunchyEpisode episodeParam){
|
||||
|
@ -160,6 +212,7 @@ public class History(){
|
|||
if (historySeason != null){
|
||||
historySeason.SeasonTitle = episode.SeasonTitle;
|
||||
historySeason.SeasonNum = Helpers.ExtractNumberAfterS(episode.Identifier) ?? episode.SeasonNumber + "";
|
||||
historySeason.SpecialSeason = CheckStringForSpecial(episode.Identifier);
|
||||
if (historySeason.EpisodesList.All(e => e.EpisodeId != episode.Id)){
|
||||
var newHistoryEpisode = new HistoryEpisode{
|
||||
EpisodeTitle = episode.Identifier.Contains("|M|") ? episode.SeasonTitle : episode.Title,
|
||||
|
@ -178,6 +231,7 @@ public class History(){
|
|||
var newSeason = NewHistorySeason(episode);
|
||||
|
||||
historySeries.Seasons.Add(newSeason);
|
||||
newSeason.Init();
|
||||
}
|
||||
|
||||
historySeries.UpdateNewEpisodes();
|
||||
|
@ -195,18 +249,33 @@ public class History(){
|
|||
|
||||
historySeries.Seasons.Add(newSeason);
|
||||
historySeries.UpdateNewEpisodes();
|
||||
historySeries.Init();
|
||||
newSeason.Init();
|
||||
}
|
||||
|
||||
SortItems();
|
||||
SortSeasons(historySeries);
|
||||
|
||||
MatchHistorySeriesWithSonarr(false);
|
||||
await MatchHistoryEpisodesWithSonarr(false, historySeries);
|
||||
UpdateHistoryFile();
|
||||
|
||||
}
|
||||
|
||||
public async Task UpdateWithSeasonData(CrunchyEpisodeList seasonData){
|
||||
public async Task UpdateWithSeasonData(CrunchyEpisodeList seasonData,bool skippVersionCheck = true){
|
||||
if (seasonData.Data != null){
|
||||
|
||||
if (!skippVersionCheck){
|
||||
if (seasonData.Data.First().Versions != null){
|
||||
var version = seasonData.Data.First().Versions.Find(a => a.Original);
|
||||
if (version.AudioLocale != seasonData.Data.First().AudioLocale){
|
||||
UpdateSeries(seasonData.Data.First().SeriesId, version.SeasonGuid);
|
||||
return;
|
||||
}
|
||||
} else{
|
||||
UpdateSeries(seasonData.Data.First().SeriesId, "");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var firstEpisode = seasonData.Data.First();
|
||||
var seriesId = firstEpisode.SeriesId;
|
||||
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
||||
|
@ -220,6 +289,7 @@ public class History(){
|
|||
if (historySeason != null){
|
||||
historySeason.SeasonTitle = firstEpisode.SeasonTitle;
|
||||
historySeason.SeasonNum = Helpers.ExtractNumberAfterS(firstEpisode.Identifier) ?? firstEpisode.SeasonNumber + "";
|
||||
historySeason.SpecialSeason = CheckStringForSpecial(firstEpisode.Identifier);
|
||||
foreach (var crunchyEpisode in seasonData.Data){
|
||||
var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == crunchyEpisode.Id);
|
||||
|
||||
|
@ -252,6 +322,7 @@ public class History(){
|
|||
newSeason.EpisodesList.Sort(new NumericStringPropertyComparer());
|
||||
|
||||
historySeries.Seasons.Add(newSeason);
|
||||
newSeason.Init();
|
||||
}
|
||||
|
||||
historySeries.UpdateNewEpisodes();
|
||||
|
@ -272,16 +343,13 @@ public class History(){
|
|||
|
||||
|
||||
historySeries.Seasons.Add(newSeason);
|
||||
|
||||
historySeries.UpdateNewEpisodes();
|
||||
historySeries.Init();
|
||||
newSeason.Init();
|
||||
}
|
||||
|
||||
SortItems();
|
||||
SortSeasons(historySeries);
|
||||
|
||||
MatchHistorySeriesWithSonarr(false);
|
||||
await MatchHistoryEpisodesWithSonarr(false, historySeries);
|
||||
UpdateHistoryFile();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -358,19 +426,19 @@ public class History(){
|
|||
return;
|
||||
|
||||
case SortingType.HistorySeriesAddDate:
|
||||
|
||||
|
||||
var sortedSeriesAddDates = Crunchyroll.Instance.HistoryList
|
||||
.OrderBy(s => sortingDir
|
||||
? -(s.HistorySeriesAddDate?.Date.Ticks ?? DateTime.MinValue.Ticks)
|
||||
: s.HistorySeriesAddDate?.Date.Ticks ?? DateTime.MaxValue.Ticks)
|
||||
.ThenBy(s => s.SeriesTitle)
|
||||
.ToList();
|
||||
|
||||
|
||||
|
||||
Crunchyroll.Instance.HistoryList.Clear();
|
||||
|
||||
Crunchyroll.Instance.HistoryList.AddRange(sortedSeriesAddDates);
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -388,7 +456,6 @@ public class History(){
|
|||
}
|
||||
|
||||
|
||||
|
||||
private string GetSeriesThumbnail(CrSeriesBase series){
|
||||
// var series = await crunInstance.CrSeries.SeriesById(seriesId);
|
||||
|
||||
|
@ -400,6 +467,10 @@ public class History(){
|
|||
}
|
||||
|
||||
private static bool CheckStringForSpecial(string identifier){
|
||||
if (string.IsNullOrEmpty(identifier)){
|
||||
return false;
|
||||
}
|
||||
|
||||
// Regex pattern to match any sequence that does NOT contain "|S" followed by one or more digits immediately after
|
||||
string pattern = @"^(?!.*\|S\d+).*";
|
||||
|
||||
|
@ -564,6 +635,9 @@ public class History(){
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
CfgManager.UpdateHistoryFile();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -675,11 +749,12 @@ public class History(){
|
|||
|
||||
public class NumericStringPropertyComparer : IComparer<HistoryEpisode>{
|
||||
public int Compare(HistoryEpisode x, HistoryEpisode y){
|
||||
if (int.TryParse(x.Episode, out int xInt) && int.TryParse(y.Episode, out int yInt)){
|
||||
return xInt.CompareTo(yInt);
|
||||
if (double.TryParse(x.Episode, NumberStyles.Any, CultureInfo.InvariantCulture, out double xDouble) &&
|
||||
double.TryParse(y.Episode, NumberStyles.Any, CultureInfo.InvariantCulture, out double yDouble)){
|
||||
return xDouble.CompareTo(yDouble);
|
||||
}
|
||||
|
||||
// Fall back to string comparison if not parseable as integers
|
||||
return String.Compare(x.Episode, y.Episode, StringComparison.Ordinal);
|
||||
// Fall back to string comparison if not parseable as doubles
|
||||
return string.Compare(x.Episode, y.Episode, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
|
@ -154,6 +154,9 @@ public enum MediaType{
|
|||
public enum DownloadMediaType{
|
||||
[EnumMember(Value = "Video")]
|
||||
Video,
|
||||
|
||||
[EnumMember(Value = "SyncVideo")]
|
||||
SyncVideo,
|
||||
|
||||
[EnumMember(Value = "Audio")]
|
||||
Audio,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Reflection;
|
||||
using CRD.Downloader;
|
||||
using CRD.Utils.Structs;
|
||||
|
@ -34,24 +35,41 @@ public class CfgManager{
|
|||
private static StreamWriter logFile;
|
||||
private static bool isLogModeEnabled = false;
|
||||
|
||||
static CfgManager(){
|
||||
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
|
||||
}
|
||||
|
||||
private static void OnProcessExit(object? sender, EventArgs e){
|
||||
DisableLogMode();
|
||||
}
|
||||
|
||||
public static void EnableLogMode(){
|
||||
if (!isLogModeEnabled){
|
||||
logFile = new StreamWriter(PathLogFile);
|
||||
logFile.AutoFlush = true;
|
||||
Console.SetError(logFile);
|
||||
isLogModeEnabled = true;
|
||||
Console.Error.WriteLine("Log mode enabled.");
|
||||
try{
|
||||
var fileStream = new FileStream(PathLogFile, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
|
||||
logFile = new StreamWriter(fileStream);
|
||||
logFile.AutoFlush = true;
|
||||
Console.SetError(logFile);
|
||||
isLogModeEnabled = true;
|
||||
Console.Error.WriteLine("Log mode enabled.");
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine($"Couldn't enable logging: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DisableLogMode(){
|
||||
if (isLogModeEnabled){
|
||||
logFile.Close();
|
||||
StreamWriter standardError = new StreamWriter(Console.OpenStandardError());
|
||||
standardError.AutoFlush = true;
|
||||
Console.SetError(standardError);
|
||||
isLogModeEnabled = false;
|
||||
Console.Error.WriteLine("Log mode disabled.");
|
||||
try{
|
||||
logFile.Close();
|
||||
StreamWriter standardError = new StreamWriter(Console.OpenStandardError());
|
||||
standardError.AutoFlush = true;
|
||||
Console.SetError(standardError);
|
||||
isLogModeEnabled = false;
|
||||
Console.Error.WriteLine("Log mode disabled.");
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine($"Couldn't disable logging: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,7 +145,7 @@ public class CfgManager{
|
|||
File.WriteAllText(PathCrDownloadOptions, yaml);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void UpdateSettingsFromFile(){
|
||||
string dirPath = Path.GetDirectoryName(PathCrDownloadOptions) ?? string.Empty;
|
||||
|
||||
|
@ -188,6 +206,10 @@ public class CfgManager{
|
|||
return properties;
|
||||
}
|
||||
|
||||
public static void UpdateHistoryFile(){
|
||||
WriteJsonToFile(PathCrHistory, Crunchyroll.Instance.HistoryList);
|
||||
}
|
||||
|
||||
private static object fileLock = new object();
|
||||
|
||||
public static void WriteJsonToFile(string pathToFile, object obj){
|
||||
|
@ -199,9 +221,9 @@ public class CfgManager{
|
|||
}
|
||||
|
||||
lock (fileLock){
|
||||
// Write the JSON string to file using a streaming approach.
|
||||
using (var fileStream = new FileStream(pathToFile, FileMode.Create, FileAccess.Write))
|
||||
using (var streamWriter = new StreamWriter(fileStream))
|
||||
using (var gzipStream = new GZipStream(fileStream, CompressionLevel.Optimal))
|
||||
using (var streamWriter = new StreamWriter(gzipStream))
|
||||
using (var jsonWriter = new JsonTextWriter(streamWriter){ Formatting = Formatting.Indented }){
|
||||
var serializer = new JsonSerializer();
|
||||
serializer.Serialize(jsonWriter, obj);
|
||||
|
@ -212,6 +234,39 @@ public class CfgManager{
|
|||
}
|
||||
}
|
||||
|
||||
public static string DecompressJsonFile(string pathToFile){
|
||||
try{
|
||||
using (var fileStream = new FileStream(pathToFile, FileMode.Open, FileAccess.Read)){
|
||||
// Check if the file is compressed
|
||||
if (IsFileCompressed(fileStream)){
|
||||
// Reset the stream position to the beginning
|
||||
fileStream.Position = 0;
|
||||
using (var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress))
|
||||
using (var streamReader = new StreamReader(gzipStream)){
|
||||
return streamReader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// If not compressed, read the file as is
|
||||
fileStream.Position = 0;
|
||||
using (var streamReader = new StreamReader(fileStream)){
|
||||
return streamReader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"An error occurred: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsFileCompressed(FileStream fileStream){
|
||||
// Check the first two bytes for the GZip header
|
||||
var buffer = new byte[2];
|
||||
fileStream.Read(buffer, 0, 2);
|
||||
return buffer[0] == 0x1F && buffer[1] == 0x8B;
|
||||
}
|
||||
|
||||
|
||||
public static bool CheckIfFileExists(string filePath){
|
||||
string dirPath = Path.GetDirectoryName(filePath) ?? string.Empty;
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ public class FileNameManager{
|
|||
string[] parts = replacement.Split(',');
|
||||
string formattedIntegerPart = parts[0].PadLeft(numbers, '0');
|
||||
replacement = formattedIntegerPart + (parts.Length > 1 ? "," + parts[1] : "");
|
||||
replacement = replacement.Replace(",", ".");
|
||||
} else if (variable.Sanitize){
|
||||
replacement = CleanupFilename(replacement);
|
||||
}
|
||||
|
|
|
@ -196,6 +196,21 @@ public class Helpers{
|
|||
}
|
||||
}
|
||||
|
||||
public static void DeleteFile(string filePath){
|
||||
if (string.IsNullOrEmpty(filePath)){
|
||||
return;
|
||||
}
|
||||
|
||||
try{
|
||||
if (File.Exists(filePath)){
|
||||
File.Delete(filePath);
|
||||
}
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"Failed to delete file {filePath}. Error: {ex.Message}");
|
||||
// Handle exceptions if you need to log them or throw
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<(bool IsOk, int ErrorCode)> ExecuteCommandAsyncWorkDir(string type, string bin, string command,string workingDir){
|
||||
try{
|
||||
using (var process = new Process()){
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using CRD.Utils.Structs;
|
||||
|
@ -11,7 +12,7 @@ using DynamicData;
|
|||
namespace CRD.Utils.Muxing;
|
||||
|
||||
public class Merger{
|
||||
private MergerOptions options;
|
||||
public MergerOptions options;
|
||||
|
||||
public Merger(MergerOptions options){
|
||||
this.options = options;
|
||||
|
@ -45,6 +46,10 @@ public class Merger{
|
|||
}
|
||||
|
||||
foreach (var aud in options.OnlyAudio){
|
||||
if (aud.Delay != null && aud.Delay != 0){
|
||||
args.Add($"-itsoffset {aud.Delay}");
|
||||
}
|
||||
|
||||
args.Add($"-i \"{aud.Path}\"");
|
||||
metaData.Add($"-map {index}:a");
|
||||
metaData.Add($"-metadata:s:a:{audioIndex} language={aud.Language.Code}");
|
||||
|
@ -53,14 +58,13 @@ public class Merger{
|
|||
}
|
||||
|
||||
if (options.Chapters != null && options.Chapters.Count > 0){
|
||||
|
||||
Helpers.ConvertChapterFileForFFMPEG(options.Chapters[0].Path);
|
||||
|
||||
|
||||
args.Add($"-i \"{options.Chapters[0].Path}\"");
|
||||
metaData.Add($"-map_metadata {index}");
|
||||
index++;
|
||||
}
|
||||
|
||||
|
||||
foreach (var sub in options.Subtitles.Select((value, i) => new{ value, i })){
|
||||
if (sub.value.Delay != null){
|
||||
args.Add($"-itsoffset -{Math.Ceiling((double)sub.value.Delay * 1000)}ms");
|
||||
|
@ -68,7 +72,7 @@ public class Merger{
|
|||
|
||||
args.Add($"-i \"{sub.value.File}\"");
|
||||
}
|
||||
|
||||
|
||||
if (options.Output.EndsWith(".mkv", StringComparison.OrdinalIgnoreCase)){
|
||||
if (options.Fonts != null){
|
||||
int fontIndex = 0;
|
||||
|
@ -78,7 +82,7 @@ public class Merger{
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
args.AddRange(metaData);
|
||||
args.AddRange(options.Subtitles.Select((sub, subIndex) => $"-map {subIndex + index}"));
|
||||
args.Add("-c:v copy");
|
||||
|
@ -87,9 +91,8 @@ public class Merger{
|
|||
args.AddRange(options.Subtitles.Select((sub, subindex) =>
|
||||
$"-metadata:s:s:{subindex} title=\"{sub.Language.Language ?? sub.Language.Name}{(sub.ClosedCaption == true ? $" {options.CcTag}" : "")}{(sub.Signs == true ? " Signs" : "")}\" -metadata:s:s:{subindex} language={sub.Language.Code}"));
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(options.VideoTitle)){
|
||||
|
||||
if (!string.IsNullOrEmpty(options.VideoTitle)){
|
||||
args.Add($"-metadata title=\"{options.VideoTitle}\"");
|
||||
}
|
||||
|
||||
|
@ -125,7 +128,6 @@ public class Merger{
|
|||
}
|
||||
|
||||
|
||||
|
||||
public string MkvMerge(){
|
||||
List<string> args = new List<string>();
|
||||
|
||||
|
@ -157,8 +159,7 @@ public class Merger{
|
|||
args.Add("--no-video");
|
||||
args.Add($"--track-name 0:\"{trackName}\"");
|
||||
args.Add($"--language 0:{aud.Language.Code}");
|
||||
|
||||
|
||||
|
||||
|
||||
if (options.Defaults.Audio.Code == aud.Language.Code){
|
||||
args.Add("--default-track 0");
|
||||
|
@ -166,6 +167,10 @@ public class Merger{
|
|||
args.Add("--default-track 0:0");
|
||||
}
|
||||
|
||||
if (aud.Delay != null && aud.Delay != 0){
|
||||
args.Add($"--sync 0:{aud.Delay}");
|
||||
}
|
||||
|
||||
args.Add($"\"{aud.Path}\"");
|
||||
}
|
||||
|
||||
|
@ -216,14 +221,65 @@ public class Merger{
|
|||
if (options.Description is{ Count: > 0 }){
|
||||
args.Add($"--global-tags \"{options.Description[0].Path}\"");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return string.Join(" ", args);
|
||||
}
|
||||
|
||||
|
||||
public async Task<double> ProcessVideo(string baseVideoPath, string compareVideoPath){
|
||||
var tempDir = Path.GetTempPath(); //TODO - maybe move this out of temp
|
||||
var baseFramesDir = Path.Combine(tempDir, "base_frames");
|
||||
var compareFramesDir = Path.Combine(tempDir, "compare_frames");
|
||||
|
||||
Directory.CreateDirectory(baseFramesDir);
|
||||
Directory.CreateDirectory(compareFramesDir);
|
||||
|
||||
var extractFramesBase = await SyncingHelper.ExtractFrames(baseVideoPath, baseFramesDir, 0, 60);
|
||||
var extractFramesCompare = await SyncingHelper.ExtractFrames(compareVideoPath, compareFramesDir, 0, 60);
|
||||
|
||||
if (!extractFramesBase.IsOk || !extractFramesCompare.IsOk){
|
||||
Console.Error.WriteLine("Failed to extract Frames to Compare");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var baseFrames = Directory.GetFiles(baseFramesDir).Select(fp => new FrameData
|
||||
{
|
||||
FilePath = fp,
|
||||
Time = GetTimeFromFileName(fp, extractFramesBase.frameRate)
|
||||
}).ToList();
|
||||
|
||||
var compareFrames = Directory.GetFiles(compareFramesDir).Select(fp => new FrameData
|
||||
{
|
||||
FilePath = fp,
|
||||
Time = GetTimeFromFileName(fp, extractFramesBase.frameRate)
|
||||
}).ToList();
|
||||
|
||||
var offset = SyncingHelper.CalculateOffset(baseFrames, compareFrames);
|
||||
Console.WriteLine($"Calculated offset: {offset} seconds");
|
||||
|
||||
CleanupDirectory(baseFramesDir);
|
||||
CleanupDirectory(compareFramesDir);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
private static void CleanupDirectory(string dirPath){
|
||||
if (Directory.Exists(dirPath)){
|
||||
Directory.Delete(dirPath, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static double GetTimeFromFileName(string fileName, double frameRate){
|
||||
var match = Regex.Match(Path.GetFileName(fileName), @"frame(\d+)");
|
||||
if (match.Success){
|
||||
return int.Parse(match.Groups[1].Value) / frameRate; // Assuming 30 fps
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
public async Task Merge(string type, string bin){
|
||||
string command = type switch{
|
||||
"ffmpeg" => FFmpeg(),
|
||||
|
@ -253,28 +309,19 @@ public class Merger{
|
|||
// Combine all media file lists and iterate through them
|
||||
var allMediaFiles = options.OnlyAudio.Concat(options.OnlyVid)
|
||||
.ToList();
|
||||
allMediaFiles.ForEach(file => DeleteFile(file.Path));
|
||||
allMediaFiles.ForEach(file => DeleteFile(file.Path + ".resume"));
|
||||
|
||||
options.Description?.ForEach(chapter => DeleteFile(chapter.Path));
|
||||
|
||||
allMediaFiles.ForEach(file => Helpers.DeleteFile(file.Path));
|
||||
allMediaFiles.ForEach(file => Helpers.DeleteFile(file.Path + ".resume"));
|
||||
|
||||
options.Description?.ForEach(chapter => Helpers.DeleteFile(chapter.Path));
|
||||
|
||||
// Delete chapter files if any
|
||||
options.Chapters?.ForEach(chapter => DeleteFile(chapter.Path));
|
||||
options.Chapters?.ForEach(chapter => Helpers.DeleteFile(chapter.Path));
|
||||
|
||||
// Delete subtitle files
|
||||
options.Subtitles.ForEach(subtitle => DeleteFile(subtitle.File));
|
||||
options.Subtitles.ForEach(subtitle => Helpers.DeleteFile(subtitle.File));
|
||||
}
|
||||
|
||||
private void DeleteFile(string filePath){
|
||||
try{
|
||||
if (File.Exists(filePath)){
|
||||
File.Delete(filePath);
|
||||
}
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"Failed to delete file {filePath}. Error: {ex.Message}");
|
||||
// Handle exceptions if you need to log them or throw
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class MergerInput{
|
||||
|
@ -291,6 +338,8 @@ public class SubtitleInput{
|
|||
public bool? ClosedCaption{ get; set; }
|
||||
public bool? Signs{ get; set; }
|
||||
public int? Delay{ get; set; }
|
||||
|
||||
public DownloadedMedia? RelatedVideoDownloadMedia;
|
||||
}
|
||||
|
||||
public class ParsedFont{
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Utils.Structs;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace CRD.Utils.Muxing;
|
||||
|
||||
public class SyncingHelper{
|
||||
public static async Task<(bool IsOk, int ErrorCode, double frameRate)> ExtractFrames(string videoPath, string outputDir, double offset, double duration){
|
||||
var ffmpegPath = CfgManager.PathFFMPEG;
|
||||
var arguments = $"-i \"{videoPath}\" -vf \"select='gt(scene,0.1)',showinfo\" -vsync vfr -frame_pts true -t {duration} -ss {offset} \"{outputDir}\\frame%03d.png\"";
|
||||
|
||||
var output = "";
|
||||
|
||||
try{
|
||||
using (var process = new Process()){
|
||||
process.StartInfo.FileName = ffmpegPath;
|
||||
process.StartInfo.Arguments = arguments;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
|
||||
process.OutputDataReceived += (sender, e) => {
|
||||
if (!string.IsNullOrEmpty(e.Data)){
|
||||
Console.WriteLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (sender, e) => {
|
||||
if (!string.IsNullOrEmpty(e.Data)){
|
||||
Console.WriteLine($"{e.Data}");
|
||||
output += e.Data;
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
await process.WaitForExitAsync();
|
||||
bool isSuccess = process.ExitCode == 0;
|
||||
double frameRate = ExtractFrameRate(output);
|
||||
return (IsOk: isSuccess, ErrorCode: process.ExitCode, frameRate);
|
||||
}
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"An error occurred: {ex.Message}");
|
||||
return (IsOk: false, ErrorCode: -1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static double ExtractFrameRate(string ffmpegOutput){
|
||||
var match = Regex.Match(ffmpegOutput, @"Stream #0:0.*?(\d+(?:\.\d+)?) fps");
|
||||
if (match.Success){
|
||||
return double.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
Console.Error.WriteLine("Failed to extract frame rate from FFmpeg output.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static double CalculateSSIM(float[] pixels1, float[] pixels2, int width, int height){
|
||||
double mean1 = pixels1.Average();
|
||||
double mean2 = pixels2.Average();
|
||||
|
||||
double var1 = 0, var2 = 0, covariance = 0;
|
||||
int count = pixels1.Length;
|
||||
|
||||
for (int i = 0; i < count; i++){
|
||||
var1 += (pixels1[i] - mean1) * (pixels1[i] - mean1);
|
||||
var2 += (pixels2[i] - mean2) * (pixels2[i] - mean2);
|
||||
covariance += (pixels1[i] - mean1) * (pixels2[i] - mean2);
|
||||
}
|
||||
|
||||
var1 /= count - 1;
|
||||
var2 /= count - 1;
|
||||
covariance /= count - 1;
|
||||
|
||||
double c1 = 0.01 * 0.01 * 255 * 255;
|
||||
double c2 = 0.03 * 0.03 * 255 * 255;
|
||||
|
||||
double ssim = ((2 * mean1 * mean2 + c1) * (2 * covariance + c2)) /
|
||||
((mean1 * mean1 + mean2 * mean2 + c1) * (var1 + var2 + c2));
|
||||
|
||||
return ssim;
|
||||
}
|
||||
|
||||
private static float[] ExtractPixels(Image<Rgba32> image, int width, int height){
|
||||
float[] pixels = new float[width * height];
|
||||
int index = 0;
|
||||
|
||||
image.ProcessPixelRows(accessor => {
|
||||
for (int y = 0; y < accessor.Height; y++){
|
||||
Span<Rgba32> row = accessor.GetRowSpan(y);
|
||||
for (int x = 0; x < row.Length; x++){
|
||||
pixels[index++] = row[x].R;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return pixels;
|
||||
}
|
||||
|
||||
public static double ComputeSSIM(string imagePath1, string imagePath2, int targetWidth, int targetHeight){
|
||||
using (var image1 = Image.Load<Rgba32>(imagePath1))
|
||||
using (var image2 = Image.Load<Rgba32>(imagePath2)){
|
||||
// Preprocess images (resize and convert to grayscale)
|
||||
image1.Mutate(x => x.Resize(new ResizeOptions{
|
||||
Size = new Size(targetWidth, targetHeight),
|
||||
Mode = ResizeMode.Max
|
||||
}).Grayscale());
|
||||
|
||||
image2.Mutate(x => x.Resize(new ResizeOptions{
|
||||
Size = new Size(targetWidth, targetHeight),
|
||||
Mode = ResizeMode.Max
|
||||
}).Grayscale());
|
||||
|
||||
// Extract pixel values into arrays
|
||||
float[] pixels1 = ExtractPixels(image1, targetWidth, targetHeight);
|
||||
float[] pixels2 = ExtractPixels(image2, targetWidth, targetHeight);
|
||||
|
||||
// Compute SSIM
|
||||
return CalculateSSIM(pixels1, pixels2, targetWidth, targetHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool AreFramesSimilar(string imagePath1, string imagePath2, double ssimThreshold){
|
||||
double ssim = ComputeSSIM(imagePath1, imagePath2, 256, 256);
|
||||
Console.WriteLine($"SSIM: {ssim}");
|
||||
return ssim > ssimThreshold;
|
||||
}
|
||||
|
||||
public static double CalculateOffset(List<FrameData> baseFrames, List<FrameData> compareFrames, double ssimThreshold = 0.9){
|
||||
foreach (var baseFrame in baseFrames){
|
||||
var matchingFrame = compareFrames.FirstOrDefault(f => AreFramesSimilar(baseFrame.FilePath, f.FilePath, ssimThreshold));
|
||||
if (matchingFrame != null){
|
||||
Console.WriteLine($"Matched Frame: Base Frame Time: {baseFrame.Time}, Compare Frame Time: {matchingFrame.Time}");
|
||||
return baseFrame.Time - matchingFrame.Time;
|
||||
} else{
|
||||
// Console.WriteLine($"No Match Found for Base Frame Time: {baseFrame.Time}");
|
||||
Debug.WriteLine($"No Match Found for Base Frame Time: {baseFrame.Time}");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -108,7 +108,7 @@ public class CrDownloadOptions{
|
|||
[YamlIgnore]
|
||||
public bool? Skipmux{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
[YamlMember(Alias = "mux_sync_dubs", ApplyNamingConventions = false)]
|
||||
public bool SyncTiming{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
|
|
|
@ -250,7 +250,8 @@ public class CrunchyEpMeta{
|
|||
public List<string>? AvailableSubs{ get; set; }
|
||||
|
||||
public string? DownloadPath{ get; set; }
|
||||
|
||||
public List<string> DownloadSubs{ get; set; } =[];
|
||||
|
||||
}
|
||||
|
||||
public class DownloadProgress{
|
||||
|
|
|
@ -6,6 +6,7 @@ using Newtonsoft.Json;
|
|||
namespace CRD.Utils.Structs.History;
|
||||
|
||||
public class HistoryEpisode : INotifyPropertyChanged{
|
||||
|
||||
[JsonProperty("episode_title")]
|
||||
public string? EpisodeTitle{ get; set; }
|
||||
|
||||
|
@ -48,7 +49,7 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
|||
WasDownloaded = !WasDownloaded;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(WasDownloaded)));
|
||||
}
|
||||
|
||||
|
||||
public void ToggleWasDownloadedSeries(HistorySeries? series){
|
||||
WasDownloaded = !WasDownloaded;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(WasDownloaded)));
|
||||
|
@ -57,14 +58,15 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
|||
foreach (var historySeason in series.Seasons){
|
||||
historySeason.UpdateDownloadedSilent();
|
||||
}
|
||||
|
||||
series.UpdateNewEpisodes();
|
||||
}
|
||||
|
||||
|
||||
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList);
|
||||
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
public async Task DownloadEpisode(){
|
||||
await Crunchyroll.Instance.AddEpisodeToQue(EpisodeId, Crunchyroll.Instance.DefaultLocale, Crunchyroll.Instance.CrunOptions.DubLang);
|
||||
await Crunchyroll.Instance.AddEpisodeToQue(EpisodeId, string.IsNullOrEmpty(Crunchyroll.Instance.CrunOptions.HistoryLang) ? Crunchyroll.Instance.DefaultLocale : Crunchyroll.Instance.CrunOptions.HistoryLang,
|
||||
Crunchyroll.Instance.CrunOptions.DubLang);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using CRD.Downloader;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
@ -19,9 +22,6 @@ public class HistorySeason : INotifyPropertyChanged{
|
|||
[JsonProperty("season_special_season")]
|
||||
public bool? SpecialSeason{ get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string CombinedProperty => SpecialSeason ?? false ? $"Specials {SeasonNum}" : $"Season {SeasonNum}";
|
||||
|
||||
[JsonProperty("season_downloaded_episodes")]
|
||||
public int DownloadedEpisodes{ get; set; }
|
||||
|
||||
|
@ -30,36 +30,126 @@ public class HistorySeason : INotifyPropertyChanged{
|
|||
|
||||
[JsonProperty("series_download_path")]
|
||||
public string? SeasonDownloadPath{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("history_season_soft_subs_override")]
|
||||
public List<string> HistorySeasonSoftSubsOverride{ get; set; } =[];
|
||||
|
||||
[JsonProperty("history_season_dub_lang_override")]
|
||||
public List<string> HistorySeasonDubLangOverride{ get; set; } =[];
|
||||
|
||||
[JsonIgnore]
|
||||
public string CombinedProperty => SpecialSeason ?? false ? $"Specials {SeasonNum}" : $"Season {SeasonNum}";
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsExpanded{ get; set; }
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
#region Language Override
|
||||
|
||||
[JsonIgnore]
|
||||
public string SelectedSubs{ get; set; } = "";
|
||||
|
||||
[JsonIgnore]
|
||||
public string SelectedDubs{ get; set; } = "";
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<StringItem> SelectedSubLang{ get; set; } = new();
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<StringItem> SelectedDubLang{ get; set; } = new();
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<StringItem> DubLangList{ get; } = new(){
|
||||
};
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<StringItem> SubLangList{ get; } = new(){
|
||||
new StringItem(){ stringValue = "all" },
|
||||
new StringItem(){ stringValue = "none" },
|
||||
};
|
||||
|
||||
private void UpdateSubAndDubString(){
|
||||
HistorySeasonSoftSubsOverride.Clear();
|
||||
HistorySeasonDubLangOverride.Clear();
|
||||
|
||||
if (SelectedSubLang.Count != 0){
|
||||
for (var i = 0; i < SelectedSubLang.Count; i++){
|
||||
HistorySeasonSoftSubsOverride.Add(SelectedSubLang[i].stringValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (SelectedDubLang.Count != 0){
|
||||
for (var i = 0; i < SelectedDubLang.Count; i++){
|
||||
HistorySeasonDubLangOverride.Add(SelectedDubLang[i].stringValue);
|
||||
}
|
||||
}
|
||||
|
||||
SelectedDubs = string.Join(", ", HistorySeasonDubLangOverride) ?? "";
|
||||
SelectedSubs = string.Join(", ", HistorySeasonSoftSubsOverride) ?? "";
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedSubs)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedDubs)));
|
||||
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
private void Changes(object? sender, NotifyCollectionChangedEventArgs e){
|
||||
UpdateSubAndDubString();
|
||||
}
|
||||
|
||||
public void Init(){
|
||||
|
||||
if (!(SubLangList.Count > 2 || DubLangList.Count > 0)){
|
||||
foreach (var languageItem in Languages.languages){
|
||||
SubLangList.Add(new StringItem{ stringValue = languageItem.CrLocale });
|
||||
DubLangList.Add(new StringItem{ stringValue = languageItem.CrLocale });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var softSubLang = SubLangList.Where(a => HistorySeasonSoftSubsOverride.Contains(a.stringValue)).ToList();
|
||||
var dubLang = DubLangList.Where(a => HistorySeasonDubLangOverride.Contains(a.stringValue)).ToList();
|
||||
|
||||
SelectedSubLang.Clear();
|
||||
foreach (var listBoxItem in softSubLang){
|
||||
SelectedSubLang.Add(listBoxItem);
|
||||
}
|
||||
|
||||
SelectedDubLang.Clear();
|
||||
foreach (var listBoxItem in dubLang){
|
||||
SelectedDubLang.Add(listBoxItem);
|
||||
}
|
||||
|
||||
SelectedDubs = string.Join(", ", HistorySeasonDubLangOverride) ?? "";
|
||||
SelectedSubs = string.Join(", ", HistorySeasonSoftSubsOverride) ?? "";
|
||||
|
||||
SelectedSubLang.CollectionChanged += Changes;
|
||||
SelectedDubLang.CollectionChanged += Changes;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void UpdateDownloaded(string? EpisodeId){
|
||||
if (!string.IsNullOrEmpty(EpisodeId)){
|
||||
EpisodesList.First(e => e.EpisodeId == EpisodeId).ToggleWasDownloaded();
|
||||
var episode = EpisodesList.First(e => e.EpisodeId == EpisodeId);
|
||||
episode.ToggleWasDownloaded();
|
||||
}
|
||||
|
||||
DownloadedEpisodes = EpisodesList.FindAll(e => e.WasDownloaded).Count;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DownloadedEpisodes)));
|
||||
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList);
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
public void UpdateDownloaded(){
|
||||
DownloadedEpisodes = EpisodesList.FindAll(e => e.WasDownloaded).Count;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DownloadedEpisodes)));
|
||||
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList);
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
|
||||
public void UpdateDownloadedSilent(){
|
||||
DownloadedEpisodes = EpisodesList.FindAll(e => e.WasDownloaded).Count;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DownloadedEpisodes)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class UpdateDownloadedHistorySeason{
|
||||
public string? EpisodeId;
|
||||
public HistorySeries? HistorySeries;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media.Imaging;
|
||||
using CRD.Downloader;
|
||||
using CRD.Utils.CustomList;
|
||||
|
@ -40,9 +42,6 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
[JsonProperty("series_new_episodes")]
|
||||
public int NewEpisodes{ get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Bitmap? ThumbnailImage{ get; set; }
|
||||
|
||||
[JsonProperty("series_season_list")]
|
||||
public required RefreshableObservableCollection<HistorySeason> Seasons{ get; set; }
|
||||
|
||||
|
@ -51,9 +50,18 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
|
||||
[JsonProperty("history_series_add_date")]
|
||||
public DateTime? HistorySeriesAddDate{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("history_series_soft_subs_override")]
|
||||
public List<string> HistorySeriesSoftSubsOverride{ get; set; } =[];
|
||||
|
||||
[JsonProperty("history_series_dub_lang_override")]
|
||||
public List<string> HistorySeriesDubLangOverride{ get; set; } =[];
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
[JsonIgnore]
|
||||
public Bitmap? ThumbnailImage{ get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool FetchingData{ get; set; }
|
||||
|
||||
|
@ -74,6 +82,90 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
[JsonIgnore]
|
||||
private bool _editModeEnabled;
|
||||
|
||||
#region Language Override
|
||||
|
||||
[JsonIgnore]
|
||||
public string SelectedSubs{ get; set; } = "";
|
||||
|
||||
[JsonIgnore]
|
||||
public string SelectedDubs{ get; set; } = "";
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<StringItem> SelectedSubLang{ get; set; } = new();
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<StringItem> SelectedDubLang{ get; set; } = new();
|
||||
|
||||
|
||||
private void UpdateSubAndDubString(){
|
||||
HistorySeriesSoftSubsOverride.Clear();
|
||||
HistorySeriesDubLangOverride.Clear();
|
||||
|
||||
if (SelectedSubLang.Count != 0){
|
||||
for (var i = 0; i < SelectedSubLang.Count; i++){
|
||||
HistorySeriesSoftSubsOverride.Add(SelectedSubLang[i].stringValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (SelectedDubLang.Count != 0){
|
||||
for (var i = 0; i < SelectedDubLang.Count; i++){
|
||||
HistorySeriesDubLangOverride.Add(SelectedDubLang[i].stringValue);
|
||||
}
|
||||
}
|
||||
|
||||
SelectedDubs = string.Join(", ", HistorySeriesDubLangOverride) ?? "";
|
||||
SelectedSubs = string.Join(", ", HistorySeriesSoftSubsOverride) ?? "";
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedSubs)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedDubs)));
|
||||
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
private void Changes(object? sender, NotifyCollectionChangedEventArgs e){
|
||||
UpdateSubAndDubString();
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<StringItem> DubLangList{ get; } = new(){
|
||||
};
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<StringItem> SubLangList{ get; } = new(){
|
||||
new StringItem(){ stringValue = "all" },
|
||||
new StringItem(){ stringValue = "none" },
|
||||
};
|
||||
|
||||
public void Init(){
|
||||
if (!(SubLangList.Count > 2 || DubLangList.Count > 0)){
|
||||
foreach (var languageItem in Languages.languages){
|
||||
SubLangList.Add(new StringItem{ stringValue = languageItem.CrLocale });
|
||||
DubLangList.Add(new StringItem{ stringValue = languageItem.CrLocale });
|
||||
}
|
||||
}
|
||||
|
||||
var softSubLang = SubLangList.Where(a => HistorySeriesSoftSubsOverride.Contains(a.stringValue)).ToList();
|
||||
var dubLang = DubLangList.Where(a => HistorySeriesDubLangOverride.Contains(a.stringValue)).ToList();
|
||||
|
||||
SelectedSubLang.Clear();
|
||||
foreach (var listBoxItem in softSubLang){
|
||||
SelectedSubLang.Add(listBoxItem);
|
||||
}
|
||||
|
||||
SelectedDubLang.Clear();
|
||||
foreach (var listBoxItem in dubLang){
|
||||
SelectedDubLang.Add(listBoxItem);
|
||||
}
|
||||
|
||||
SelectedDubs = string.Join(", ", HistorySeriesDubLangOverride) ?? "";
|
||||
SelectedSubs = string.Join(", ", HistorySeriesSoftSubsOverride) ?? "";
|
||||
|
||||
SelectedSubLang.CollectionChanged += Changes;
|
||||
SelectedDubLang.CollectionChanged += Changes;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public async Task LoadImage(){
|
||||
try{
|
||||
using (var client = new HttpClient()){
|
||||
|
@ -165,9 +257,8 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
if (objectToRemove != null){
|
||||
Seasons.Remove(objectToRemove);
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Seasons)));
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList);
|
||||
}
|
||||
|
||||
public void OpenSonarrPage(){
|
||||
|
|
|
@ -111,7 +111,7 @@ public class Languages{
|
|||
|
||||
|
||||
public static LanguageItem Locale2language(string locale){
|
||||
LanguageItem? filteredLocale = languages.FirstOrDefault(l => { return l.Locale == locale; });
|
||||
LanguageItem? filteredLocale = languages.FirstOrDefault(l => { return l.Locale == locale || l.CrLocale == locale; });
|
||||
if (filteredLocale != null){
|
||||
return (LanguageItem)filteredLocale;
|
||||
} else{
|
||||
|
|
|
@ -9,7 +9,9 @@ public struct AuthData{
|
|||
}
|
||||
|
||||
public class DrmAuthData{
|
||||
[JsonProperty("custom_data")] public string? CustomData{ get; set; }
|
||||
[JsonProperty("custom_data")]
|
||||
public string? CustomData{ get; set; }
|
||||
|
||||
public string? Token{ get; set; }
|
||||
}
|
||||
|
||||
|
@ -21,6 +23,7 @@ public struct Meta{
|
|||
public struct LanguageItem{
|
||||
[JsonProperty("cr_locale")]
|
||||
public string CrLocale{ get; set; }
|
||||
|
||||
public string Locale{ get; set; }
|
||||
public string Code{ get; set; }
|
||||
public string Name{ get; set; }
|
||||
|
@ -62,7 +65,7 @@ public struct Episode{
|
|||
public struct DownloadResponse{
|
||||
public List<DownloadedMedia> Data{ get; set; }
|
||||
public string FileName{ get; set; }
|
||||
|
||||
|
||||
public string VideoTitle{ get; set; }
|
||||
public bool Error{ get; set; }
|
||||
public string ErrorText{ get; set; }
|
||||
|
@ -75,6 +78,8 @@ public class DownloadedMedia : SxItem{
|
|||
|
||||
public bool? Cc{ get; set; }
|
||||
public bool? Signs{ get; set; }
|
||||
|
||||
public DownloadedMedia? RelatedVideoDownloadMedia;
|
||||
}
|
||||
|
||||
public class SxItem{
|
||||
|
@ -85,3 +90,11 @@ public class SxItem{
|
|||
public Dictionary<string, List<string>>? Fonts{ get; set; }
|
||||
}
|
||||
|
||||
public class FrameData{
|
||||
public string FilePath{ get; set; }
|
||||
public double Time{ get; set; }
|
||||
}
|
||||
|
||||
public class StringItem{
|
||||
public string stringValue{ get; set; }
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using CRD.Utils.Structs.History;
|
||||
|
||||
namespace CRD.Utils.UI;
|
||||
|
||||
public class UiUpdateDownloadedHistorySeasonConverter : IMultiValueConverter{
|
||||
|
||||
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture){
|
||||
|
||||
if (values[0] is string stringValue1){
|
||||
Console.WriteLine(stringValue1);
|
||||
}
|
||||
|
||||
if (values is[string stringValue, HistorySeries historySeries]){
|
||||
|
||||
return new UpdateDownloadedHistorySeason{
|
||||
EpisodeId = stringValue,
|
||||
HistorySeries = historySeries
|
||||
};
|
||||
}
|
||||
|
||||
return new UpdateDownloadedHistorySeason{
|
||||
EpisodeId = "",
|
||||
HistorySeries = null
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -1,16 +1,11 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Utils.HLS;
|
||||
using CRD.ViewModels;
|
||||
using CRD.Views.Utils;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.Updater;
|
||||
|
|
|
@ -89,7 +89,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
episode.LoadImage(imageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SearchItems.Add(episode);
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
RaisePropertyChanged(nameof(SearchVisible));
|
||||
SearchItems.Clear();
|
||||
}
|
||||
|
||||
|
||||
partial void OnUrlInputChanged(string value){
|
||||
if (SearchEnabled){
|
||||
UpdateSearch(value);
|
||||
|
@ -186,7 +186,10 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
if (match.Success){
|
||||
var locale = match.Groups[1].Value; // Capture the locale part
|
||||
var id = match.Groups[2].Value; // Capture the ID part
|
||||
Crunchyroll.Instance.AddEpisodeToQue(id, Languages.Locale2language(locale).CrLocale, Crunchyroll.Instance.CrunOptions.DubLang, true);
|
||||
Crunchyroll.Instance.AddEpisodeToQue(id,
|
||||
string.IsNullOrEmpty(locale)
|
||||
? string.IsNullOrEmpty(Crunchyroll.Instance.CrunOptions.HistoryLang) ? Crunchyroll.Instance.DefaultLocale : Crunchyroll.Instance.CrunOptions.HistoryLang
|
||||
: Languages.Locale2language(locale).CrLocale, Crunchyroll.Instance.CrunOptions.DubLang, true);
|
||||
UrlInput = "";
|
||||
selectedEpisodes.Clear();
|
||||
SelectedItems.Clear();
|
||||
|
@ -209,7 +212,9 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
|
||||
ButtonEnabled = false;
|
||||
ShowLoading = true;
|
||||
var list = await Crunchyroll.Instance.CrSeries.ListSeriesId(id, Languages.Locale2language(locale).CrLocale, new CrunchyMultiDownload(Crunchyroll.Instance.CrunOptions.DubLang, true));
|
||||
var list = await Crunchyroll.Instance.CrSeries.ListSeriesId(id, string.IsNullOrEmpty(locale)
|
||||
? string.IsNullOrEmpty(Crunchyroll.Instance.CrunOptions.HistoryLang) ? Crunchyroll.Instance.DefaultLocale : Crunchyroll.Instance.CrunOptions.HistoryLang
|
||||
: Languages.Locale2language(locale).CrLocale, new CrunchyMultiDownload(Crunchyroll.Instance.CrunOptions.DubLang, true));
|
||||
ShowLoading = false;
|
||||
if (list != null){
|
||||
currentSeriesList = list;
|
||||
|
@ -275,8 +280,8 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
}
|
||||
}
|
||||
|
||||
async partial void OnSelectedSearchItemChanged(CrBrowseSeries value){
|
||||
if (value == null){
|
||||
async partial void OnSelectedSearchItemChanged(CrBrowseSeries? value){
|
||||
if (value == null || string.IsNullOrEmpty(value.Id)){
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -286,7 +291,9 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
SearchVisible = false;
|
||||
ButtonEnabled = false;
|
||||
ShowLoading = true;
|
||||
var list = await Crunchyroll.Instance.CrSeries.ListSeriesId(value.Id, "", new CrunchyMultiDownload(Crunchyroll.Instance.CrunOptions.DubLang, true));
|
||||
var list = await Crunchyroll.Instance.CrSeries.ListSeriesId(value.Id,
|
||||
string.IsNullOrEmpty(Crunchyroll.Instance.CrunOptions.HistoryLang) ? Crunchyroll.Instance.DefaultLocale : Crunchyroll.Instance.CrunOptions.HistoryLang,
|
||||
new CrunchyMultiDownload(Crunchyroll.Instance.CrunOptions.DubLang, true));
|
||||
ShowLoading = false;
|
||||
if (list != null){
|
||||
currentSeriesList = list;
|
||||
|
@ -296,7 +303,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
episode.Lang));
|
||||
} else{
|
||||
episodesBySeason.Add("S" + episode.Season, new List<ItemModel>{
|
||||
new ItemModel(episode.Img, episode.Description, episode.Time, episode.Name, "S" + episode.Season, "E" + episode.EpisodeNum, episode.E, episode.Lang)
|
||||
new(episode.Img, episode.Description, episode.Time, episode.Name, "S" + episode.Season, "E" + episode.EpisodeNum, episode.E, episode.Lang)
|
||||
});
|
||||
SeasonList.Add(new ComboBoxItem{ Content = "S" + episode.Season });
|
||||
}
|
||||
|
@ -351,11 +358,11 @@ public class ItemModel(string imageUrl, string description, string time, string
|
|||
public string TitleFull{ get; set; } = season + episode + " - " + title;
|
||||
|
||||
public List<string> AvailableAudios{ get; set; } = availableAudios;
|
||||
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public async void LoadImage(string url){
|
||||
ImageBitmap = await Helpers.LoadImage(url);
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ImageBitmap)));
|
||||
}
|
||||
|
||||
}
|
|
@ -258,7 +258,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
private async void BuildCustomCalendar(){
|
||||
ShowLoading = true;
|
||||
|
||||
var newEpisodesBase = await Crunchyroll.Instance.CrEpisode.GetNewEpisodes(Crunchyroll.Instance.CrunOptions.HistoryLang, 200);
|
||||
var newEpisodesBase = await Crunchyroll.Instance.CrEpisode.GetNewEpisodes(Crunchyroll.Instance.CrunOptions.HistoryLang, 200,true);
|
||||
|
||||
CalendarWeek week = new CalendarWeek();
|
||||
week.CalendarDays = new List<CalendarDay>();
|
||||
|
@ -283,7 +283,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
foreach (var crBrowseEpisode in newEpisodes){
|
||||
var targetDate = FilterByAirDate ? crBrowseEpisode.EpisodeMetadata.EpisodeAirDate : crBrowseEpisode.LastPublic;
|
||||
|
||||
if (HideDubs && crBrowseEpisode.EpisodeMetadata.SeasonTitle != null && crBrowseEpisode.EpisodeMetadata.SeasonTitle.EndsWith("Dub)")){
|
||||
if (HideDubs && crBrowseEpisode.EpisodeMetadata.SeasonTitle != null && (crBrowseEpisode.EpisodeMetadata.SeasonTitle.EndsWith("Dub)") || crBrowseEpisode.EpisodeMetadata.AudioLocale != Locale.JaJp)){
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -304,8 +304,8 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
calEpisode.DateTime = targetDate;
|
||||
calEpisode.HasPassed = DateTime.Now > targetDate;
|
||||
calEpisode.EpisodeName = crBrowseEpisode.Title;
|
||||
calEpisode.SeriesUrl = "https://www.crunchyroll.com/series/" + crBrowseEpisode.EpisodeMetadata.SeriesId;
|
||||
calEpisode.EpisodeUrl = $"https://www.crunchyroll.com/de/watch/{crBrowseEpisode.Id}/";
|
||||
calEpisode.SeriesUrl = $"https://www.crunchyroll.com/{Crunchyroll.Instance.CrunOptions.HistoryLang}/series/" + crBrowseEpisode.EpisodeMetadata.SeriesId;
|
||||
calEpisode.EpisodeUrl = $"https://www.crunchyroll.com/{Crunchyroll.Instance.CrunOptions.HistoryLang}/watch/{crBrowseEpisode.Id}/";
|
||||
calEpisode.ThumbnailUrl = crBrowseEpisode.Images.Thumbnail.First().First().Source;
|
||||
calEpisode.IsPremiumOnly = crBrowseEpisode.EpisodeMetadata.IsPremiumOnly;
|
||||
calEpisode.IsPremiere = crBrowseEpisode.EpisodeMetadata.Episode == "1";
|
||||
|
|
|
@ -104,7 +104,7 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
|||
|
||||
var softSubs = "Softsub: ";
|
||||
|
||||
if (Crunchyroll.Instance.CrunOptions.DlSubs.Contains("all")){
|
||||
if (epMeta.DownloadSubs.Contains("all")){
|
||||
if (epMeta.AvailableSubs != null){
|
||||
foreach (var epMetaAvailableSub in epMeta.AvailableSubs){
|
||||
softSubs += epMetaAvailableSub + " ";
|
||||
|
@ -114,7 +114,7 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
|||
}
|
||||
}
|
||||
|
||||
foreach (var crunOptionsDlSub in Crunchyroll.Instance.CrunOptions.DlSubs){
|
||||
foreach (var crunOptionsDlSub in epMeta.DownloadSubs){
|
||||
if (epMeta.AvailableSubs != null && epMeta.AvailableSubs.Contains(crunOptionsDlSub)){
|
||||
softSubs += crunOptionsDlSub + " ";
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
public static bool _sortDir = false;
|
||||
|
||||
|
||||
public HistoryPageViewModel(){
|
||||
Items = Crunchyroll.Instance.HistoryList;
|
||||
|
||||
|
@ -217,7 +217,6 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
|
||||
if (!string.IsNullOrEmpty(value.SonarrSeriesId) && Crunchyroll.Instance.CrunOptions.SonarrProperties is{ SonarrEnabled: true }){
|
||||
Crunchyroll.Instance.CrHistory.MatchHistoryEpisodesWithSonarr(true, SelectedSeries);
|
||||
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList);
|
||||
}
|
||||
|
||||
|
||||
|
@ -230,9 +229,8 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
if (objectToRemove != null){
|
||||
Crunchyroll.Instance.HistoryList.Remove(objectToRemove);
|
||||
Items.Remove(objectToRemove);
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList);
|
||||
}
|
||||
|
||||
|
||||
|
@ -291,9 +289,9 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
|
||||
if (season != null){
|
||||
season.SeasonDownloadPath = selectedFolder.Path.LocalPath;
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,9 +314,8 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
|
||||
if (series != null){
|
||||
series.SeriesDownloadPath = selectedFolder.Path.LocalPath;
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,8 +48,8 @@ public partial class MainWindowViewModel : ViewModelBase{
|
|||
public async void Init(){
|
||||
UpdateAvailable = await Updater.Instance.CheckForUpdatesAsync();
|
||||
|
||||
await Crunchyroll.Instance.Init();
|
||||
|
||||
Crunchyroll.Instance.InitOptions();
|
||||
|
||||
if (Crunchyroll.Instance.CrunOptions.AccentColor != null){
|
||||
_faTheme.CustomAccentColor = Color.Parse(Crunchyroll.Instance.CrunOptions.AccentColor);
|
||||
}
|
||||
|
@ -63,5 +63,8 @@ public partial class MainWindowViewModel : ViewModelBase{
|
|||
_faTheme.PreferSystemTheme = false;
|
||||
Application.Current.RequestedThemeVariant = ThemeVariant.Light;
|
||||
}
|
||||
|
||||
await Crunchyroll.Instance.Init();
|
||||
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform.Storage;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
@ -21,28 +23,34 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
public static bool _editMode;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
public static bool _sonarrAvailable;
|
||||
|
||||
private IStorageProvider _storageProvider;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private IStorageProvider? _storageProvider;
|
||||
|
||||
public SeriesPageViewModel(){
|
||||
|
||||
|
||||
|
||||
_selectedSeries = Crunchyroll.Instance.SelectedSeries;
|
||||
|
||||
if (_selectedSeries.ThumbnailImage == null){
|
||||
_selectedSeries.LoadImage();
|
||||
}
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(SelectedSeries.SonarrSeriesId) && Crunchyroll.Instance.CrunOptions.SonarrProperties != null){
|
||||
SonarrAvailable = SelectedSeries.SonarrSeriesId.Length > 0 && Crunchyroll.Instance.CrunOptions.SonarrProperties.SonarrEnabled;
|
||||
}else{
|
||||
} else{
|
||||
SonarrAvailable = false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OpenFolderDialogAsync(HistorySeason? season){
|
||||
if (_storageProvider == null){
|
||||
|
@ -62,25 +70,25 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
|
||||
if (season != null){
|
||||
season.SeasonDownloadPath = selectedFolder.Path.LocalPath;
|
||||
CfgManager.UpdateHistoryFile();
|
||||
} else{
|
||||
SelectedSeries.SeriesDownloadPath = selectedFolder.Path.LocalPath;
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetStorageProvider(IStorageProvider storageProvider){
|
||||
_storageProvider = storageProvider ?? throw new ArgumentNullException(nameof(storageProvider));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public async Task UpdateData(string? season){
|
||||
await SelectedSeries.FetchData(season);
|
||||
|
||||
|
||||
SelectedSeries.Seasons.Refresh();
|
||||
|
||||
|
||||
// MessageBus.Current.SendMessage(new NavigationMessage(typeof(SeriesPageViewModel), false, true));
|
||||
}
|
||||
|
||||
|
@ -89,9 +97,8 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
HistorySeason? objectToRemove = SelectedSeries.Seasons.FirstOrDefault(se => se.SeasonId == season) ?? null;
|
||||
if (objectToRemove != null){
|
||||
SelectedSeries.Seasons.Remove(objectToRemove);
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList);
|
||||
MessageBus.Current.SendMessage(new NavigationMessage(typeof(SeriesPageViewModel), false, true));
|
||||
}
|
||||
|
||||
|
@ -101,7 +108,4 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
SelectedSeries.UpdateNewEpisodes();
|
||||
MessageBus.Current.SendMessage(new NavigationMessage(null, true, false));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -16,6 +16,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.CustomList;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Structs;
|
||||
using FluentAvalonia.Styling;
|
||||
|
@ -51,6 +52,10 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
private bool _muxToMp4;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _syncTimings;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _includeEpisodeDescription;
|
||||
|
@ -102,8 +107,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedDescriptionLang;
|
||||
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private string _selectedDubs = "ja-JP";
|
||||
|
||||
|
@ -268,7 +272,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
new ComboBoxItem(){ Content = "ar-SA" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> DubLangList{ get; } = new(){
|
||||
public ObservableCollection<ListBoxItem> DubLangList{ get; } = new(){
|
||||
};
|
||||
|
||||
|
||||
|
@ -318,7 +322,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
foreach (var languageItem in Languages.languages){
|
||||
HardSubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||
SubLangList.Add(new ListBoxItem{ Content = languageItem.CrLocale });
|
||||
DubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||
DubLangList.Add(new ListBoxItem{ Content = languageItem.CrLocale });
|
||||
DefaultDubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||
DefaultSubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||
}
|
||||
|
@ -358,9 +362,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
foreach (var listBoxItem in dubLang){
|
||||
SelectedDubLang.Add(listBoxItem);
|
||||
}
|
||||
|
||||
UpdateSubAndDubString();
|
||||
|
||||
|
||||
var props = options.SonarrProperties;
|
||||
|
||||
if (props != null){
|
||||
|
@ -383,6 +385,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
DownloadVideoForEveryDub = !options.DlVideoOnce;
|
||||
DownloadChapters = options.Chapters;
|
||||
MuxToMp4 = options.Mp4;
|
||||
SyncTimings = options.SyncTiming;
|
||||
SkipSubMux = options.SkipSubsMux;
|
||||
LeadingNumbers = options.Numbers;
|
||||
FileName = options.FileName;
|
||||
|
@ -417,6 +420,12 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
FfmpegOptions.Add(new MuxingParam(){ ParamValue = ffmpegParam });
|
||||
}
|
||||
}
|
||||
|
||||
var dubs = SelectedDubLang.Select(item => item.Content?.ToString());
|
||||
SelectedDubs = string.Join(", ", dubs) ?? "";
|
||||
|
||||
var subs = SelectedSubLang.Select(item => item.Content?.ToString());
|
||||
SelectedSubs = string.Join(", ", subs) ?? "";
|
||||
|
||||
SelectedSubLang.CollectionChanged += Changes;
|
||||
SelectedDubLang.CollectionChanged += Changes;
|
||||
|
@ -431,9 +440,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
if (!settingsLoaded){
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateSubAndDubString();
|
||||
|
||||
|
||||
Crunchyroll.Instance.CrunOptions.IncludeVideoDescription = IncludeEpisodeDescription;
|
||||
Crunchyroll.Instance.CrunOptions.VideoTitle = FileTitle;
|
||||
Crunchyroll.Instance.CrunOptions.Novids = !DownloadVideo;
|
||||
|
@ -441,6 +448,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
Crunchyroll.Instance.CrunOptions.DlVideoOnce = !DownloadVideoForEveryDub;
|
||||
Crunchyroll.Instance.CrunOptions.Chapters = DownloadChapters;
|
||||
Crunchyroll.Instance.CrunOptions.Mp4 = MuxToMp4;
|
||||
Crunchyroll.Instance.CrunOptions.SyncTiming = SyncTimings;
|
||||
Crunchyroll.Instance.CrunOptions.SkipSubsMux = SkipSubMux;
|
||||
Crunchyroll.Instance.CrunOptions.Numbers = Math.Clamp((int)(LeadingNumbers ?? 0),0,10);
|
||||
Crunchyroll.Instance.CrunOptions.FileName = FileName;
|
||||
|
@ -459,11 +467,11 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
|
||||
string descLang = SelectedDescriptionLang.Content + "";
|
||||
|
||||
Crunchyroll.Instance.CrunOptions.DescriptionLang = descLang != "default" ? descLang : "";
|
||||
Crunchyroll.Instance.CrunOptions.DescriptionLang = descLang != "default" ? descLang : Crunchyroll.Instance.DefaultLocale;
|
||||
|
||||
string historyLang = SelectedHistoryLang.Content + "";
|
||||
|
||||
Crunchyroll.Instance.CrunOptions.HistoryLang = historyLang != "default" ? historyLang : "";
|
||||
Crunchyroll.Instance.CrunOptions.HistoryLang = historyLang != "default" ? historyLang : Crunchyroll.Instance.DefaultLocale;
|
||||
|
||||
string hslang = SelectedHSLang.Content + "";
|
||||
|
||||
|
@ -481,10 +489,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
Crunchyroll.Instance.CrunOptions.DubLang = dubLangs;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Crunchyroll.Instance.CrunOptions.QualityAudio = SelectedAudioQuality?.Content + "";
|
||||
Crunchyroll.Instance.CrunOptions.QualityVideo = SelectedVideoQuality?.Content + "";
|
||||
Crunchyroll.Instance.CrunOptions.Theme = CurrentAppTheme?.Content + "";
|
||||
|
@ -556,28 +561,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
return ScaledBorderAndShadow[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void UpdateSubAndDubString(){
|
||||
if (SelectedSubLang.Count == 0){
|
||||
SelectedSubs = "none";
|
||||
} else{
|
||||
SelectedSubs = SelectedSubLang[0].Content.ToString();
|
||||
for (var i = 1; i < SelectedSubLang.Count; i++){
|
||||
SelectedSubs += "," + SelectedSubLang[i].Content;
|
||||
}
|
||||
}
|
||||
|
||||
if (SelectedDubLang.Count == 0){
|
||||
SelectedDubs = "none";
|
||||
} else{
|
||||
SelectedDubs = SelectedDubLang[0].Content.ToString();
|
||||
for (var i = 1; i < SelectedDubLang.Count; i++){
|
||||
SelectedDubs += "," + SelectedDubLang[i].Content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public void AddMkvMergeParam(){
|
||||
MkvMergeOptions.Add(new MuxingParam(){ ParamValue = MkvMergeOption });
|
||||
|
@ -681,13 +665,21 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
private void Changes(object? sender, NotifyCollectionChangedEventArgs e){
|
||||
|
||||
UpdateSettings();
|
||||
|
||||
var dubs = SelectedDubLang.Select(item => item.Content?.ToString());
|
||||
SelectedDubs = string.Join(", ", dubs) ?? "";
|
||||
|
||||
var subs = SelectedSubLang.Select(item => item.Content?.ToString());
|
||||
SelectedSubs = string.Join(", ", subs) ?? "";
|
||||
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(PropertyChangedEventArgs e){
|
||||
base.OnPropertyChanged(e);
|
||||
|
||||
if (e.PropertyName is nameof(SelectedSubs) or nameof(SelectedDubs) or nameof(CustomAccentColor) or nameof(ListBoxColor) or nameof(CurrentAppTheme) or nameof(UseCustomAccent) or nameof(LogMode)){
|
||||
if (e.PropertyName is nameof(SelectedDubs) or nameof(SelectedSubs) or nameof(CustomAccentColor) or nameof(ListBoxColor) or nameof(CurrentAppTheme) or nameof(UseCustomAccent) or nameof(LogMode)){
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.ComponentModel;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
xmlns:ui="clr-namespace:CRD.Utils.UI"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:history="clr-namespace:CRD.Utils.Structs.History"
|
||||
xmlns:structs="clr-namespace:CRD.Utils.Structs"
|
||||
x:DataType="vm:HistoryPageViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="CRD.Views.HistoryPageView">
|
||||
|
@ -13,7 +14,6 @@
|
|||
<UserControl.Resources>
|
||||
<ui:UiIntToVisibilityConverter x:Key="UiIntToVisibilityConverter" />
|
||||
<ui:UiSonarrIdToVisibilityConverter x:Key="UiSonarrIdToVisibilityConverter" />
|
||||
<ui:UiUpdateDownloadedHistorySeasonConverter x:Key="UiUpdateDownloadedHistorySeasonConverter" />
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
|
@ -120,8 +120,8 @@
|
|||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Left">
|
||||
<controls:SymbolIcon IsVisible="{Binding !$parent[UserControl].((vm:HistoryPageViewModel)DataContext).SortDir}" Symbol="ChevronUp" FontSize="12" Margin="0 0 10 0"/>
|
||||
<controls:SymbolIcon IsVisible="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).SortDir}" Symbol="ChevronDown" FontSize="12" Margin="0 0 10 0"/>
|
||||
<controls:SymbolIcon IsVisible="{Binding !$parent[UserControl].((vm:HistoryPageViewModel)DataContext).SortDir}" Symbol="ChevronUp" FontSize="12" Margin="0 0 10 0" />
|
||||
<controls:SymbolIcon IsVisible="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).SortDir}" Symbol="ChevronDown" FontSize="12" Margin="0 0 10 0" />
|
||||
<TextBlock Text="{Binding SortingTitle}"></TextBlock>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
@ -338,7 +338,7 @@
|
|||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Command="{Binding FetchData}" Margin="0 0 5 10">Fetch Series</Button>
|
||||
<Button Command="{Binding FetchData}" Margin="0 0 5 10">Refresh Series</Button>
|
||||
<ToggleButton x:Name="SeriesEditModeToggle" IsChecked="{Binding EditModeEnabled}" Margin="0 0 5 10">Edit</ToggleButton>
|
||||
|
||||
<Button Margin="0 0 5 10" FontStyle="Italic"
|
||||
|
@ -354,6 +354,115 @@
|
|||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="SeriesOverride" Margin="0 0 5 10" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding EditModeEnabled}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Speaker2" FontSize="18" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
MaxWidth="400"
|
||||
MaxHeight="600"
|
||||
IsOpen="{Binding IsChecked, ElementName=SeriesOverride, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=SeriesOverride}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
|
||||
<StackPanel>
|
||||
<controls:SettingsExpander Header="Language Override">
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 0 0 10">
|
||||
<TextBlock Text="Dub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedDubs}"
|
||||
VerticalAlignment="Center" />
|
||||
<Path Grid.Column="1" Data="M 0,1 L 4,4 L 8,1" Stroke="White" StrokeThickness="1"
|
||||
VerticalAlignment="Center" Margin="5,0,5,0" Stretch="Uniform" Width="8" />
|
||||
</Grid>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
IsOpen="{Binding IsChecked, ElementName=DropdownButtonDub, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=DropdownButtonDub}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="ListBoxDubsSelection" SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding DubLangList, Mode=OneWay}"
|
||||
SelectedItems="{Binding SelectedDubLang}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:StringItem}">
|
||||
<TextBlock Text="{Binding stringValue}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="Sub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonSub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedSubs}"
|
||||
VerticalAlignment="Center" />
|
||||
<Path Grid.Column="1" Data="M 0,1 L 4,4 L 8,1" Stroke="White" StrokeThickness="1"
|
||||
VerticalAlignment="Center" Margin="5,0,5,0" Stretch="Uniform" Width="8" />
|
||||
</Grid>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
IsOpen="{Binding IsChecked, ElementName=DropdownButtonSub, Mode=TwoWay}" Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=DropdownButtonSub}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding SubLangList, Mode=OneWay}"
|
||||
SelectedItems="{Binding SelectedSubLang}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:StringItem}">
|
||||
<TextBlock Text="{Binding stringValue}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
|
@ -481,7 +590,7 @@
|
|||
Command="{Binding $parent[ScrollViewer].((history:HistorySeries)DataContext).FetchData}"
|
||||
CommandParameter="{Binding SeasonId}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Fetch Season" FontSize="15" />
|
||||
<TextBlock Text="Refresh Season" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Refresh"
|
||||
|
@ -503,6 +612,117 @@
|
|||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<ToggleButton x:Name="SeasonOverride" Margin="10 0 0 0" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding $parent[ScrollViewer].((history:HistorySeries)DataContext).EditModeEnabled}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Speaker2" FontSize="18" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
MaxWidth="400"
|
||||
MaxHeight="600"
|
||||
IsOpen="{Binding IsChecked, ElementName=SeasonOverride, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=SeasonOverride}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
|
||||
<StackPanel>
|
||||
<controls:SettingsExpander Header="Language Override">
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 0 0 10">
|
||||
<TextBlock Text="Dub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="SeasonOverrideDropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedDubs}"
|
||||
VerticalAlignment="Center" />
|
||||
<Path Grid.Column="1" Data="M 0,1 L 4,4 L 8,1" Stroke="White" StrokeThickness="1"
|
||||
VerticalAlignment="Center" Margin="5,0,5,0" Stretch="Uniform" Width="8" />
|
||||
</Grid>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
IsOpen="{Binding IsChecked, ElementName=SeasonOverrideDropdownButtonDub, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=SeasonOverrideDropdownButtonDub}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="ListBoxDubsSelection" SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding DubLangList}"
|
||||
SelectedItems="{Binding SelectedDubLang}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:StringItem}">
|
||||
<TextBlock Text="{Binding stringValue}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="Sub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="SeasonOverrideDropdownButtonSub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedSubs}"
|
||||
VerticalAlignment="Center" />
|
||||
<Path Grid.Column="1" Data="M 0,1 L 4,4 L 8,1" Stroke="White" StrokeThickness="1"
|
||||
VerticalAlignment="Center" Margin="5,0,5,0" Stretch="Uniform" Width="8" />
|
||||
</Grid>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
IsOpen="{Binding IsChecked, ElementName=SeasonOverrideDropdownButtonSub, Mode=TwoWay}" Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=SeasonOverrideDropdownButtonSub}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding SubLangList}"
|
||||
SelectedItems="{Binding SelectedSubLang}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:StringItem}">
|
||||
<TextBlock Text="{Binding stringValue}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<Button Margin="10 0 0 0" FontStyle="Italic"
|
||||
IsVisible="{Binding $parent[ScrollViewer].((history:HistorySeries)DataContext).EditModeEnabled}"
|
||||
Command="{Binding $parent[ScrollViewer].((history:HistorySeries)DataContext).RemoveSeason}"
|
||||
|
|
|
@ -43,7 +43,7 @@ public partial class MainWindow : AppWindow{
|
|||
public MainWindow(){
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
TitleBar.ExtendsContentIntoTitleBar = true;
|
||||
TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex;
|
||||
|
||||
|
|
|
@ -5,10 +5,13 @@
|
|||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:vm="clr-namespace:CRD.ViewModels"
|
||||
xmlns:history="clr-namespace:CRD.Utils.Structs.History"
|
||||
xmlns:ui="clr-namespace:CRD.Utils.UI"
|
||||
xmlns:structs="clr-namespace:CRD.Utils.Structs"
|
||||
x:DataType="vm:SeriesPageViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="CRD.Views.SeriesPageView">
|
||||
|
||||
|
||||
<Grid>
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
|
@ -64,7 +67,7 @@
|
|||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Command="{Binding UpdateData}" Margin="0 0 5 10">Fetch Series</Button>
|
||||
<Button Command="{Binding UpdateData}" Margin="0 0 5 10">Refresh Series</Button>
|
||||
<ToggleButton IsChecked="{Binding EditMode}" Margin="0 0 5 10">Edit</ToggleButton>
|
||||
|
||||
<Button Margin="0 0 5 10" FontStyle="Italic"
|
||||
|
@ -78,6 +81,116 @@
|
|||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="SeriesOverride" Margin="0 0 5 10" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding EditMode}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Speaker2" FontSize="18" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
MaxWidth="400"
|
||||
MaxHeight="600"
|
||||
IsOpen="{Binding IsChecked, ElementName=SeriesOverride, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=SeriesOverride}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
|
||||
<StackPanel>
|
||||
<controls:SettingsExpander Header="Language Override">
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 0 0 10">
|
||||
<TextBlock Text="Dub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedSeries.SelectedDubs}"
|
||||
VerticalAlignment="Center" />
|
||||
<Path Grid.Column="1" Data="M 0,1 L 4,4 L 8,1" Stroke="White" StrokeThickness="1"
|
||||
VerticalAlignment="Center" Margin="5,0,5,0" Stretch="Uniform" Width="8" />
|
||||
</Grid>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
IsOpen="{Binding IsChecked, ElementName=DropdownButtonDub, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=DropdownButtonDub}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="ListBoxDubsSelection" SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding SelectedSeries.DubLangList , Mode=OneWay}"
|
||||
SelectedItems="{Binding SelectedSeries.SelectedDubLang}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:StringItem}">
|
||||
<TextBlock Text="{Binding stringValue}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="Sub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonSub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedSeries.SelectedSubs}"
|
||||
VerticalAlignment="Center" />
|
||||
<Path Grid.Column="1" Data="M 0,1 L 4,4 L 8,1" Stroke="White" StrokeThickness="1"
|
||||
VerticalAlignment="Center" Margin="5,0,5,0" Stretch="Uniform" Width="8" />
|
||||
</Grid>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
IsOpen="{Binding IsChecked, ElementName=DropdownButtonSub, Mode=TwoWay}" Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=DropdownButtonSub}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding SelectedSeries.SubLangList , Mode=OneWay}"
|
||||
SelectedItems="{Binding SelectedSeries.SelectedSubLang}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:StringItem}">
|
||||
<TextBlock Text="{Binding stringValue}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
|
@ -178,7 +291,7 @@
|
|||
Command="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).UpdateData}"
|
||||
CommandParameter="{Binding SeasonId}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Fetch Season" FontSize="15" />
|
||||
<TextBlock Text="Refresh Season" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Refresh" FontSize="18" />
|
||||
|
@ -197,6 +310,116 @@
|
|||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<ToggleButton x:Name="SeasonOverride" Margin="10 0 0 0" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).EditMode}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Speaker2" FontSize="18" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
MaxWidth="400"
|
||||
MaxHeight="600"
|
||||
IsOpen="{Binding IsChecked, ElementName=SeasonOverride, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=SeasonOverride}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
|
||||
<StackPanel>
|
||||
<controls:SettingsExpander Header="Language Override">
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 0 0 10">
|
||||
<TextBlock Text="Dub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="SeasonOverrideDropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedDubs}"
|
||||
VerticalAlignment="Center" />
|
||||
<Path Grid.Column="1" Data="M 0,1 L 4,4 L 8,1" Stroke="White" StrokeThickness="1"
|
||||
VerticalAlignment="Center" Margin="5,0,5,0" Stretch="Uniform" Width="8" />
|
||||
</Grid>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
IsOpen="{Binding IsChecked, ElementName=SeasonOverrideDropdownButtonDub, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=SeasonOverrideDropdownButtonDub}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="ListBoxDubsSelection" SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding DubLangList , Mode=OneWay}"
|
||||
SelectedItems="{Binding SelectedDubLang}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:StringItem}">
|
||||
<TextBlock Text="{Binding stringValue}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="Sub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="SeasonOverrideDropdownButtonSub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedSubs}"
|
||||
VerticalAlignment="Center" />
|
||||
<Path Grid.Column="1" Data="M 0,1 L 4,4 L 8,1" Stroke="White" StrokeThickness="1"
|
||||
VerticalAlignment="Center" Margin="5,0,5,0" Stretch="Uniform" Width="8" />
|
||||
</Grid>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
IsOpen="{Binding IsChecked, ElementName=SeasonOverrideDropdownButtonSub, Mode=TwoWay}" Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=SeasonOverrideDropdownButtonSub}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding SubLangList , Mode=OneWay}"
|
||||
SelectedItems="{Binding SelectedSubLang}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:StringItem}">
|
||||
<TextBlock Text="{Binding stringValue}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<Button Margin="10 0 0 0" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).RemoveSeason}"
|
||||
|
|
|
@ -12,8 +12,7 @@
|
|||
<Design.DataContext>
|
||||
<vm:SettingsPageViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
|
||||
|
||||
<ScrollViewer Padding="20 20 20 0">
|
||||
<StackPanel Spacing="8">
|
||||
|
||||
|
@ -51,7 +50,8 @@
|
|||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="ListBoxDubsSelection" SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding DubLangList}" SelectedItems="{Binding SelectedDubLang}">
|
||||
ItemsSource="{Binding DubLangList}"
|
||||
SelectedItems="{Binding SelectedDubLang}">
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
|
@ -333,6 +333,11 @@
|
|||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Sync Timings" Description="Does not work for all episodes but for the ones that only have a different intro">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding SyncTimings}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Additional MKVMerge Options">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
|
|
Loading…
Reference in New Issue