heygen-com/hyperframes · error · Error

Color grading requires an <img> or <video>; selector matched

Error message

Color grading requires an <img> or <video>; selector matched <${tag}>

What it means

Thrown by selectMediaElement() in packages/cli/src/commands/media-treatment.ts:440. After resolving a single element, it lowercases tagName and requires 'img' or 'video' — color grading only applies to media. Any other tag (div, picture, source, a, span, etc.) throws, naming the matched tag.

Source

Thrown at packages/cli/src/commands/media-treatment.ts:441

    matches = queryIncludingTemplates(document, selector);
  } catch {
    throw new Error(`Invalid selector: ${selector}`);
  }
  if (matches.length === 0) throw new Error(`Selector did not match: ${selector}`);
  if (selectorIndex === undefined && matches.length > 1) {
    throw new Error(
      `Selector matched ${matches.length} elements; use a unique selector or --selector-index`,
    );
  }

  const resolvedIndex = selectorIndex ?? 0;
  const element = matches[resolvedIndex];
  if (!element) {
    throw new Error(`--selector-index ${resolvedIndex} is outside ${matches.length} matches`);
  }
  const tag = element.tagName.toLowerCase();
  if (tag !== "img" && tag !== "video") {
    throw new Error(`Color grading requires an <img> or <video>; selector matched <${tag}>`);
  }
  return { element, selectorIndex: resolvedIndex, tag };
}

export function applyMediaTreatmentToHtml(
  source: string,
  options: ApplyMediaTreatmentOptions,
): ApplyMediaTreatmentResult {
  const { element, selectorIndex, tag } = selectMediaElement(
    source,
    options.selector,
    options.selectorIndex,
  );
  const before = parseStoredGrading(element.getAttribute(HF_COLOR_GRADING_ATTR));

  const value = options.clear ? null : serializeGradingPatch(before, options.grading);

  const changed = element.getAttribute(HF_COLOR_GRADING_ATTR) !== value;

View on GitHub (pinned to c2996c8626)

Solutions

  1. Make the selector resolve to the actual <img> or <video> element (e.g. '#hero img' instead of '#hero').
  2. For <picture>, target the inner <img>; for <video> with <source> children, target the <video> itself.
  3. Inspect the matched tag in the error message and adjust the selector accordingly.

Example fix

<!-- DOM: <div id="hero"><img src="hero.png"></div> -->
# before -- matched <div>
hyperframes media-treatment --selector '#hero' --grading '{...}' --apply
# after -- target the img
hyperframes media-treatment --selector '#hero img' --grading '{...}' --apply
Defensive patterns

Strategy: type-guard

Validate before calling

import { parseHTML } from 'linkedom';

function selectorTargetsMedia(selector: string, source: string): boolean {
  const { document } = parseHTML(source);
  const el = document.querySelector(selector);
  if (!el) return false;
  const tag = el.tagName.toLowerCase();
  return tag === 'img' || tag === 'video';
}

Type guard

function isMediaTag(tag: string): tag is 'img' | 'video' {
  return tag === 'img' || tag === 'video';
}

Try / catch

try {
  selectMediaElement(source, selector);
} catch (error) {
  if (/Color grading requires an <img> or <video>/.test(String(error))) {
    // target the inner img/video instead of the wrapper
    selectMediaElement(source, `${selector} img, ${selector} video`);
  } else throw error;
}

Prevention

When it happens

Trigger: A selector that resolves to a non-media element: a wrapper div around an image, a <picture> element, a <source> inside a <video>, an <a> wrapping an image, etc.

Common situations: Targeting '.hero' which is the wrapper div, not the img inside; targeting <picture> instead of its child <img>; targeting <source> instead of the parent <video>.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/62ed6917161f1509. Report an issue: GitHub.