Some fixes for Live2D export

This commit is contained in:
VaDiM 2023-11-08 01:25:32 +03:00
parent 45bf9251c9
commit e415740373
3 changed files with 143 additions and 100 deletions

View File

@ -624,6 +624,7 @@ namespace AssetStudioCLI
}
return false;
}).Select(x => x.Asset).ToArray();
if (cubismMocs.Length == 0)
{
Logger.Default.Log(LoggerEvent.Info, "Live2D Cubism models were not found.", ignoreLevel: true);
@ -631,7 +632,18 @@ namespace AssetStudioCLI
}
if (cubismMocs.Length > 1)
{
var basePathSet = cubismMocs.Select(x => containers[x].Substring(0, containers[x].LastIndexOf("/"))).ToHashSet();
var basePathSet = cubismMocs.Select(x =>
{
var pathLen = containers.TryGetValue(x, out var itemContainer) ? itemContainer.LastIndexOf("/") : 0;
pathLen = pathLen < 0 ? containers[x].Length : pathLen;
return itemContainer?.Substring(0, pathLen);
}).ToHashSet();
if (basePathSet.All(x => x == null))
{
Logger.Error($"Live2D Cubism export error: Cannot find any model related files.");
return;
}
if (basePathSet.Count != cubismMocs.Length)
{
@ -639,9 +651,16 @@ namespace AssetStudioCLI
Logger.Debug($"useFullContainerPath: {useFullContainerPath}");
}
}
var basePathList = useFullContainerPath ?
cubismMocs.Select(x => containers[x]).ToList() :
cubismMocs.Select(x => containers[x].Substring(0, containers[x].LastIndexOf("/"))).ToList();
var basePathList = cubismMocs.Select(x =>
{
containers.TryGetValue(x, out var container);
container = useFullContainerPath
? container
: container?.Substring(0, container.LastIndexOf("/"));
return container;
}).Where(x => x != null).ToList();
var lookup = containers.ToLookup(
x => basePathList.Find(b => x.Value.Contains(b) && x.Value.Split('/').Any(y => y == b.Substring(b.LastIndexOf("/") + 1))),
x => x.Key
@ -649,16 +668,15 @@ namespace AssetStudioCLI
var totalModelCount = lookup.LongCount(x => x.Key != null);
Logger.Info($"Found {totalModelCount} model(s).");
var name = "";
var modelCounter = 0;
foreach (var assets in lookup)
{
var container = assets.Key;
if (container == null)
var srcContainer = assets.Key;
if (srcContainer == null)
continue;
name = container;
var container = srcContainer;
Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{container.Color(Ansi.BrightCyan)}\"");
Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{srcContainer.Color(Ansi.BrightCyan)}\"");
try
{
var modelName = useFullContainerPath ? Path.GetFileNameWithoutExtension(container) : container.Substring(container.LastIndexOf('/') + 1);
@ -670,10 +688,11 @@ namespace AssetStudioCLI
}
catch (Exception ex)
{
Logger.Error($"Live2D model export error: \"{name}\"", ex);
Logger.Error($"Live2D model export error: \"{srcContainer}\"", ex);
}
Progress.Report(modelCounter, (int)totalModelCount);
}
var status = modelCounter > 0 ?
$"Finished exporting [{modelCounter}/{totalModelCount}] Live2D model(s) to \"{CLIOptions.o_outputFolder.Value.Color(Ansi.BrightCyan)}\"" :
"Nothing exported.";

View File

@ -259,6 +259,7 @@ namespace AssetStudioGUI
Progress.Report(++i, objectCount);
}
}
allContainers.Clear();
foreach ((var pptr, var container) in containers)
{
if (pptr.TryGet(out var obj))
@ -753,32 +754,51 @@ namespace AssetStudioGUI
var useFullContainerPath = false;
if (cubismMocs.Length > 1)
{
var basePathSet = cubismMocs.Select(x => allContainers[x].Substring(0, allContainers[x].LastIndexOf("/"))).ToHashSet();
var basePathSet = cubismMocs.Select(x =>
{
var pathLen = allContainers.TryGetValue(x, out var itemContainer) ? itemContainer.LastIndexOf("/") : 0;
pathLen = pathLen < 0 ? allContainers[x].Length : pathLen;
return itemContainer?.Substring(0, pathLen);
}).ToHashSet();
if (basePathSet.All(x => x == null))
{
Logger.Error($"Live2D Cubism export error\r\nCannot find any model related files");
StatusStripUpdate("Live2D export canceled");
Progress.Reset();
return;
}
if (basePathSet.Count != cubismMocs.Length)
{
useFullContainerPath = true;
}
}
var basePathList = useFullContainerPath ?
cubismMocs.Select(x => allContainers[x]).ToList() :
cubismMocs.Select(x => allContainers[x].Substring(0, allContainers[x].LastIndexOf("/"))).ToList();
var basePathList = cubismMocs.Select(x =>
{
allContainers.TryGetValue(x, out var container);
container = useFullContainerPath
? container
: container?.Substring(0, container.LastIndexOf("/"));
return container;
}).Where(x => x != null).ToList();
var lookup = allContainers.ToLookup(
x => basePathList.Find(b => x.Value.Contains(b) && x.Value.Split('/').Any(y => y == b.Substring(b.LastIndexOf("/") + 1))),
x => x.Key
);
var totalModelCount = lookup.LongCount(x => x.Key != null);
var name = "";
var modelCounter = 0;
foreach (var assets in lookup)
{
var container = assets.Key;
if (container == null)
var srcContainer = assets.Key;
if (srcContainer == null)
continue;
name = container;
var container = srcContainer;
Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{container}\"...");
Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{srcContainer}\"...");
try
{
var modelName = useFullContainerPath ? Path.GetFileNameWithoutExtension(container) : container.Substring(container.LastIndexOf('/') + 1);
@ -790,11 +810,17 @@ namespace AssetStudioGUI
}
catch (Exception ex)
{
Logger.Error($"Live2D model export error: \"{name}\"", ex);
Logger.Error($"Live2D model export error: \"{srcContainer}\"", ex);
}
Progress.Report(modelCounter, (int)totalModelCount);
}
Logger.Info($"Finished exporting [{modelCounter}/{totalModelCount}] Live2D model(s).");
if (modelCounter < totalModelCount)
{
var total = (int)totalModelCount;
Progress.Report(total, total);
}
if (Properties.Settings.Default.openAfterExport && modelCounter > 0)
{
OpenFolderInExplorer(exportPath);

View File

@ -26,20 +26,57 @@ namespace CubismLive2DExtractor
Directory.CreateDirectory(destPath);
Directory.CreateDirectory(destTexturePath);
var monoBehaviours = new List<MonoBehaviour>();
var texture2Ds = new List<Texture2D>();
var expressionList = new List<MonoBehaviour>();
var gameObjects = new List<GameObject>();
var animationClips = new List<AnimationClip>();
var textures = new SortedSet<string>();
var eyeBlinkParameters = new HashSet<string>();
var lipSyncParameters = new HashSet<string>();
MonoBehaviour physics = null;
foreach (var asset in assets)
{
switch (asset)
{
case MonoBehaviour m_MonoBehaviour:
monoBehaviours.Add(m_MonoBehaviour);
if (m_MonoBehaviour.m_Script.TryGet(out var m_Script))
{
switch (m_Script.m_ClassName)
{
case "CubismMoc":
File.WriteAllBytes($"{destPath}{modelName}.moc3", ParseMoc(m_MonoBehaviour)); //moc
break;
case "CubismPhysicsController":
physics = physics ?? m_MonoBehaviour;
break;
case "CubismExpressionData":
expressionList.Add(m_MonoBehaviour);
break;
case "CubismEyeBlinkParameter":
if (m_MonoBehaviour.m_GameObject.TryGet(out var blinkGameObject))
{
eyeBlinkParameters.Add(blinkGameObject.m_Name);
}
break;
case "CubismMouthParameter":
if (m_MonoBehaviour.m_GameObject.TryGet(out var mouthGameObject))
{
lipSyncParameters.Add(mouthGameObject.m_Name);
}
break;
}
}
break;
case Texture2D m_Texture2D:
texture2Ds.Add(m_Texture2D);
using (var image = m_Texture2D.ConvertToImage(flip: true))
{
using (var file = File.OpenWrite($"{destTexturePath}{m_Texture2D.m_Name}.png"))
{
image.WriteToStream(file, ImageFormat.Png);
}
textures.Add($"textures/{m_Texture2D.m_Name}.png"); //texture
}
break;
case GameObject m_GameObject:
gameObjects.Add(m_GameObject);
@ -50,15 +87,12 @@ namespace CubismLive2DExtractor
}
}
//physics
var physics = monoBehaviours.FirstOrDefault(x =>
if (textures.Count == 0)
{
if (x.m_Script.TryGet(out var m_Script))
{
return m_Script.m_ClassName == "CubismPhysicsController";
}
return false;
});
Logger.Warning($"No textures found for \"{modelName}\" model.");
}
//physics
if (physics != null)
{
try
@ -73,31 +107,6 @@ namespace CubismLive2DExtractor
}
}
//moc
var moc = monoBehaviours.First(x =>
{
if (x.m_Script.TryGet(out var m_Script))
{
return m_Script.m_ClassName == "CubismMoc";
}
return false;
});
File.WriteAllBytes($"{destPath}{modelName}.moc3", ParseMoc(moc));
//texture
var textures = new SortedSet<string>();
foreach (var texture2D in texture2Ds)
{
using (var image = texture2D.ConvertToImage(flip: true))
{
textures.Add($"textures/{texture2D.m_Name}.png");
using (var file = File.OpenWrite($"{destTexturePath}{texture2D.m_Name}.png"))
{
image.WriteToStream(file, ImageFormat.Png);
}
}
}
//motion
var motions = new SortedDictionary<string, JArray>();
@ -205,23 +214,30 @@ namespace CubismLive2DExtractor
}
json.Meta.TotalUserDataSize = totalUserDataSize;
var motionPath = new JObject(new JProperty("File", $"motions/{animation.Name}.motion3.json"));
motions.Add(animation.Name, new JArray(motionPath));
File.WriteAllText($"{destMotionPath}{animation.Name}.motion3.json", JsonConvert.SerializeObject(json, Formatting.Indented, new MyJsonConverter()));
var animName = animation.Name;
if (motions.ContainsKey(animName))
{
animName = $"{animName}_{animation.GetHashCode()}";
if (motions.ContainsKey(animName))
{
continue;
}
}
var motionPath = new JObject(new JProperty("File", $"motions/{animName}.motion3.json"));
motions.Add(animName, new JArray(motionPath));
File.WriteAllText($"{destMotionPath}{animName}.motion3.json", JsonConvert.SerializeObject(json, Formatting.Indented, new MyJsonConverter()));
}
}
//expression
var expressions = new JArray();
var monoBehaviourArray = monoBehaviours.Where(x => x.m_Name.EndsWith(".exp3")).ToArray();
if (monoBehaviourArray.Length > 0)
if (expressionList.Count > 0)
{
Directory.CreateDirectory(destExpressionPath);
}
foreach (var monoBehaviour in monoBehaviourArray)
foreach (var monoBehaviour in expressionList)
{
var fullName = monoBehaviour.m_Name;
var expressionName = fullName.Replace(".exp3", "");
var expressionName = monoBehaviour.m_Name.Replace(".exp3", "");
var expressionObj = monoBehaviour.ToType();
if (expressionObj == null)
{
@ -238,57 +254,38 @@ namespace CubismLive2DExtractor
expressions.Add(new JObject
{
{ "Name", expressionName },
{ "File", $"expressions/{fullName}.json" }
{ "File", $"expressions/{expressionName}.exp3.json" }
});
File.WriteAllText($"{destExpressionPath}{fullName}.json", JsonConvert.SerializeObject(expression, Formatting.Indented));
File.WriteAllText($"{destExpressionPath}{expressionName}.exp3.json", JsonConvert.SerializeObject(expression, Formatting.Indented));
}
//model
//group
var groups = new List<CubismModel3Json.SerializableGroup>();
var eyeBlinkParameters = monoBehaviours.Where(x =>
{
x.m_Script.TryGet(out var m_Script);
return m_Script?.m_ClassName == "CubismEyeBlinkParameter";
}).Select(x =>
{
x.m_GameObject.TryGet(out var m_GameObject);
return m_GameObject?.m_Name;
}).ToHashSet();
//Try looking for group IDs among the gameObjects
if (eyeBlinkParameters.Count == 0)
{
eyeBlinkParameters = gameObjects.Where(x =>
{
return x.m_Name.ToLower().Contains("eye")
x.m_Name.ToLower().Contains("eye")
&& x.m_Name.ToLower().Contains("open")
&& (x.m_Name.ToLower().Contains('l') || x.m_Name.ToLower().Contains('r'));
}).Select(x => x.m_Name).ToHashSet();
&& (x.m_Name.ToLower().Contains('l') || x.m_Name.ToLower().Contains('r'))
).Select(x => x.m_Name).ToHashSet();
}
if (lipSyncParameters.Count == 0)
{
lipSyncParameters = gameObjects.Where(x =>
x.m_Name.ToLower().Contains("mouth")
&& x.m_Name.ToLower().Contains("open")
&& x.m_Name.ToLower().Contains('y')
).Select(x => x.m_Name).ToHashSet();
}
groups.Add(new CubismModel3Json.SerializableGroup
{
Target = "Parameter",
Name = "EyeBlink",
Ids = eyeBlinkParameters.ToArray()
});
var lipSyncParameters = monoBehaviours.Where(x =>
{
x.m_Script.TryGet(out var m_Script);
return m_Script?.m_ClassName == "CubismMouthParameter";
}).Select(x =>
{
x.m_GameObject.TryGet(out var m_GameObject);
return m_GameObject?.m_Name;
}).ToHashSet();
if (lipSyncParameters.Count == 0)
{
lipSyncParameters = gameObjects.Where(x =>
{
return x.m_Name.ToLower().Contains("mouth")
&& x.m_Name.ToLower().Contains("open")
&& x.m_Name.ToLower().Contains('y');
}).Select(x => x.m_Name).ToHashSet();
}
groups.Add(new CubismModel3Json.SerializableGroup
{
Target = "Parameter",
@ -296,6 +293,7 @@ namespace CubismLive2DExtractor
Ids = lipSyncParameters.ToArray()
});
//model
var model3 = new CubismModel3Json
{
Version = 3,