Newer
Older
remotion_docker_devcontainer / voicevox-remotion-template / src / lib / VQRemotionLib / components / VQLipSyncedStandeeImage.tsx
import React from "react";
import {Img, staticFile} from "remotion";
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;
  transform?: string;
  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,
  transform,
  filter,
}) => {
  return (
    <div
      style={{
        position: "relative",
        zIndex: 1,
        width,
        height,
        maxHeight,
        lineHeight: 0,
        transform,
      }}
    >
      <Img
        src={staticFile(imagePath)}
        style={{
          width: "100%",
          height: height ? "100%" : undefined,
          maxHeight,
          objectFit: "contain",
          filter,
        }}
      />
      {shouldShowMouthCoverLayer(mouthCoverLayer, mouth) ? (
        <div
          style={{
            position: "absolute",
            left: mouthCoverLayer?.left,
            top: mouthCoverLayer?.top,
            width: mouthCoverLayer?.width,
            height: mouthCoverLayer?.height,
            backgroundColor: mouthCoverLayer?.backgroundColor,
            borderRadius: mouthCoverLayer?.borderRadius ?? "999px",
            opacity: mouthCoverLayer?.opacity ?? 1,
            pointerEvents: "none",
          }}
        />
      ) : null}
      <Img
        src={staticFile(`${mouthImageDir}/${mouth}.png`)}
        style={{
          position: "absolute",
          inset: 0,
          width: "100%",
          height: "100%",
          objectFit: "contain",
          pointerEvents: "none",
        }}
      />
    </div>
  );
};