reelcn
GitHubBack to landing

Podcast teaser

Vertical teaser cut from a podcast: one clip of the audio with its waveform, the show, and a pull quote.

PodcastTeaser, defaults
PodcastTeaser, defaults thumbnail, 16:9

Installation

npx shadcn add https://www.reelcn.dev/r/podcast-teaser.json

Files land in src/reelcn/ with the items it builds on. Dependencies: remotion, zod.

Register the composition

<Composition
  id="PodcastTeaser"
  component={PodcastTeaser}
  schema={podcastTeaserSchema}
  defaultProps={{ ...podcastTeaserDefaults, audio: staticFile("episode-42.mp3"), clipStart: 734, clipEnd: 762 }}
  calculateMetadata={podcastTeaserMetadata}
  width={1080}
  height={1920}
  fps={30}
  durationInFrames={1}
/>

Render

npx remotion render PodcastTeaser # 16:9
npx remotion render PodcastTeaser --width=1080 --height=1920 # 9:16

Props

PropTypeDescription
clipStart*numberSeconds into the audio where the clip starts.
clipEnd*numberSeconds into the audio where the clip ends.
quote*string
show*string
theme"daylight" | "midnight" | "mono" | "ledger" | "afterglow" | "chromewave" | "neon" | "paper" | "sunset"Theme preset. Inherits the surrounding `ThemeProvider` when omitted.
brand{ accent?: string; logo?: string; font?: string; }
sfxbooleanPlays a soft whoosh as each scene starts.
musicstringBackground music file, looped under everything at 25% volume.
audiostringThe episode audio. Beat-driven bars stand in when omitted.
coverstring

Built from

Use

  • Promoting an episode with its best thirty seconds

Avoid

  • Whole episodes or segments with captions. Use audiogram

Make it with an agent

With the reelcn agent skill installed, ask for the video in plain words, such as "make a launch video for Relay with reelcn". The skill picks a template, sets its props, reviews a contact sheet and renders.

Guides

Components take their colors from a theme, their timing from motion props and their layout from the canvas.

Source

registry/items/podcast-teaser.tsx
/**
 * @title Podcast Teaser
 * @category templates
 * @description Vertical teaser cut from a podcast: one clip of the audio with its waveform, the show, and a pull quote.
 * @duration data-driven
 * @use Promoting an episode with its best thirty seconds
 * @avoid Whole episodes or segments with captions. Use `audiogram`
 * @tags podcast, teaser, clip, short, template
 * @example
 * <Composition
 *   id="PodcastTeaser"
 *   component={PodcastTeaser}
 *   schema={podcastTeaserSchema}
 *   defaultProps={{ ...podcastTeaserDefaults, audio: staticFile("episode-42.mp3"), clipStart: 734, clipEnd: 762 }}
 *   calculateMetadata={podcastTeaserMetadata}
 *   width={1080}
 *   height={1920}
 *   fps={30}
 *   durationInFrames={1}
 * />
 */
import type { CalculateMetadataFunction } from "remotion";
import { z } from "zod";
import { AudiogramLayout } from "./audiogram";
import { STORY_FPS } from "./story";
import { TemplateFrame, templateSchema } from "./storyboard";

export const podcastTeaserSchema = templateSchema
  .extend({
    /** The episode audio. Beat-driven bars stand in when omitted. */
    audio: z.string().optional(),
    /** Seconds into the audio where the clip starts. */
    clipStart: z.number().min(0),
    /** Seconds into the audio where the clip ends. */
    clipEnd: z.number().positive(),
    quote: z.string(),
    show: z.string(),
    cover: z.string().optional(),
  })
  .refine((props) => props.clipEnd > props.clipStart, {
    message: "clipEnd must be after clipStart",
    path: ["clipEnd"],
  });

export type PodcastTeaserProps = z.infer<typeof podcastTeaserSchema>;

export const podcastTeaserDefaults: PodcastTeaserProps = {
  clipStart: 0,
  clipEnd: 8,
  quote: "The best render farm is the one you never have to think about.",
  show: "Frame by Frame",
};

export function PodcastTeaser({ audio, clipStart, quote, show, cover, ...fields }: PodcastTeaserProps) {
  return (
    <TemplateFrame fields={fields}>
      <AudiogramLayout audio={audio} clipStart={clipStart} show={show} cover={cover} quote={quote} />
    </TemplateFrame>
  );
}

export const podcastTeaserMetadata: CalculateMetadataFunction<PodcastTeaserProps> = ({ props }) => ({
  fps: STORY_FPS,
  durationInFrames: Math.round((props.clipEnd - props.clipStart) * STORY_FPS),
});

On this page