mirror of
https://github.com/AssetRipper/AssetRipper.git
synced 2025-12-11 20:15:29 +01:00
parent
3c33256957
commit
be74e2cee9
@ -1,5 +1,4 @@
|
||||
using AssetRipper.Import.Logging;
|
||||
using AssetRipper.SourceGenerated.Classes.ClassID_83;
|
||||
using AssetRipper.SourceGenerated.Classes.ClassID_83;
|
||||
using AssetRipper.SourceGenerated.Extensions;
|
||||
using Fmod5Sharp;
|
||||
using Fmod5Sharp.FmodTypes;
|
||||
@ -9,174 +8,71 @@ namespace AssetRipper.Export.UnityProjects.Audio
|
||||
{
|
||||
public static class AudioClipDecoder
|
||||
{
|
||||
public static bool CanDecode(IAudioClip audioClip)
|
||||
public static bool TryDecode(
|
||||
IAudioClip audioClip,
|
||||
[NotNullWhen(true)] out byte[]? decodedData,
|
||||
[NotNullWhen(true)] out string? fileExtension,
|
||||
[NotNullWhen(false)] out string? message)
|
||||
{
|
||||
byte[] rawData = audioClip.GetAudioData();
|
||||
if (rawData.Length == 0)
|
||||
{
|
||||
Logger.Info(LogCategory.Export, $"Can't decode audio clip '{audioClip.Name}' with default decoder because its audio data could not be found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryGetAudioType(rawData, out FmodAudioType audioType))
|
||||
{
|
||||
if (FmodAudioTypeExtensions.IsSupported(audioType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info(LogCategory.Export, $"Can't decode audio clip '{audioClip.Name}' with default decoder because it's '{audioType}' encoded.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info(LogCategory.Export, $"Can't decode audio clip '{audioClip.Name}' with default decoder because its {nameof(audioType)} could not be determined.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetDecodedAudioClipData(IAudioClip audioClip, [NotNullWhen(true)] out byte[]? decodedData, [NotNullWhen(true)] out string? fileExtension)
|
||||
{
|
||||
return TryGetDecodedAudioClipData(audioClip.GetAudioData(), out decodedData, out fileExtension);
|
||||
}
|
||||
public static bool TryGetDecodedAudioClipData(byte[] rawData, [NotNullWhen(true)] out byte[]? decodedData, [NotNullWhen(true)] out string? fileExtension)
|
||||
{
|
||||
decodedData = null;
|
||||
fileExtension = null;
|
||||
|
||||
if (rawData.Length == 0)
|
||||
{
|
||||
decodedData = null;
|
||||
fileExtension = null;
|
||||
message = $"Can't decode audio clip '{audioClip.Name}' with default decoder because its audio data could not be found.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FsbLoader.TryLoadFsbFromByteArray(rawData, out FmodSoundBank? fsbData))
|
||||
if (audioClip.Has_Type())
|
||||
{
|
||||
fileExtension = audioClip.GetSoundType().ToRawExtension();
|
||||
decodedData = rawData;
|
||||
message = null;
|
||||
return true;
|
||||
}
|
||||
else if (CheckMagic(rawData, "FSB5"u8) && FsbLoader.TryLoadFsbFromByteArray(rawData, out FmodSoundBank? fsbData))
|
||||
{
|
||||
FmodAudioType audioType = fsbData!.Header.AudioType;
|
||||
try
|
||||
{
|
||||
if (audioType.IsSupported() && fsbData.Samples.Single().RebuildAsStandardFileFormat(out decodedData, out fileExtension))
|
||||
{
|
||||
message = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
decodedData = null;
|
||||
fileExtension = null;
|
||||
message = $"Can't decode audio clip '{audioClip.Name}' with default decoder because it's '{audioType}' encoded.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(LogCategory.Export, $"Failed to convert audio ({Enum.GetName(audioType)})", ex);
|
||||
decodedData = null;
|
||||
fileExtension = null;
|
||||
message = $"Failed to convert audio ({Enum.GetName(audioType)})\n{ex}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error(LogCategory.Export, $"Failed to convert audio");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes WAV data from an AudioClip
|
||||
/// </summary>
|
||||
/// <param name="audioClip">The audio clip to extract the data from</param>
|
||||
/// <param name="decodedData">The decoded data in the wav audio format</param>
|
||||
/// <returns>True if the audio could be exported in the wav format</returns>
|
||||
public static bool TryGetDecodedWavData(IAudioClip audioClip, [NotNullWhen(true)] out byte[]? decodedData)
|
||||
{
|
||||
return TryGetDecodedWavData(audioClip.GetAudioData(), out decodedData);
|
||||
}
|
||||
/// <summary>
|
||||
/// Decodes WAV data from FSB data
|
||||
/// </summary>
|
||||
/// <param name="fsbData">The data from an FSB file</param>
|
||||
/// <param name="decodedData">The decoded data in the wav audio format</param>
|
||||
/// <returns>True if the audio could be exported in the wav format</returns>
|
||||
public static bool TryGetDecodedWavData(byte[] fsbData, [NotNullWhen(true)] out byte[]? decodedData)
|
||||
{
|
||||
if (TryGetDecodedAudioClipData(fsbData, out decodedData, out string? fileExtension))
|
||||
{
|
||||
if (fileExtension == "ogg")
|
||||
{
|
||||
decodedData = AudioConverter.OggToWav(decodedData);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fileExtension == "wav";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
decodedData = null;
|
||||
fileExtension = null;
|
||||
message = "Failed to convert audio";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetAudioType(byte[] rawData, out FmodAudioType type)
|
||||
private static bool CheckMagic(byte[] data, ReadOnlySpan<byte> magic)
|
||||
{
|
||||
if (rawData.Length == 0)
|
||||
if (data.Length < magic.Length)
|
||||
{
|
||||
type = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
using MemoryStream input = new MemoryStream(rawData);
|
||||
using BinaryReader reader = new BinaryReader(input);
|
||||
try
|
||||
{
|
||||
if (CheckMagic(reader))
|
||||
{
|
||||
FmodAudioHeader header = new FmodAudioHeader(reader);
|
||||
type = header.AudioType;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
type = default;
|
||||
Logger.Info(LogCategory.Export, "Audio clip data is not an FSB5 binary.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning($"An exception was thrown while attempting to determine the audio type:{Environment.NewLine}{ex.Message}");
|
||||
type = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFileExtension(IAudioClip audioClip) => GetFileExtension(audioClip.GetAudioData());
|
||||
public static string GetFileExtension(byte[] rawData)
|
||||
{
|
||||
if (TryGetAudioType(rawData, out FmodAudioType audioType))
|
||||
{
|
||||
return audioType.FileExtension() ?? throw new Exception($"No extension for {audioType}");
|
||||
}
|
||||
else if (rawData.Length == 0)
|
||||
{
|
||||
throw new Exception($"{nameof(rawData)} was empty.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"{nameof(FmodAudioType)} could not be determined.");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CheckMagic(BinaryReader reader)
|
||||
{
|
||||
const byte magic0 = (byte)'F';
|
||||
const byte magic1 = (byte)'S';
|
||||
const byte magic2 = (byte)'B';
|
||||
const byte magic3 = (byte)'5';
|
||||
|
||||
long initialPosition = reader.BaseStream.Position;
|
||||
|
||||
bool isValid = reader.ReadByte() == magic0 && reader.ReadByte() == magic1 && reader.ReadByte() == magic2 && reader.ReadByte() == magic3;
|
||||
reader.BaseStream.Position = initialPosition;
|
||||
return isValid;
|
||||
return data.AsSpan(0, magic.Length).SequenceEqual(magic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,32 +1,36 @@
|
||||
using AssetRipper.Assets;
|
||||
using AssetRipper.Export.UnityProjects.Configuration;
|
||||
using AssetRipper.Assets.Export;
|
||||
using AssetRipper.SourceGenerated.Classes.ClassID_83;
|
||||
|
||||
namespace AssetRipper.Export.UnityProjects.Audio
|
||||
{
|
||||
public sealed class AudioClipExportCollection : AudioExportCollection
|
||||
{
|
||||
public AudioClipExportCollection(AudioClipExporter assetExporter, IAudioClip asset) : base(assetExporter, asset)
|
||||
private byte[]? data;
|
||||
private readonly string fileExtension;
|
||||
public AudioClipExportCollection(AudioClipExporter assetExporter, IAudioClip asset, byte[] data, string fileExtension) : base(assetExporter, asset)
|
||||
{
|
||||
this.data = data;
|
||||
this.fileExtension = fileExtension;
|
||||
}
|
||||
|
||||
protected override bool ExportInner(IExportContainer container, string filePath, string dirPath)
|
||||
{
|
||||
if (data is null or { Length: 0 })
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.IO.File.WriteAllBytes(filePath, data);
|
||||
data = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetExportExtension(IUnityObjectBase asset)
|
||||
{
|
||||
string defaultExtension = AudioClipDecoder.GetFileExtension((IAudioClip)asset);
|
||||
if (IsWavExtension((AudioClipExporter)AssetExporter, defaultExtension))
|
||||
{
|
||||
return "wav";
|
||||
}
|
||||
else
|
||||
{
|
||||
return defaultExtension;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsWavExtension(AudioClipExporter assetExporter, string defaultExtension)
|
||||
{
|
||||
return assetExporter.AudioFormat == AudioExportFormat.PreferWav
|
||||
&& defaultExtension == "ogg";
|
||||
return fileExtension;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
using AssetRipper.Assets;
|
||||
using AssetRipper.Assets.Export;
|
||||
using AssetRipper.Export.UnityProjects.Configuration;
|
||||
using AssetRipper.Import.Logging;
|
||||
using AssetRipper.SourceGenerated.Classes.ClassID_83;
|
||||
using AssetRipper.SourceGenerated.Extensions;
|
||||
|
||||
namespace AssetRipper.Export.UnityProjects.Audio
|
||||
{
|
||||
@ -19,37 +19,34 @@ namespace AssetRipper.Export.UnityProjects.Audio
|
||||
|
||||
public override bool TryCreateCollection(IUnityObjectBase asset, [NotNullWhen(true)] out IExportCollection? exportCollection)
|
||||
{
|
||||
if (asset is IAudioClip audio && AudioClipDecoder.CanDecode(audio))
|
||||
if (asset is IAudioClip audio)
|
||||
{
|
||||
exportCollection = new AudioClipExportCollection(this, audio);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
exportCollection = null;
|
||||
return false;
|
||||
if (AudioClipDecoder.TryDecode(audio, out byte[]? decodedData, out string? fileExtension, out string? message))
|
||||
{
|
||||
if (AudioFormat == AudioExportFormat.PreferWav && fileExtension == "ogg")
|
||||
{
|
||||
exportCollection = new AudioClipExportCollection(this, audio, AudioConverter.OggToWav(decodedData), "wav");
|
||||
}
|
||||
else
|
||||
{
|
||||
exportCollection = new AudioClipExportCollection(this, audio, decodedData, fileExtension);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error(LogCategory.Export, message);
|
||||
}
|
||||
}
|
||||
|
||||
exportCollection = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Export(IExportContainer container, IUnityObjectBase asset, string path)
|
||||
{
|
||||
if (!AudioClipDecoder.TryGetDecodedAudioClipData((IAudioClip)asset, out byte[]? decodedData, out string? fileExtension))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AudioFormat == AudioExportFormat.PreferWav && fileExtension == "ogg")
|
||||
{
|
||||
decodedData = AudioConverter.OggToWav(decodedData);
|
||||
}
|
||||
|
||||
if (decodedData.IsNullOrEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
File.WriteAllBytes(path, decodedData);
|
||||
return true;
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ internal sealed class AudioTab : HtmlTab
|
||||
|
||||
private static string TryDecode(IUnityObjectBase asset)
|
||||
{
|
||||
if (asset is IAudioClip clip && AudioClipDecoder.TryGetDecodedAudioClipData(clip, out byte[]? decodedAudioData, out string? extension))
|
||||
if (asset is IAudioClip clip && AudioClipDecoder.TryDecode(clip, out byte[]? decodedAudioData, out string? extension, out _))
|
||||
{
|
||||
return $"data:audio/{extension};base64,{Convert.ToBase64String(decodedAudioData, Base64FormattingOptions.None)}";
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user