diff --git a/voicevox-remotion-template/src/lib/VQRemotionLib/components/VQCharacterAvatar.tsx b/voicevox-remotion-template/src/lib/VQRemotionLib/components/VQCharacterAvatar.tsx index d7d752b..24e662f 100644 --- a/voicevox-remotion-template/src/lib/VQRemotionLib/components/VQCharacterAvatar.tsx +++ b/voicevox-remotion-template/src/lib/VQRemotionLib/components/VQCharacterAvatar.tsx @@ -184,6 +184,7 @@ imagePath={imagePath ?? avatar.imagePath} mouthImageDir={avatar.mouthImageDir} mouth={mouth} + mouthCoverLayer={avatar.mouthCoverLayer} width={imageWidth} height={avatar.imageLayout?.height} maxHeight={avatar.imageLayout?.maxHeight ?? 360} diff --git a/voicevox-remotion-template/src/lib/VQRemotionLib/components/VQLipSyncedStandeeImage.tsx b/voicevox-remotion-template/src/lib/VQRemotionLib/components/VQLipSyncedStandeeImage.tsx index 32d421b..06f746a 100644 --- a/voicevox-remotion-template/src/lib/VQRemotionLib/components/VQLipSyncedStandeeImage.tsx +++ b/voicevox-remotion-template/src/lib/VQRemotionLib/components/VQLipSyncedStandeeImage.tsx @@ -1,11 +1,12 @@ import React from "react"; import {Img, staticFile} from "remotion"; -import type {VQMouthShape} from "../types"; +import type {VQMouthCoverLayer, VQMouthShape} from "../types"; export type VQLipSyncedStandeeImageProps = Readonly<{ imagePath: string; mouthImageDir: string; mouth: VQMouthShape; + mouthCoverLayer?: VQMouthCoverLayer; width: number | string; maxHeight: number | string; height?: number | string; @@ -13,12 +14,26 @@ filter?: string; }>; +// 用途: 立ち絵の元口を口形画像の直前で隠すかどうか判定する。 +// 使用方法: VQLipSyncedStandeeImage 内で mouthCoverLayer と現在の mouth を渡して使う。 +// オプションや引数詳細: visibleWhen が "activeMouth" の場合は rest で非表示にし、hiddenMouths に含まれる口形でも非表示にする。 +const shouldShowMouthCoverLayer = ( + mouthCoverLayer: VQMouthCoverLayer | undefined, + mouth: VQMouthShape +) => + Boolean( + mouthCoverLayer && + !mouthCoverLayer.hiddenMouths?.includes(mouth) && + (mouthCoverLayer.visibleWhen !== "activeMouth" || mouth !== "rest") + ); + export const VQLipSyncedStandeeImage: React.FC< VQLipSyncedStandeeImageProps > = ({ imagePath, mouthImageDir, mouth, + mouthCoverLayer, width, maxHeight, height, @@ -47,6 +62,21 @@ filter, }} /> + {shouldShowMouthCoverLayer(mouthCoverLayer, mouth) ? ( +
+ ) : null}
;
+export type VQMouthCoverLayer = Readonly<{
+ left: number | string;
+ top: number | string;
+ width: number | string;
+ height: number | string;
+ backgroundColor: string;
+ borderRadius?: number | string;
+ opacity?: number;
+ visibleWhen?: "always" | "activeMouth";
+ hiddenMouths?: readonly VQMouthShape[];
+}>;
+
export type VQAvatarDefinition = Readonly<{
kind?: string;
imagePath: string;
mouthImageDir: string;
+ mouthCoverLayer?: VQMouthCoverLayer;
imageLayout?: VQAvatarImageLayout;
accentColor: string;
nameplatePosition?: VQAvatarNameplatePosition;
diff --git a/voicevox-remotion-template/src/standee-sets.ts b/voicevox-remotion-template/src/standee-sets.ts
index a6e844b..f60d052 100644
--- a/voicevox-remotion-template/src/standee-sets.ts
+++ b/voicevox-remotion-template/src/standee-sets.ts
@@ -1,5 +1,6 @@
import type {
VQIdleAvatarAnimationType as IdleAvatarAnimationType,
+ VQMouthCoverLayer,
VQSpeakingAvatarAnimationType as SpeakingAvatarAnimationType,
} from "./lib/VQRemotionLib";
@@ -21,6 +22,7 @@
description: string;
imagePath: string;
mouthImageDir: string;
+ mouthCoverLayer?: VQMouthCoverLayer;
imageLayout: StandeeImageLayout;
}>;
@@ -98,6 +100,16 @@
description: "小夜の立ち絵(SAYONAKA 素材)",
imagePath: "image/sayo_by_sayonaka/variants/sayo_001_neutral.png",
mouthImageDir: "image/sayo_by_sayonaka/rhubarb-mouths",
+ mouthCoverLayer: {
+ left: "48.4%",
+ top: "33.65%",
+ width: "4.35%",
+ height: "2.25%",
+ backgroundColor: "#fff8ef",
+ borderRadius: "50%",
+ visibleWhen: "activeMouth",
+ hiddenMouths: ["rest", "closed"],
+ },
imageLayout: {
width: 520,
maxHeight: 720,