using System.IO; using UnityEditor; using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEngine; namespace Audio2Lipsync.Editor { /// /// Puts the packaged model into StreamingAssets/Audio2Lipsync/ for the length of a player build, /// then removes it again, so a project never has to keep its own copy. A model the user put /// there themselves is used as-is and left alone. /// sealed class Audio2LipsyncBuildHook : IPreprocessBuildWithReport, IPostprocessBuildWithReport { // Marks a copy made by this hook, so a build that failed before post-processing still gets // cleaned up on the next domain reload. const string Marker = "Library/Audio2LipsyncBuildCopy.txt"; public int callbackOrder => 0; public void OnPreprocessBuild(BuildReport report) { var dst = Audio2LipsyncModel.StreamingAssetsPath; if (File.Exists(dst)) return; var src = Audio2LipsyncModel.PackagedPath; if (src == null || !File.Exists(src)) throw new BuildFailedException($"Audio2Lipsync model not found in the package: {src} (is git-lfs installed?)"); Directory.CreateDirectory(Path.GetDirectoryName(dst)); File.Copy(src, dst); File.WriteAllText(Marker, dst); EditorApplication.delayCall += Cleanup; // runs after the build even when it failed Debug.Log($"[Audio2Lipsync] model staged for build -> {dst}"); } public void OnPostprocessBuild(BuildReport report) => Cleanup(); [InitializeOnLoadMethod] static void CleanupAfterFailedBuild() { if (!BuildPipeline.isBuildingPlayer) Cleanup(); } static void Cleanup() { if (!File.Exists(Marker)) return; var path = File.ReadAllText(Marker); File.Delete(Marker); DeleteWithMeta(path); var dir = Path.GetDirectoryName(path); if (Directory.Exists(dir) && Directory.GetFileSystemEntries(dir).Length == 0) { Directory.Delete(dir); DeleteWithMeta(dir); } } static void DeleteWithMeta(string path) { if (File.Exists(path)) File.Delete(path); if (File.Exists(path + ".meta")) File.Delete(path + ".meta"); } } }