Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better Directory Input & Game Object based Lip Syncing #76

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 191 additions & 0 deletions Assets/uLipSync/Editor/BakedDataWizard.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using uLipSync;
using UnityEngine;

///
/
///
public class BakedDataWizard : ScriptableWizard
{
SerializedObject _serializedObject;

[SerializeField]
Profile profile;
[SerializeField]
string inputDirectory = "";
[SerializeField]
string outputDirectory = "";

StringBuilder _message = new();

[MenuItem("Window/uLipSync/Baked Data Wizard")]
static void Open()
{
DisplayWizard<BakedDataWizard>("Baked Data Directory Mirror Generator", "Generate");
}

protected override bool DrawWizardGUI()
{
_serializedObject ??= new SerializedObject(this);

_serializedObject.Update();

EditorGUILayout.Separator();
EditorGUILayout.LabelField("Baked Data Profile", EditorStyles.boldLabel);
EditorUtil.DrawProperty(_serializedObject, nameof(profile));
EditorGUILayout.Separator();

// Input Directory
EditorGUILayout.LabelField("Baked Data Input Directory", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
EditorUtil.DrawProperty(_serializedObject, nameof(inputDirectory));
if (GUILayout.Button("Browse", GUILayout.Width(75)))
{
var path = EditorUtility.OpenFolderPanel("Baked Data Input Directory", Application.dataPath, "BakedDataInput");
if (!string.IsNullOrEmpty(path))
{
inputDirectory = EditorUtil.GetAssetPath(path);
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Separator();

// Output Directory
EditorGUILayout.LabelField("Baked Data Output Directory", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
EditorUtil.DrawProperty(_serializedObject, nameof(outputDirectory));
if (GUILayout.Button("Browse", GUILayout.Width(75)))
{
var path = EditorUtility.OpenFolderPanel("Baked Data Output Directory", Application.dataPath, "OverrideOutput");
if (!string.IsNullOrEmpty(path))
{
outputDirectory = EditorUtil.GetAssetPath(path);
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Separator();

DrawMessage();

_serializedObject.ApplyModifiedProperties();

return true;
}

private void OnWizardCreate()
{
// This method is called when the "Generate" button is clicked.
OnWizardOtherButton();
}

void OnWizardOtherButton()
{
var dataList = new List<BakedData>();

// Failsafe: Check the directories actually exist first
if (!Directory.Exists(inputDirectory))
{
Debug.LogError($"Input directory does not exist: {inputDirectory}");
return;
}

if (!Directory.Exists(outputDirectory))
{
Debug.LogError($"Output directory does not exist: {outputDirectory}");
return;
}

// Step 1: Get a list of all audioclips in the input directory
var clipIDList = AssetDatabase.FindAssets("t:AudioClip", new[] { inputDirectory });
//generate a list of all audioclip paths
List<string> audioClipPaths = clipIDList.Select(AssetDatabase.GUIDToAssetPath).ToList();

var audioClipCount = audioClipPaths.Count;
var bakedDataGenerated = 0;

// Step 2: For each audioclip, create a BakedData scriptable object with the defaultData set to the BakedData scriptable object
foreach (var audioClipPath in audioClipPaths)
{

AudioClip clip = AssetDatabase.LoadAssetAtPath<AudioClip>(audioClipPath);
if (clip == null) continue;

// Generate the relative path and the output path for the bakedDataObject
var relativePath = audioClipPath[(inputDirectory.Length + 1)..];
var bakedDataPath = Path.Combine(outputDirectory, relativePath);
var bakedDataDirectory = Path.GetDirectoryName(bakedDataPath);
var bakedDataAssetName = Path.GetFileNameWithoutExtension(bakedDataPath) + ".asset";
if (bakedDataDirectory == null) continue;
var bakedDataAssetPath = Path.Combine(bakedDataDirectory, bakedDataAssetName);

// Ensure the output directory exists
if (!Directory.Exists(bakedDataDirectory))
{
Directory.CreateDirectory(bakedDataDirectory);
}

// Check if the BakedData asset already exists
var data = AssetDatabase.LoadAssetAtPath<BakedData>(bakedDataAssetPath);
if (data == null)
{
//Create the baked data
data = CreateInstance<BakedData>();
data.profile = profile;
data.audioClip = clip;
data.name = clip.name;

var editor = (BakedDataEditor)Editor.CreateEditor(data, typeof(BakedDataEditor));
editor.Bake();

AssetDatabase.CreateAsset(data, bakedDataAssetPath);
bakedDataGenerated++;

var progress = (float)dataList.Count / audioClipPaths.Count;
var msg = $"Baking... {dataList.Count}/{audioClipPaths.Count}";
EditorUtility.DisplayProgressBar("uLipSync", msg, progress);
}
else
{
// Update the existing BakedData asset
data = CreateInstance<BakedData>();
data.profile = profile;
data.audioClip = clip;
data.name = clip.name;

var editor = (BakedDataEditor)Editor.CreateEditor(data, typeof(BakedDataEditor));
editor.Bake();

EditorUtility.SetDirty(data);
var progress = (float)dataList.Count / audioClipPaths.Count;
var msg = $"Baking... {dataList.Count}/{audioClipPaths.Count}";
EditorUtility.DisplayProgressBar("uLipSync", msg, progress);
}
}

EditorUtility.ClearProgressBar();

// Save all changes to the asset database
AssetDatabase.SaveAssets();

// Step 5: Display a message with the number of BakedData scriptable objects found and the number of BakedData scriptable objects created
_message.Clear();
_message.AppendLine($"Found {audioClipCount} audioClips.");
_message.AppendLine($"Created {bakedDataGenerated} BakedData scriptable objects.");
Debug.Log(_message.ToString());

Repaint(); // Repaint the GUI to update the message
}

void DrawMessage()
{
if (_message.Length == 0) return;

EditorGUILayout.HelpBox(_message.ToString(), MessageType.Info);
}
}
145 changes: 145 additions & 0 deletions Assets/uLipSync/Editor/FixAssetMainObjectNames.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using System.IO;
using UnityEngine;
using UnityEditor;
using UnityEditor.PackageManager;

using PackageInfo = UnityEditor.PackageManager.PackageInfo;

/// <summary>
/// Adds a menu command to find and fix all assets that have mismatched main
/// object names and file names. I.e., "Main Object Name '{0}' does not match
/// filename '{1}'". This warning can appear after renaming from outside of
/// Unity.
/// <br /><br />
///
/// Read-only assets are ignored. Supports Undo. Has a dry-run mode to list
/// the assets it would have modified in a full run.
/// <br /><br />
///
/// This is essentially a way to do what Unity already does <see
/// href="https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/Inspector/Editor.cs#L1176">
/// here</see> but in batch.
/// </summary>
public static class FixAssetMainObjectNames
{
[MenuItem("Tools/Fix Asset Main Object Names")]
public static void FindAndFixAssets()
{
string dialogTitle =
ObjectNames.NicifyVariableName(nameof(FixAssetMainObjectNames));

// Prompt the user to do a dry run or the real thing. A dry run
// allows the user to verify nothing unwanted is affected.
int dialogResult =
EditorUtility.DisplayDialogComplex(
dialogTitle,
message: "Find and fix assets with incorrect main object " +
"names?\n\nThis loads every asset in the project and " +
"can be very slow in large projects.",
ok: "Dry Run",
cancel: "Cancel",
alt: "Fix All"
);

const int DialogResultOK = 0;
const int DialogResultCancel = 1;

if (dialogResult == DialogResultCancel)
{
return;
}

bool isDryRun = dialogResult == DialogResultOK;
if (isDryRun)
{
dialogTitle += " (Dry Run)";
}

// Find all assets
string[] allGuidsInProject = AssetDatabase.FindAssets("");

// List of types that don't complain in the inspector when their
// names are mismatched.
System.Type[] assetTypeBlacklist = new[]
{
typeof(Shader), // Shaders appear with mismatched names regularly. Even in official packages.
typeof(DefaultAsset), // Folders with periods in them will have different names. This is OK.
};

bool didCancel = false;

try
{
int assetCount = allGuidsInProject.Length;
Debug.Log($"Starting scan over {assetCount} assets...");
for (int i = 0; i < assetCount; i++)
{
string assetGuid = allGuidsInProject[i];
string assetPath = AssetDatabase.GUIDToAssetPath(assetGuid);

Object asset = AssetDatabase.LoadAssetAtPath<Object>(assetPath);

if(!asset || string.IsNullOrEmpty(asset.name)) continue;
string mainObjectName = asset.name ;
string expectedMainObjectName = Path.GetFileNameWithoutExtension(assetPath);

didCancel =
EditorUtility.DisplayCancelableProgressBar(
dialogTitle + $" - {i + 1} of {assetCount}",
info: assetPath,
progress: (float)(i + 1) / assetCount
);

if (didCancel)
{
// The user requested an early exit
break;
}

int blacklistedTypeIndex =
System.Array.IndexOf(assetTypeBlacklist, asset.GetType());

if (blacklistedTypeIndex >= 0)
{
// Skip assets of blacklisted types. See array definition
// above.
continue;
}

if (mainObjectName == expectedMainObjectName)
{
// Asset is already correct
continue;
}

PackageInfo packageInfo = PackageInfo.FindForAssetPath(assetPath);
if (packageInfo != null &&
packageInfo.source != PackageSource.Embedded &&
packageInfo.source != PackageSource.Local)
{
// Asset is read-only
continue;
}

if (isDryRun)
{
Debug.Log("Would fix: " + assetPath, asset);
}
else
{
Undo.RecordObject(asset, dialogTitle);
using SerializedObject serializedAsset = new SerializedObject(asset);
asset.name = expectedMainObjectName;
Debug.Log("Fixed: " + assetPath, asset);
}
}
}
finally
{
EditorUtility.ClearProgressBar();

Debug.Log(didCancel ? "Canceled." : "Finished.");
}
}

} // class FixAssetMainObjectNames
Loading