From 12d950382afc5dadfcf58166335b38b4e262d4f0 Mon Sep 17 00:00:00 2001 From: Jonko <69772986+jonko0493@users.noreply.github.com> Date: Sat, 25 Jan 2025 05:37:31 -0800 Subject: [PATCH] Update voiced line editor to account for new subtitle features (#452) --- src/SerialLoops.Lib/Items/VoicedLineItem.cs | 24 +++-- src/SerialLoops.Lib/SerialLoops.Lib.csproj | 4 +- src/SerialLoops/Assets/Strings.Designer.cs | 63 ++++++++++++ src/SerialLoops/Assets/Strings.resx | 21 ++++ src/SerialLoops/SerialLoops.csproj | 4 +- .../Editors/VoicedLineEditorViewModel.cs | 98 +++++++++++++++++-- .../Views/Editors/VoicedLineEditorView.axaml | 20 +++- .../SerialLoops.Tests.Headless.csproj | 6 +- .../SerialLoops.Tests.Shared.csproj | 2 +- 9 files changed, 215 insertions(+), 27 deletions(-) diff --git a/src/SerialLoops.Lib/Items/VoicedLineItem.cs b/src/SerialLoops.Lib/Items/VoicedLineItem.cs index ba68be6b..916efaeb 100644 --- a/src/SerialLoops.Lib/Items/VoicedLineItem.cs +++ b/src/SerialLoops.Lib/Items/VoicedLineItem.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using HaruhiChokuretsuLib.Archive.Event; using HaruhiChokuretsuLib.Audio.ADX; using HaruhiChokuretsuLib.Util; using NAudio.Flac; @@ -24,7 +25,7 @@ public VoicedLineItem(string voiceFile, int index, Project project) : base(Path. _vceFile = voiceFile; Index = index; } - + public IWaveProvider GetWaveProvider(ILogger log, bool loop = false) { byte[] adxBytes = []; @@ -61,7 +62,7 @@ public IWaveProvider GetWaveProvider(ILogger log, bool loop = false) return new AdxWaveProvider(decoder); } - public void Replace(string audioFile, string baseDirectory, string iterativeDirectory, string vceCachedFile, ILogger log) + public void Replace(string audioFile, string baseDirectory, string iterativeDirectory, string vceCachedFile, ILogger log, VoiceMapFile.VoiceMapEntry vceMapEntry = null) { // The MP3 decoder is able to create wave files but for whatever reason messes with the ADX encoder // So we just convert to WAV AOT @@ -78,18 +79,21 @@ public void Replace(string audioFile, string baseDirectory, string iterativeDire WaveFileWriter.CreateWaveFile(vceCachedFile, vorbisReader.ToSampleProvider().ToWaveProvider16()); audioFile = vceCachedFile; } + using WaveStream audio = Path.GetExtension(audioFile).ToLower() switch { ".wav" => new WaveFileReader(audioFile), ".flac" => new FlacReader(audioFile), _ => null, }; + if (audio is null) { log.LogError("Invalid audio file selected."); log.LogWarning(audioFile); return; } + if (audio.WaveFormat.Channels > 1 || audio.WaveFormat.SampleRate > SoundItem.MAX_SAMPLERATE) { string newAudioFile = ""; @@ -115,19 +119,25 @@ public void Replace(string audioFile, string baseDirectory, string iterativeDire } } - log.Log($"Encoding audio to AHX..."); - audioFile = newAudioFile; + log.Log("Encoding audio to AHX..."); AdxUtil.EncodeWav(newAudioFile, Path.Combine(baseDirectory, VoiceFile), true); } else { - log.Log($"Encoding audio to AHX..."); + log.Log("Encoding audio to AHX..."); AdxUtil.EncodeAudio(audio, Path.Combine(baseDirectory, VoiceFile), true); } File.Copy(Path.Combine(baseDirectory, VoiceFile), Path.Combine(iterativeDirectory, VoiceFile), true); + + // Adjust subtitle length to new voice item length + if (vceMapEntry is not null) + { + vceMapEntry.Timer = (int)(audio.TotalTime.TotalSeconds * 180 + 30); + UnsavedChanges = true; + } } public override void Refresh(Project project, ILogger log) { - } -} \ No newline at end of file + } +} diff --git a/src/SerialLoops.Lib/SerialLoops.Lib.csproj b/src/SerialLoops.Lib/SerialLoops.Lib.csproj index 15cb300d..b7dc43f7 100644 --- a/src/SerialLoops.Lib/SerialLoops.Lib.csproj +++ b/src/SerialLoops.Lib/SerialLoops.Lib.csproj @@ -12,14 +12,14 @@ - + - + diff --git a/src/SerialLoops/Assets/Strings.Designer.cs b/src/SerialLoops/Assets/Strings.Designer.cs index f5f13919..284314ec 100644 --- a/src/SerialLoops/Assets/Strings.Designer.cs +++ b/src/SerialLoops/Assets/Strings.Designer.cs @@ -2915,6 +2915,15 @@ public static string FADE_TO_CENTER { } } + /// + /// Looks up a localized string similar to Faded Gray. + /// + public static string FADED_GRAY { + get { + return ResourceManager.GetString("FADED_GRAY", resourceCulture); + } + } + /// /// Looks up a localized string similar to Failed attempting to cache audio file. /// @@ -3626,6 +3635,15 @@ public static string Flags { } } + /// + /// Looks up a localized string similar to Force Drop Shadow. + /// + public static string Force_Drop_Shadow { + get { + return ResourceManager.GetString("Force Drop Shadow", resourceCulture); + } + } + /// /// Looks up a localized string similar to Frames. /// @@ -3770,6 +3788,15 @@ public static string GIF_file { } } + /// + /// Looks up a localized string similar to Gray. + /// + public static string GRAY { + get { + return ResourceManager.GetString("GRAY", resourceCulture); + } + } + /// /// Looks up a localized string similar to Grayscale. /// @@ -4330,6 +4357,15 @@ public static string Language_Template { } } + /// + /// Looks up a localized string similar to Lavender. + /// + public static string LAVENDER { + get { + return ResourceManager.GetString("LAVENDER", resourceCulture); + } + } + /// /// Looks up a localized string similar to Layout. /// @@ -5124,6 +5160,15 @@ public static string Off { } } + /// + /// Looks up a localized string similar to Off White. + /// + public static string OFF_WHITE { + get { + return ResourceManager.GetString("OFF_WHITE", resourceCulture); + } + } + /// /// Looks up a localized string similar to On. /// @@ -5844,6 +5889,15 @@ public static string Recents { } } + /// + /// Looks up a localized string similar to Red. + /// + public static string RED { + get { + return ResourceManager.GetString("RED", resourceCulture); + } + } + /// /// Looks up a localized string similar to References to {0}. /// @@ -8568,6 +8622,15 @@ public static string XDelta_patch { } } + /// + /// Looks up a localized string similar to Yellow. + /// + public static string YELLOW { + get { + return ResourceManager.GetString("YELLOW", resourceCulture); + } + } + /// /// Looks up a localized string similar to Yes. /// diff --git a/src/SerialLoops/Assets/Strings.resx b/src/SerialLoops/Assets/Strings.resx index 0ca2232a..dc879eda 100644 --- a/src/SerialLoops/Assets/Strings.resx +++ b/src/SerialLoops/Assets/Strings.resx @@ -3086,4 +3086,25 @@ This action is irreversible. Import Item Image + + Force Drop Shadow + + + Yellow + + + Off White + + + Gray + + + Lavender + + + Red + + + Faded Gray + diff --git a/src/SerialLoops/SerialLoops.csproj b/src/SerialLoops/SerialLoops.csproj index 3568c9a8..8daa9f95 100644 --- a/src/SerialLoops/SerialLoops.csproj +++ b/src/SerialLoops/SerialLoops.csproj @@ -73,13 +73,13 @@ - + - + diff --git a/src/SerialLoops/ViewModels/Editors/VoicedLineEditorViewModel.cs b/src/SerialLoops/ViewModels/Editors/VoicedLineEditorViewModel.cs index 7d359b60..8b751658 100644 --- a/src/SerialLoops/ViewModels/Editors/VoicedLineEditorViewModel.cs +++ b/src/SerialLoops/ViewModels/Editors/VoicedLineEditorViewModel.cs @@ -1,9 +1,12 @@ -using System.IO; +using System; +using System.Collections.ObjectModel; +using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows.Input; using Avalonia.Platform.Storage; using HaruhiChokuretsuLib.Archive.Event; +using HaruhiChokuretsuLib.Audio.ADX; using HaruhiChokuretsuLib.Util; using NAudio.Wave; using ReactiveUI; @@ -38,6 +41,23 @@ public class VoicedLineEditorViewModel : EditorViewModel [Reactive] public SKBitmap SubtitlesPreview { get; set; } = new(256, 384); + public ObservableCollection SubtitleColors { get; } + private LocalizedDialogueColor _subtitleColor; + public LocalizedDialogueColor SubtitleColor + { + get => _subtitleColor; + set + { + this.RaiseAndSetIfChanged(ref _subtitleColor, value); + if (_voiceMapEntry is not null) + { + _voiceMapEntry.Color = _subtitleColor.Color; + UpdatePreview(); + Description.UnsavedChanges = true; + } + } + } + private DsScreen _subtitleScreen; public DsScreen SubtitleScreen { @@ -47,13 +67,40 @@ public DsScreen SubtitleScreen this.RaiseAndSetIfChanged(ref _subtitleScreen, value); if (_voiceMapEntry is not null) { - _voiceMapEntry.TargetScreen = SubtitleScreen == DsScreen.BOTTOM ? VoiceMapEntry.Screen.BOTTOM : VoiceMapEntry.Screen.TOP; + _voiceMapEntry.TargetScreen = SubtitleScreen == DsScreen.BOTTOM ? VoiceMapEntry.Screen.BOTTOM : _forceDropShadow ? VoiceMapEntry.Screen.TOP_FORCE_SHADOW : VoiceMapEntry.Screen.TOP; UpdatePreview(); Description.UnsavedChanges = true; } } } + private bool _forceDropShadow; + public bool ForceDropShadow + { + get => _forceDropShadow; + set + { + this.RaiseAndSetIfChanged(ref _forceDropShadow, value); + if (_voiceMapEntry is not null) + { + if ((_voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.TOP || + _voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.TOP_FORCE_SHADOW) && _forceDropShadow) + { + _voiceMapEntry.TargetScreen = VoiceMapEntry.Screen.TOP_FORCE_SHADOW; + UpdatePreview(); + Description.UnsavedChanges = true; + } + else if (_voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.TOP || + _voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.TOP_FORCE_SHADOW) + { + _voiceMapEntry.TargetScreen = VoiceMapEntry.Screen.TOP; + UpdatePreview(); + Description.UnsavedChanges = true; + } + } + } + } + private VoiceMapEntry.YPosition _yPos; public bool TopY @@ -121,12 +168,13 @@ public string Subtitle this.RaiseAndSetIfChanged(ref _subtitle, _project.LangCode.Equals("ja") ? value : value.GetOriginalString(_project)); if (_voiceMapEntry is null) { + AdxHeader header = new(File.ReadAllBytes(Path.Combine(_project.IterativeDirectory, _vce.VoiceFile)), _log); _project.VoiceMap.VoiceMapEntries.Add(new() { VoiceFileName = Path.GetFileNameWithoutExtension(_vce.VoiceFile), Color = DialogueColor.WHITE, TargetScreen = SubtitleScreen == DsScreen.BOTTOM ? VoiceMapEntry.Screen.BOTTOM : VoiceMapEntry.Screen.TOP, - Timer = 350, + Timer = (int)((double)header.TotalSamples / header.SampleRate * 180 + 30), }); _project.VoiceMap.VoiceMapEntries[^1].SetSubtitle(_subtitle, _project.FontReplacement); _project.VoiceMap.VoiceMapEntries[^1].YPos = _yPos; @@ -160,8 +208,11 @@ public VoicedLineEditorViewModel(VoicedLineItem item, MainWindowViewModel window _voiceMapEntry = _project.VoiceMap.VoiceMapEntries.FirstOrDefault(v => v.VoiceFileName.Equals(Path.GetFileNameWithoutExtension(_vce.VoiceFile))); if (_voiceMapEntry is not null) { + SubtitleColors = new(Enum.GetValues().Select(c => new LocalizedDialogueColor(c))); + _subtitleColor = SubtitleColors.First(c => c.Color == _voiceMapEntry.Color); _subtitle = _voiceMapEntry.Subtitle; _subtitleScreen = _voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.BOTTOM ? DsScreen.BOTTOM : DsScreen.TOP; + _forceDropShadow = _voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.TOP_FORCE_SHADOW; _yPos = _voiceMapEntry.YPos; UpdatePreview(); } @@ -176,8 +227,10 @@ private async Task Replace() { LoopyProgressTracker tracker = new(); VcePlayer.Stop(); - await new ProgressDialog(() => _vce.Replace(openFile.Path.LocalPath, _project.BaseDirectory, _project.IterativeDirectory, Path.Combine(_project.Config.CachesDirectory, "vce", $"{_vce.Name}.wav"), _log), + await new ProgressDialog(() => _vce.Replace(openFile.Path.LocalPath, _project.BaseDirectory, _project.IterativeDirectory, Path.Combine(_project.Config.CachesDirectory, "vce", $"{_vce.Name}.wav"), _log, + _voiceMapEntry), () => { }, tracker, Strings.Replace_voiced_line).ShowDialog(Window.Window); + VcePlayer.Stop(); } } @@ -192,7 +245,16 @@ private async Task Export() private void Restore() { - + VcePlayer.Stop(); + File.Copy(Path.Combine(_project.BaseDirectory, "original", "vce", Path.GetFileName(_vce.VoiceFile)), Path.Combine(_project.BaseDirectory, _vce.VoiceFile), true); + File.Copy(Path.Combine(_project.IterativeDirectory, "original", "vce", Path.GetFileName(_vce.VoiceFile)), Path.Combine(_project.IterativeDirectory, _vce.VoiceFile), true); + AdxHeader header = new(File.ReadAllBytes(Path.Combine(_project.IterativeDirectory, _vce.VoiceFile)), _log); + if (_voiceMapEntry is not null) + { + _voiceMapEntry.Timer = (int)((double)header.TotalSamples / header.SampleRate * 180 + 30); + _vce.UnsavedChanges = true; + } + VcePlayer.Stop(); } private void UpdatePreview() @@ -202,8 +264,7 @@ private void UpdatePreview() canvas.DrawColor(SKColors.DarkGray); canvas.DrawLine(new() { X = 0, Y = 192 }, new() { X = 256, Y = 192 }, DialogueScriptParameter.Paint00); - bool bottomScreen = _voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.BOTTOM; - if (bottomScreen) + if (_voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.BOTTOM) { for (int i = 0; i <= 1; i++) { @@ -212,11 +273,22 @@ private void UpdatePreview() DialogueScriptParameter.Paint07, _project, i + _voiceMapEntry.X, - 1 + _voiceMapEntry.Y + (bottomScreen ? 192 : 0), + 1 + _voiceMapEntry.Y + 192, false ); } } + else if (_voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.TOP_FORCE_SHADOW) + { + canvas.DrawHaroohieText( + _subtitle, + DialogueScriptParameter.Paint07, + _project, + 1 + _voiceMapEntry.X, + 1 + _voiceMapEntry.Y, + false + ); + } canvas.DrawHaroohieText( _subtitle, @@ -234,10 +306,18 @@ private void UpdatePreview() }, _project, _voiceMapEntry.X, - _voiceMapEntry.Y + (bottomScreen ? 192 : 0), + _voiceMapEntry.Y + (_voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.BOTTOM ? 192 : 0), false ); canvas.Flush(); } } + +public class LocalizedDialogueColor(DialogueColor color) : ReactiveObject +{ + [Reactive] + public DialogueColor Color { get; set; } = color; + + public string DisplayText => Strings.ResourceManager.GetString(Color.ToString()); +} diff --git a/src/SerialLoops/Views/Editors/VoicedLineEditorView.axaml b/src/SerialLoops/Views/Editors/VoicedLineEditorView.axaml index d0db3e14..830c25fe 100644 --- a/src/SerialLoops/Views/Editors/VoicedLineEditorView.axaml +++ b/src/SerialLoops/Views/Editors/VoicedLineEditorView.axaml @@ -13,9 +13,9 @@ -