Catalog

Edit Props (live)

IMAGE
IMAGE
IMAGE
AUDIO
config
config
config

Source

155 lines
import React from "react";
import {
  AbsoluteFill,
  Audio,
  Img,
  OffthreadVideo,
  staticFile,
  useCurrentFrame,
  useVideoConfig,
  interpolate,
  getInputProps,
} from "remotion";
import { KineticCaption } from "./KineticCaption";

interface WordTimestamp {
  word: string;
  start: number;
  end: number;
  emphasis?: boolean;
}

export interface EspnDebriefProps {
  avatarSrc: string;
  screenshotSrc: string;
  screenshotHeight: number; // actual pixel height of the full-page screenshot
  audioSrc: string | null;
  wordTimestamps: WordTimestamp[];
  fps: number;
  durationSec: number;
}

export const EspnDebrief: React.FC<EspnDebriefProps> = ({
  avatarSrc,
  screenshotSrc,
  screenshotHeight,
  audioSrc,
  wordTimestamps,
  fps,
  durationSec,
}) => {
  const frame = useCurrentFrame();
  const { width, height, durationInFrames } = useVideoConfig();

  // Layout: top half = scrolling article, bottom half = avatar
  const splitY = Math.round(height * 0.48); // article gets top 48%
  const avatarH = height - splitY;           // avatar gets bottom 52%

  // Scrolling article — move the tall screenshot upward over the duration.
  // The visible window is splitY pixels tall, the image is screenshotHeight tall.
  // Scroll from top to (screenshotHeight - splitY) over the video duration.
  const maxScroll = Math.max(0, (screenshotHeight || 4000) - splitY);
  const scrollY = interpolate(
    frame,
    [0, durationInFrames],
    [0, -maxScroll],
    { extrapolateRight: "clamp" },
  );

  return (
    <AbsoluteFill style={{ background: "#0a0a0a" }}>
      {/* Top half: scrolling article screenshot */}
      <div
        style={{
          position: "absolute",
          top: 0,
          left: 0,
          width,
          height: splitY,
          overflow: "hidden",
        }}
      >
        {screenshotSrc && (
          <Img
            src={staticFile(screenshotSrc)}
            style={{
              position: "absolute",
              top: scrollY,
              left: 0,
              width: "100%",
            }}
          />
        )}
        {/* Bottom fade so article blends into the avatar section */}
        <div
          style={{
            position: "absolute",
            bottom: 0,
            left: 0,
            right: 0,
            height: 60,
            background: "linear-gradient(transparent, #0a0a0a)",
          }}
        />
      </div>

      {/* Bottom half: avatar video (HeyGen 16:9 output, cover-fit) */}
      <div
        style={{
          position: "absolute",
          top: splitY,
          left: 0,
          width,
          height: avatarH,
          overflow: "hidden",
        }}
      >
        {avatarSrc && (
          <OffthreadVideo
            src={staticFile(avatarSrc)}
            style={{
              width: "100%",
              height: "100%",
              objectFit: "cover",
              objectPosition: "center top",
            }}
          />
        )}
      </div>

      {/* Thin divider line between sections */}
      <div
        style={{
          position: "absolute",
          top: splitY - 1,
          left: 0,
          right: 0,
          height: 2,
          background: "#CC0000",
        }}
      />

      {/* Kinetic captions — over the avatar section */}
      {wordTimestamps.length > 0 && (
        <div
          style={{
            position: "absolute",
            bottom: 60,
            left: 0,
            right: 0,
            display: "flex",
            justifyContent: "center",
          }}
        >
          <KineticCaption words={wordTimestamps} />
        </div>
      )}

      {/* Separate audio track if provided */}
      {audioSrc && (
        <Audio src={staticFile(audioSrc)} volume={1} />
      )}
    </AbsoluteFill>
  );
};

Schema (Zod)

// Auto-extracted from Props interface — see source for authoritative types.
// Component: EspnDebrief
// 7 props detected · 4 port(s)

Espn Debrief

espn_debrief

Espn Debrief — Andy's hand-crafted Remotion scene. 7 props. avatarSrc:IMAGE, screenshotSrc:IMAGE, screenshotHeight:IMAGE, +1 more.

Workflow Inputs (4)

  • Avatar Src
    avatarSrc
    IMAGERequired
  • Screenshot Src
    screenshotSrc
    IMAGERequired
  • Screenshot Height
    screenshotHeight
    IMAGERequired
  • Audio Src
    audioSrc
    AUDIORequired

Config Fields (3)

  • fps
  • durationSec
  • wordTimestamps

Meta

Updated
4/21/2026