diff --git a/FModel/Enums.cs b/FModel/Enums.cs index 59700533..f59efb9d 100644 --- a/FModel/Enums.cs +++ b/FModel/Enums.cs @@ -114,5 +114,6 @@ public enum EBulkType Textures = 1 << 2, Meshes = 1 << 3, Skeletons = 1 << 4, - Animations = 1 << 5 + Animations = 1 << 5, + Audio = 1 << 6 } diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index 9021fa43..f70fc5af 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -103,9 +103,6 @@ - - @@ -376,6 +373,15 @@ + + + + + + + + + @@ -608,6 +614,21 @@ + + + + + + + + + + + + + + + @@ -838,19 +859,6 @@ - - - - - - - diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index e1d71bc3..b6a957fa 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -238,6 +238,19 @@ public partial class MainWindow } } + private async void OnFolderAudioClick(object sender, RoutedEventArgs e) + { + if (AssetsFolderName.SelectedItem is TreeItem folder) + { + await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.AudioFolder(cancellationToken, folder); }); + FLogger.Append(ELog.Information, () => + { + FLogger.Text("Successfully saved audio from ", Constants.WHITE); + FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.AudioDirectory, true); + }); + } + } + private void OnFavoriteDirectoryClick(object sender, RoutedEventArgs e) { if (AssetsFolderName.SelectedItem is not TreeItem folder) return; diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs index ef577f0b..66d2aec8 100644 --- a/FModel/Settings/UserSettings.cs +++ b/FModel/Settings/UserSettings.cs @@ -140,13 +140,6 @@ namespace FModel.Settings set => SetProperty(ref _lastOpenedSettingTab, value); } - private bool _isAutoOpenSounds = true; - public bool IsAutoOpenSounds - { - get => _isAutoOpenSounds; - set => SetProperty(ref _isAutoOpenSounds, value); - } - private bool _isLoggerExpanded = true; public bool IsLoggerExpanded { diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 80fdfd22..252060ca 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -583,6 +583,9 @@ public class CUE4ParseViewModel : ViewModel public void AnimationFolder(CancellationToken cancellationToken, TreeItem folder) => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Animations | EBulkType.Auto)); + public void AudioFolder(CancellationToken cancellationToken, TreeItem folder) + => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Audio | EBulkType.Auto)); + public void Extract(CancellationToken cancellationToken, GameFile entry, bool addNewTab = false, EBulkType bulk = EBulkType.None) { Log.Information("User DOUBLE-CLICKED to extract '{FullPath}'", entry.Path); @@ -594,6 +597,7 @@ public class CUE4ParseViewModel : ViewModel var updateUi = !HasFlag(bulk, EBulkType.Auto); var saveProperties = HasFlag(bulk, EBulkType.Properties); var saveTextures = HasFlag(bulk, EBulkType.Textures); + var saveAudio = HasFlag(bulk, EBulkType.Audio); switch (entry.Extension) { case "uasset": @@ -740,7 +744,7 @@ public class CUE4ParseViewModel : ViewModel var medias = WwiseProvider.ExtractBankSounds(wwise); foreach (var media in medias) { - SaveAndPlaySound(media.OutputPath, media.Extension, media.Data); + SaveAndPlaySound(media.OutputPath, media.Extension, media.Data, saveAudio); } break; @@ -755,7 +759,7 @@ public class CUE4ParseViewModel : ViewModel // todo: CSCore.MediaFoundation.MediaFoundationException The byte stream type of the given URL is unsupported. case "aif": { var data = Provider.SaveAsset(entry); - SaveAndPlaySound(entry.PathWithoutExtension, entry.Extension, data); + SaveAndPlaySound(entry.PathWithoutExtension, entry.Extension, data, saveAudio); break; } @@ -870,6 +874,7 @@ public class CUE4ParseViewModel : ViewModel var isNone = bulk == EBulkType.None; var updateUi = !HasFlag(bulk, EBulkType.Auto); var saveTextures = HasFlag(bulk, EBulkType.Textures); + var saveAudio = HasFlag(bulk, EBulkType.Audio); var pointer = new FPackageIndex(pkg, index + 1).ResolvedObject; if (pointer?.Object is null) return false; @@ -940,37 +945,37 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.AddImage(sourceFile.SubstringAfterLast('/'), false, bitmap, false, updateUi); return false; } - case UAkAudioEvent when isNone && pointer.Object.Value is UAkAudioEvent audioEvent: + case UAkAudioEvent when (isNone || saveAudio) && pointer.Object.Value is UAkAudioEvent audioEvent: { var extractedSounds = WwiseProvider.ExtractAudioEventSounds(audioEvent); foreach (var sound in extractedSounds) { - SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data); + SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio); } return false; } - case UFMODEvent when isNone && pointer.Object.Value is UFMODEvent fmodEvent: + case UFMODEvent when (isNone || saveAudio) && pointer.Object.Value is UFMODEvent fmodEvent: { var extractedSounds = FmodProvider.ExtractEventSounds(fmodEvent); var directory = Path.GetDirectoryName(fmodEvent.Owner?.Name) ?? "/FMOD/Desktop/"; foreach (var sound in extractedSounds) { - SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data); + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); } return false; } - case UFMODBank when isNone && pointer.Object.Value is UFMODBank fmodBank: + case UFMODBank when (isNone || saveAudio) && pointer.Object.Value is UFMODBank fmodBank: { var extractedSounds = FmodProvider.ExtractBankSounds(fmodBank); var directory = Path.GetDirectoryName(fmodBank.Owner?.Name) ?? "/FMOD/Desktop/"; foreach (var sound in extractedSounds) { - SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data); + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); } return false; } - case UAkMediaAssetData when isNone: - case USoundWave when isNone: + case UAkMediaAssetData when isNone || saveAudio: + case USoundWave when isNone || saveAudio: { var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed; pointer.Object.Value.Decode(shouldDecompress, out var audioFormat, out var data); @@ -981,7 +986,7 @@ public class CUE4ParseViewModel : ViewModel return false; } - SaveAndPlaySound(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/'), audioFormat, data); + SaveAndPlaySound(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/'), audioFormat, data, saveAudio); return false; } case UWorld when isNone && UserSettings.Default.PreviewWorlds: @@ -1102,13 +1107,13 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.SetDocumentText(cpp, false, false); } - private void SaveAndPlaySound(string fullPath, string ext, byte[] data) + private void SaveAndPlaySound(string fullPath, string ext, byte[] data, bool isBulk) { if (fullPath.StartsWith('/')) fullPath = fullPath[1..]; var savedAudioPath = Path.Combine(UserSettings.Default.AudioDirectory, UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLowerInvariant()}"; - if (!UserSettings.Default.IsAutoOpenSounds) + if (isBulk) { Directory.CreateDirectory(savedAudioPath.SubstringBeforeLast('/')); using var stream = new FileStream(savedAudioPath, FileMode.Create, FileAccess.Write); diff --git a/FModel/ViewModels/Commands/RightClickMenuCommand.cs b/FModel/ViewModels/Commands/RightClickMenuCommand.cs index fdd2fb6b..b27c388f 100644 --- a/FModel/ViewModels/Commands/RightClickMenuCommand.cs +++ b/FModel/ViewModels/Commands/RightClickMenuCommand.cs @@ -92,6 +92,14 @@ public class RightClickMenuCommand : ViewModelCommand contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Animations | updateUi); } break; + case "Assets_Save_Audio": + foreach (var entry in entries) + { + Thread.Yield(); + cancellationToken.ThrowIfCancellationRequested(); + contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Audio | updateUi); + } + break; } }); } diff --git a/FModel/ViewModels/Commands/TabCommand.cs b/FModel/ViewModels/Commands/TabCommand.cs index a7911782..86a83f10 100644 --- a/FModel/ViewModels/Commands/TabCommand.cs +++ b/FModel/ViewModels/Commands/TabCommand.cs @@ -1,4 +1,4 @@ -using System.Windows; +using System.Windows; using AdonisUI.Controls; using FModel.Framework; using FModel.Services; @@ -58,6 +58,12 @@ public class TabCommand : ViewModelCommand _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Animations); }); break; + case "Asset_Save_Audio": + await _threadWorkerView.Begin(cancellationToken => + { + _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Audio); + }); + break; case "Open_Properties": if (tabViewModel.Header == "New Tab" || tabViewModel.Document == null) return; Helper.OpenWindow(tabViewModel.Header + " (Properties)", () => diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml index fd2eea5f..6957f118 100644 --- a/FModel/Views/Resources/Resources.xaml +++ b/FModel/Views/Resources/Resources.xaml @@ -909,6 +909,15 @@ + + + + + + + + + diff --git a/FModel/Views/SearchView.xaml b/FModel/Views/SearchView.xaml index 5bfacacc..317a8e2a 100644 --- a/FModel/Views/SearchView.xaml +++ b/FModel/Views/SearchView.xaml @@ -1,4 +1,4 @@ - + + + + + + + + + + + + + + +