Skip to content

Commit 82ce831

Browse files
committed
feat: add visual lead option
1 parent aefbbd2 commit 82ce831

File tree

3 files changed

+119
-81
lines changed

3 files changed

+119
-81
lines changed

KuchiPaku.Core/Models/YmmpUtil.cs

Lines changed: 66 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public static Dictionary<int, int>
6060
{
6161
//シーンごとにMaxLayerを取得
6262
true => tl.Select((v, i) => (Scene: i, MaxLayer: (int)v["MaxLayer"]!)),
63-
_ => [(Scene:0, MaxLayer:(int)tl["MaxLayer"]!)],
63+
_ => [(Scene: 0, MaxLayer: (int)tl["MaxLayer"]!)],
6464
};
6565

6666
return maxLayers.ToDictionary(x => x.Scene, x => x.MaxLayer);
@@ -80,7 +80,7 @@ public static async ValueTask<List<YmmCharacter>> ParseCharactersAsync(JObject y
8080
});
8181
}
8282

83-
public static async ValueTask<IEnumerable<(int Scene,YmmVoiceItem Item)>>
83+
public static async ValueTask<IEnumerable<(int Scene, YmmVoiceItem Item)>>
8484
ParseVoiceItemsAsync(
8585
JObject ymmp
8686
)
@@ -120,7 +120,7 @@ private static Dictionary<int, JArray>
120120
return items;
121121
}
122122

123-
public static async ValueTask<IEnumerable<(int Scene,YmmVoiceItem Item)>>
123+
public static async ValueTask<IEnumerable<(int Scene, YmmVoiceItem Item)>>
124124
FilterCustomVoiceAsync(
125125
IEnumerable<(int Scene, YmmVoiceItem Item)> voices
126126
)
@@ -196,7 +196,8 @@ public static async ValueTask MakeRipSyncItemAsync(
196196
int insertLayer = 0,
197197
int offsetFrame = 0,
198198
bool isLocked = false,
199-
int sceneIndex = 0
199+
int sceneIndex = 0,
200+
int visualLeadFrames = 0
200201
)
201202
{
202203
if (lab is null || lab.Lines is null)
@@ -280,62 +281,62 @@ public static async ValueTask MakeRipSyncItemAsync(
280281
switch (line.Phoneme)
281282
{
282283
case var p when VOWELS_A.Contains(p):
283-
{
284-
imageFileName = images["a"];
285-
lastVowel = imageFileName;
286-
break;
287-
}
284+
{
285+
imageFileName = images["a"];
286+
lastVowel = imageFileName;
287+
break;
288+
}
288289

289290
case var p when VOWELS_I.Contains(p):
290-
{
291-
imageFileName = images["i"];
292-
lastVowel = imageFileName;
293-
break;
294-
}
291+
{
292+
imageFileName = images["i"];
293+
lastVowel = imageFileName;
294+
break;
295+
}
295296

296297
case var p when VOWELS_U.Contains(p):
297-
{
298-
imageFileName = images["u"];
299-
lastVowel = imageFileName;
300-
break;
301-
}
298+
{
299+
imageFileName = images["u"];
300+
lastVowel = imageFileName;
301+
break;
302+
}
302303

303304
case var p when VOWELS_E.Contains(p):
304-
{
305-
imageFileName = images["e"];
306-
lastVowel = imageFileName;
307-
break;
308-
}
305+
{
306+
imageFileName = images["e"];
307+
lastVowel = imageFileName;
308+
break;
309+
}
309310

310311
case var p when VOWELS_O.Contains(p):
311-
{
312-
imageFileName = images["o"];
313-
lastVowel = imageFileName;
314-
break;
315-
}
312+
{
313+
imageFileName = images["o"];
314+
lastVowel = imageFileName;
315+
break;
316+
}
316317

317318
case var p when CLOSE_CONSONANT.Contains(p):
318-
{
319-
imageFileName = images["N"];
320-
break;
321-
}
319+
{
320+
imageFileName = images["N"];
321+
break;
322+
}
322323

323324
case var p when OPEN_CONSONANT.Contains(p):
324-
{
325-
imageFileName = consoOpt switch
326325
{
327-
ConsonantOption.CONTINUE_BEFORE_VOWEL => lastVowel,
328-
ConsonantOption.SMALL_MOUSE => images["u"], //TODO:代理処理
329-
_ => images["N"],
330-
};
331-
break;
332-
}
326+
imageFileName = consoOpt switch
327+
{
328+
ConsonantOption.CONTINUE_BEFORE_VOWEL => lastVowel,
329+
ConsonantOption.SMALL_MOUSE => images["u"], //TODO:代理処理
330+
_ => images["N"],
331+
};
332+
break;
333+
}
333334

334335
default:
335-
{
336-
imageFileName = images["N"];
337-
break;
338-
}
336+
{
337+
imageFileName = images["N"];
338+
break;
339+
}
339340
}
340341

341342
var mouseImagePath = Path.Combine(lipSyncOption.MouseDir!, imageFileName);
@@ -349,7 +350,7 @@ public static async ValueTask MakeRipSyncItemAsync(
349350
newItem["Layer"] = insertLayer;
350351
newItem["CharacterName"] = lipSyncOption.CharacterName;
351352
newItem["TachieFaceParameter"]!["Mouth"] = mouseImagePath;
352-
newItem["Frame"] = line.FrameFrom + offsetFrame;
353+
newItem["Frame"] = line.FrameFrom + offsetFrame - visualLeadFrames;
353354
newItem["Length"] = line.FrameLen;
354355
newItem["IsLocked"] = isLocked;
355356

@@ -371,11 +372,12 @@ public static int CulcContentOffset(double totalSeconds, int fps)
371372
}
372373

373374
public static void MakeCustomVoiceFaceItem(
374-
IDictionary<int, int> maxLayer,
375+
IDictionary<int, int> maxLayer,
375376
IEnumerable<(int Scene, YmmVoiceItem Item)> customVoices,
376377
JObject ymmp,
377378
Dictionary<string, LipSyncOption> lipSyncSettings,
378-
IEnumerable<(int Scene, int Fps)> currentYmmpFPS
379+
IEnumerable<(int Scene, int Fps)> currentYmmpFPS,
380+
int visualLeadMs = 0
379381
)
380382
{
381383
var sw = new System.Diagnostics.Stopwatch();
@@ -436,7 +438,8 @@ await MakeRipSyncItemAsync(
436438
set[v.Item.CharacterName!]!,
437439
maxLayer[v.Scene] + 1,
438440
v.Item.Frame - contentOffset,
439-
sceneIndex: v.Scene
441+
sceneIndex: v.Scene,
442+
visualLeadFrames: CulcVisualLeadOffset(visualLeadMs, sceneFps)
440443
);
441444

442445
maxLayer[v.Scene]++;
@@ -451,7 +454,8 @@ public static async ValueTask MakeAPIVoiceFaceItemAsync(
451454
IEnumerable<(int Scene, YmmVoiceItem Item)> voiceItems,
452455
JObject ymmp,
453456
Dictionary<string, LipSyncOption> lipSyncSettings,
454-
IEnumerable<(int Scene, int Fps)> currentYmmpFPS
457+
IEnumerable<(int Scene, int Fps)> currentYmmpFPS,
458+
int visualLeadMs = 0
455459
)
456460
{
457461
var ymmChara = await ParseCharactersAsync(ymmp);
@@ -549,7 +553,13 @@ await MakeRipSyncItemAsync(
549553
set[v!.item.Item.CharacterName!]!,
550554
maxLayer[v.item.Scene] + 1,
551555
v.item.Item.Frame - contentOffset,
552-
sceneIndex: v.item.Scene
556+
sceneIndex: v.item.Scene,
557+
visualLeadFrames: CulcVisualLeadOffset(
558+
visualLeadMs,
559+
currentYmmpFPS
560+
.ElementAtOrDefault(v.item.Scene)
561+
.Fps
562+
)
553563
);
554564
}
555565
catch (System.Exception e)
@@ -610,4 +620,9 @@ static bool HasSceneTimelines(JObject ymmp)
610620

611621
return default;
612622
}
623+
624+
static int CulcVisualLeadOffset(double ms, int fps)
625+
{
626+
return (int)Math.Round((ms / 1000.0) * fps);
627+
}
613628
}

KuchiPaku/ViewModels/MainWindowViewModel.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ public sealed class MainWindowViewModel
5050
public bool IsSaveBackup { get; set; } = true;
5151
public bool IsOpenWithSave { get; set; } = true;
5252

53+
public int VisualLeadMs { get; set; } = 66;
54+
public bool IsEnabledVisualLead { get; set; }
55+
5356
private JObject? CurrentYmmp { get; set; }
5457

5558
private string? CurrentYmmpPath { get; set; }
@@ -311,10 +314,11 @@ IDictionary<int, int> maxLayer
311314
{
312315
YmmpUtil.MakeCustomVoiceFaceItem(
313316
maxLayer,
314-
customVoices.ToList(),
317+
[.. customVoices],
315318
ymmp,
316319
LipSyncSettings,
317-
CurrentYmmpSceneFps
320+
CurrentYmmpSceneFps,
321+
IsEnabledVisualLead ? VisualLeadMs : 0
318322
);
319323
}
320324
catch (System.Exception e)
@@ -353,7 +357,8 @@ await YmmpUtil.MakeAPIVoiceFaceItemAsync(
353357
apiVoices,
354358
ymmp,
355359
LipSyncSettings,
356-
CurrentYmmpSceneFps
360+
CurrentYmmpSceneFps,
361+
visualLeadMs: IsEnabledVisualLead ? VisualLeadMs : 0
357362
);
358363
}
359364
catch (System.Exception e)

KuchiPaku/Views/MainWindow.xaml

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -287,38 +287,56 @@
287287
</GroupBox>
288288
</ui:SimpleStackPanel>
289289

290+
<!--
290291
<GroupBox Header="挿入レイヤー設定">
291292
<ui:SimpleStackPanel></ui:SimpleStackPanel>
292293
</GroupBox>
294+
-->
293295
<GroupBox Header="オプション">
294296
<ui:SimpleStackPanel>
295-
<Expander x:Name="otherOptions"
296-
Header="そのほか"
297-
ExpandDirection="Down"
298-
IsExpanded="False"
299-
VerticalAlignment="Top"
300-
Margin="10,0,0,0">
301-
<WrapPanel fw:PointerTracker.Enabled="True">
302-
<Button x:Name="openLicense"
303-
Content="ライセンス"
304-
Margin="10"
305-
Padding="5"
306-
Style="{StaticResource ButtonRevealStyle}"
307-
Command="{Binding OpenLicenses}" />
308-
<Button x:Name="openWebsite"
309-
Content="KuchiPaku公式サイト"
310-
Margin="10"
311-
Padding="5"
312-
Style="{StaticResource ButtonRevealStyle}"
313-
Command="{Binding OpenWebsite}" />
314-
<Button x:Name="openYMM4Website"
315-
Content="YMM4公式サイト"
316-
Margin="10"
317-
Padding="5"
318-
Style="{StaticResource ButtonRevealStyle}"
319-
Command="{Binding OpenYMM4Website}" />
320-
</WrapPanel>
321-
</Expander>
297+
<ui:SimpleStackPanel
298+
Orientation="Horizontal"
299+
VerticalAlignment="Center"
300+
>
301+
<CheckBox
302+
x:Name="MyCheckBox"
303+
Content="口パク先行させる"
304+
IsChecked="{Binding IsEnabledVisualLead, Mode=TwoWay}"
305+
Margin="3" />
306+
<ui:NumberBox Header="映像先行 msec (66~100):"
307+
Value="{Binding VisualLeadMs}"
308+
Minimum="0"
309+
Maximum="200"
310+
IsEnabled="{Binding ElementName=MyCheckBox, Path=IsChecked}"
311+
/>
312+
</ui:SimpleStackPanel>
313+
<Expander x:Name="otherOptions"
314+
Header="そのほか"
315+
ExpandDirection="Down"
316+
IsExpanded="False"
317+
VerticalAlignment="Top"
318+
Margin="10,0,0,0">
319+
<WrapPanel fw:PointerTracker.Enabled="True">
320+
<Button x:Name="openLicense"
321+
Content="ライセンス"
322+
Margin="10"
323+
Padding="5"
324+
Style="{StaticResource ButtonRevealStyle}"
325+
Command="{Binding OpenLicenses}" />
326+
<Button x:Name="openWebsite"
327+
Content="KuchiPaku公式サイト"
328+
Margin="10"
329+
Padding="5"
330+
Style="{StaticResource ButtonRevealStyle}"
331+
Command="{Binding OpenWebsite}" />
332+
<Button x:Name="openYMM4Website"
333+
Content="YMM4公式サイト"
334+
Margin="10"
335+
Padding="5"
336+
Style="{StaticResource ButtonRevealStyle}"
337+
Command="{Binding OpenYMM4Website}" />
338+
</WrapPanel>
339+
</Expander>
322340
</ui:SimpleStackPanel>
323341
</GroupBox>
324342
</ui:SimpleStackPanel>

0 commit comments

Comments
 (0)