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,