using UnityEditor;
using UnityEngine;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
///
/// 音频GUID转移工具 - 将MP3/WAV文件的GUID转移到OGG文件
///
/// 此脚本解决了Unity Timeline中音频资源格式转换的问题:
/// 当需要将Timeline中的WAV/MP3音频替换为OGG格式时,直接替换文件会导致引用丢失。
/// 本工具通过转移GUID,确保Timeline中的引用保持不变。
///
public static class AudioGUIDTransfer
{
// 定义我们关心的原始音频扩展名
private static readonly HashSet OriginalAudioExtensions = new HashSet
{
".mp3",
".wav"
};
// 定义目标音频扩展名
private const string TargetAudioExtension = ".ogg";
// 在Unity菜单栏的 "Assets" 下添加一个菜单项
[MenuItem("Assets/AudioFormatTransfer")]
public static void TransferGUIDsInSelectedFolder()
{
// 获取当前在Project窗口中选中的对象
Object selectedObject = Selection.activeObject;
// --- 输入验证 ---
if (selectedObject == null)
{
EditorUtility.DisplayDialog("错误", "请在Project窗口中选择一个包含音频文件的文件夹。", "确定");
return;
}
string selectedPath = AssetDatabase.GetAssetPath(selectedObject);
if (!AssetDatabase.IsValidFolder(selectedPath))
{
EditorUtility.DisplayDialog("错误", "请选择一个有效的文件夹。", "确定");
return;
}
// --- 确认对话框(对于破坏性操作至关重要) ---
if (!EditorUtility.DisplayDialog("确认操作",
$"您确定要将文件夹 '{selectedPath}' 中所有MP3/WAV文件的GUID转移到对应的同名OGG文件,并删除原始文件吗?\n\n此操作不可逆,请务必提前备份您的项目!",
"是", "否"))
{
return; // 用户取消了操作
}
// --- 核心处理逻辑 ---
ProcessFolder(selectedPath);
// --- 完成消息 ---
EditorUtility.DisplayDialog("完成", "MP3/WAV GUID转移到OGG并删除原始文件的操作已完成。请检查Console窗口获取详细日志。", "确定");
}
///
/// 处理给定文件夹,将MP3/WAV文件的GUID转移到对应的OGG文件,并删除原始文件。
///
/// 要处理的文件夹的完整路径。
private static void ProcessFolder(string folderPath)
{
// 获取选中文件夹中所有文件(仅限顶层目录,不包括子文件夹)
string[] allFilesInFolder = Directory.GetFiles(folderPath, "*.*", SearchOption.TopDirectoryOnly);
// 按基础名称(不含扩展名)对文件进行分组
Dictionary> fileGroups = new Dictionary>();
foreach (string filePath in allFilesInFolder)
{
// 跳过.meta文件,因为我们只关心实际资源文件
if (filePath.EndsWith(".meta")) continue;
string fileName = Path.GetFileName(filePath);
string baseName = GetBaseNameWithoutExtensions(fileName);
if (!fileGroups.ContainsKey(baseName))
{
fileGroups.Add(baseName, new List());
}
fileGroups[baseName].Add(filePath);
}
int processedCount = 0;
int skippedCount = 0;
// 遍历每个文件组
foreach (var group in fileGroups)
{
string baseName = group.Key;
List filesInGroup = group.Value;
// 尝试找到一个原始文件(MP3/WAV)和一个OGG文件
string originalFilePath = null;
string oggFilePath = null;
foreach (string filePath in filesInGroup)
{
string ext = Path.GetExtension(filePath).ToLower();
if (ext == TargetAudioExtension)
{
if (oggFilePath != null)
{
Debug.LogWarning($"在 '{folderPath}' 文件夹中发现多个同名OGG文件 '{baseName}{TargetAudioExtension}'。跳过此组文件。");
oggFilePath = null;
break;
}
oggFilePath = filePath;
}
else if (OriginalAudioExtensions.Contains(ext))
{
if (originalFilePath != null)
{
Debug.LogWarning($"在 '{folderPath}' 文件夹中发现多个同名原始音频文件(MP3/WAV)'{baseName}'。跳过此组文件。");
originalFilePath = null;
break;
}
originalFilePath = filePath;
}
}
// 如果找到了一个有效的配对(一个原始文件,一个OGG文件)
if (originalFilePath != null && oggFilePath != null)
{
// 转换为Unity资产路径格式
string assetOriginalPath = originalFilePath.Replace(Application.dataPath, "Assets").Replace('\\', '/');
string assetOggPath = oggFilePath.Replace(Application.dataPath, "Assets").Replace('\\', '/');
// 从原始文件的.meta文件中获取GUID
string originalMetaPath = originalFilePath + ".meta";
string originalGuid = GetGuidFromMetaFile(originalMetaPath);
if (string.IsNullOrEmpty(originalGuid))
{
Debug.LogError($"无法从原始文件 '{originalFilePath}' 的.meta文件中读取GUID。跳过此文件。");
skippedCount++;
continue;
}
Debug.Log($"原始文件 '{assetOriginalPath}' 的GUID为: {originalGuid}");
// 关键步骤1:删除OGG文件的.meta文件(如果存在)
string oggMetaPath = oggFilePath + ".meta";
if (File.Exists(oggMetaPath))
{
try
{
File.Delete(oggMetaPath);
Debug.Log($"已删除OGG文件的.meta文件: '{oggMetaPath}'");
}
catch (System.Exception ex)
{
Debug.LogError($"删除OGG文件的.meta文件时出错: {ex.Message}");
skippedCount++;
continue;
}
}
// 关键步骤2:删除原始文件(MP3/WAV)
// 注意:我们不使用AssetDatabase.DeleteAsset,因为它可能不会完全释放GUID
try
{
// 先删除原始文件的.meta文件
if (File.Exists(originalMetaPath))
{
File.Delete(originalMetaPath);
Debug.Log($"已删除原始文件的.meta文件: '{originalMetaPath}'");
}
// 再删除原始文件本身
if (File.Exists(originalFilePath))
{
File.Delete(originalFilePath);
Debug.Log($"已删除原始文件: '{originalFilePath}'");
}
}
catch (System.Exception ex)
{
Debug.LogError($"删除原始文件或其.meta文件时出错: {ex.Message}");
skippedCount++;
continue;
}
// 关键步骤3:刷新资产数据库,确保Unity知道文件已被删除
AssetDatabase.Refresh();
// 关键步骤4:为OGG文件创建新的.meta文件,使用原始文件的GUID
string metaContent = GenerateMetaFileContent(originalGuid);
try
{
File.WriteAllText(oggMetaPath, metaContent);
Debug.Log($"已为OGG文件创建新的.meta文件,使用原始GUID: '{oggMetaPath}'");
}
catch (System.Exception ex)
{
Debug.LogError($"为OGG文件创建.meta文件时出错: {ex.Message}");
skippedCount++;
continue;
}
// 关键步骤5:再次刷新资产数据库,确保Unity导入新的.meta文件
AssetDatabase.Refresh();
// 关键步骤6:验证OGG文件的GUID是否正确
string newOggGuid = AssetDatabase.AssetPathToGUID(assetOggPath);
if (newOggGuid == originalGuid)
{
Debug.Log($"成功: 文件 '{assetOggPath}' 现在使用原始文件的GUID: {originalGuid}");
processedCount++;
}
else
{
Debug.LogError($"GUID转移失败! OGG文件 '{assetOggPath}' 的GUID为 {newOggGuid},而不是预期的 {originalGuid}");
skippedCount++;
}
}
else if (originalFilePath == null && oggFilePath != null)
{
// 只有OGG文件,没有对应的原始文件,这是正常的,不需要警告
continue;
}
else if (originalFilePath != null && oggFilePath == null)
{
Debug.LogWarning($"找到原始文件 '{originalFilePath}',但没有对应的OGG文件。跳过此文件。");
skippedCount++;
}
}
// 最终刷新,确保所有更改都已反映在Unity中
AssetDatabase.Refresh();
Debug.Log($"处理完成:成功处理 {processedCount} 对文件,跳过 {skippedCount} 对文件。");
}
///
/// 从文件名中提取基础名称,移除所有已知的音频扩展名。
///
private static string GetBaseNameWithoutExtensions(string fileName)
{
string baseName = Path.GetFileNameWithoutExtension(fileName);
// 检查是否还有其他音频扩展名
foreach (string ext in OriginalAudioExtensions.Concat(new[] { TargetAudioExtension }))
{
if (baseName.EndsWith(ext, System.StringComparison.OrdinalIgnoreCase))
{
baseName = baseName.Substring(0, baseName.Length - ext.Length);
}
}
return baseName;
}
///
/// 从Unity的.meta文件中提取GUID。
///
private static string GetGuidFromMetaFile(string metaFilePath)
{
if (!File.Exists(metaFilePath))
{
Debug.LogError($"Meta文件不存在: {metaFilePath}");
return null;
}
string metaContent = File.ReadAllText(metaFilePath);
Match match = Regex.Match(metaContent, @"guid: ([0-9a-fA-F]{32})");
if (match.Success)
{
return match.Groups[1].Value;
}
return null;
}
///
/// 生成包含指定GUID的.meta文件内容。
///
private static string GenerateMetaFileContent(string guid)
{
// 创建一个基本的AudioImporter .meta文件内容
return $@"fileFormatVersion: 2
guid: {guid}
AudioImporter:
externalObjects: {{}}
serializedVersion: 6
defaultSettings:
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
platformSettingOverrides: {{}}
forceToMono: 0
normalize: 1
preloadAudioData: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:
";
}
// 菜单项的验证方法
[MenuItem("Assets/Transfer MP3/WAV GUID to OGG in Selected Folder", true)]
public static bool ValidateTransferGUIDsInSelectedFolder()
{
// 仅当在Project窗口中选中单个文件夹时才启用菜单项
if (Selection.activeObject == null) return false;
string selectedPath = AssetDatabase.GetAssetPath(Selection.activeObject);
return AssetDatabase.IsValidFolder(selectedPath);
}
}