Integration by Closed Caption Creator

<iFrame> Integration

Integrate our powerful subtitle editor in your web application or platform.

Give your users professional-grade caption editing without building it yourself. Our new IFrame Embed support lets you integrate our intuitive caption editor into your platform with minimal effort. Your users can edit, review, and correct captions without ever leaving your ecosystem, while you save valuable development time and resources.

Browser-based Subtitle Editor

Subtitle Editor, Ready for Your Platform.

Embed our editor on your website using an IFrame tag and a couple lines of code.

Embedding our caption editor is remarkably simple—just a few lines of code to get started. With support for over 30 different closed caption formats, we seamlessly integrate with virtually any captioning workflow. Whether you're handling standard SRT files or specialized broadcast formats, our editor has you covered.

Designed specifically for broadcast manufacturers and video platforms, our embedded editor fills the critical gap for services that support video captioning but lack editing capabilities. We're committed to your success with free technical support and training throughout the development process, ensuring a smooth implementation that meets your specific needs.

  • Easy to setup and configure
  • No backend required
  • Import and publish captions
  • Import multiple subtitle tracks and languages
  • Media support for HLS streams,  cloud storage, YouTube URL, Vimeo links, and more
  • Theming options for seamless brand integration
  • Flexible pricing options
ElevenLabs Display Photo showing a female robot and the text "Eleven Labs AI Voice Generator & Best Text to Speech"

How do I add a caption editor to my web application?

Embed Closed Caption Creator's IFrame Remote: drop an iframe that points at the editor into the page and include PenPal JS for secure two-way communication. The editor supports over 30 caption formats, loads HLS streams, cloud storage, YouTube and Vimeo media, needs no backend, and can be themed to match your product.

Have Questions?

Contact our team for a free demo.

Book a Demo
Developer Docs

IFrame Remote Documentation

Build your own integration with Closed Caption Creator using our IFrame Remote.

Leverage Closed Caption Creator's incredible flexibility for all your captioning and timed-text metadata needs. This section provides the essential reference documentation—including setup guides, API details, and practical examples—to get your integration up and running fast.

Introduction

Overview

The Closed Caption Creator IFrame Remote provides a powerful, embeddable subtitle and timed-text editor designed to seamlessly integrate into your web-based applications. Instead of building complex captioning functionalities from scratch, you can leverage our professional-grade editor to empower your users with advanced editing capabilities directly within your platform.

How It Works: Communication with PenPal JS

Our IFrame Remote is designed for secure and efficient cross-window communication using PenPal JS. This lightweight library facilitates reliable two-way communication between your parent application and the embedded editor, allowing you to:

  • Programmatically control the editor's behavior (e.g., load media, import captions, apply settings).
  • Receive updates and metadata including markers, captions, speakers, etc.

This approach ensures a robust and flexible integration, enabling you to build highly interactive and customized captioning workflows.

Getting Started Resources

To help you begin immediately, here are two essential links:

  • IFrame POC System: Use the following link as the source attribute in your IFrame tag to begin: https://iframe-demo.closedcaptioncreator.com
  • PenPal JS CDN: Include this library in your parent application to enable communication with the IFrame Remote: https://unpkg.com/penpal@^7/dist/penpal.min.js

Dive into the Getting Started section below to begin your integration.

Getting Started

This section will walk you through the fundamental steps to embed the Closed Caption Creator IFrame Remote into your web application and establish communication for controlling the editor.

Prerequisites

To follow along, you should have a basic understanding of HTML and JavaScript.

Step 1: Import Penpal JS

Import PenPal JS using NPM, or CDN.

HTML Code

<!DOCTYPE html>
<html lang="en">
<head>
    ...
</head>
<body>
    ...
    <script src="https://unpkg.com/penpal@^7/dist/penpal.min.js"></script>
</body>
</html>

Step 2: Embed the IFrame

Begin by adding an element to your HTML where you want the editor to appear. Ensure it points to the Closed Caption Creator IFrame demo URL as its src (https://iframe-demo.closedcaptioncreator.com). Giving your iframe an id (e.g., editor) will make it easy to reference in JavaScript.

HTML Code

<div class="iframe-container">
    <iframe id="editor" 
        src="https://iframe-demo.closedcaptioncreator.com/" 
        class="w-100 rounded" 
        style="height: 90vh;" 
        frameborder="0" 
        allow="accelerometer; autoplay; clipboard-write; clipboard-read; encrypted-media"
        allowfullscreen>
    </iframe>
</div>
  • id="editor": Used to easily select the iframe element in JavaScript.
  • src="https://iframe-demo.closedcaptioncreator.com/": This is the URL of the Closed Caption Creator IFrame editor itself.
  • style="height: 90vh;": Sets the initial height of the iframe to take up 90% of the viewport height. Adjust this as needed for your layout.
  • allowfullscreen: Enables the ability for the editor toggle fullscreen mode.
  • allow="accelerometer; autoplay; clipboard-write; clipboard-read; encrypted-media": Clipboard access and encrypted-media are required.

JavaScript Code

document.addEventListener('DOMContentLoaded', () => {
    const iframe = document.getElementById('editor');
});

Step 3: Establish Connection with PenPal

Once the IFrame is loaded, you'll establish a connection using PenPal. This connection provides a remote object that allows you to call functions exposed by the IFrame, and optionally, define methods on the parent window that the IFrame can call.

JavaScript Code

document.addEventListener('DOMContentLoaded', () => {
    const iframe = document.getElementById('editor'); // Get reference to your iframe
    let iframeConnection = null; // Will store the remote object for calling iframe functions

    const connectToIframe = async () => {
        try {
            // Create a messenger to communicate with the iframe
            const messenger = new Penpal.WindowMessenger({
                remoteWindow: iframe.contentWindow,
                allowedOrigins: ['*'] // IMPORTANT: For production, restrict this to your iframe's origin!
            });

            // Connect to the iframe
            const connection = Penpal.connect({
                messenger,
                // Define methods the parent exposes to the iframe (optional)
                methods: {
                    //...
                }
            });

            // Wait for the connection to be established
            const remote = await connection.promise;
            console.log('Connected to iframe');
            iframeConnection = remote; // Store the remote object for later use

            // ... (initial editor configuration calls will go here)
            
        } catch (error) {
            console.error('Connection error:', error);
        }
    };

    // Initialize connection when iframe loads
    iframe.addEventListener('load', connectToIframe);
});
  • Penpal.WindowMessenger: Configures how PenPal will communicate. remoteWindow points to the iframe's content window. allowedOrigins: ['*'] is set for demo purposes, but you must specify the exact origin(s) of your iframe (e.g., https://iframe-demo.closedcaptioncreator.com) in a production environment for security.
  • Penpal.connect: Initiates the connection. The methods object allows you to define functions on your parent page that the IFrame can call (e.g., parentMethod, notifyParent).
  • connection.promise: This Promise resolves once the connection to the IFrame is successfully established. The resolved value (remote) is the object containing all the functions exposed by the IFrame Remote that you can call.

Step 4: Initial Editor Configuration

Once connected, you can immediately start interacting with the editor. A common first step is to configure basic project settings and load media/captions. This often involves showing a loading modal to the user.

JavaScript Code

await remote.updateModalTitle("Project Import");
await remote.toggleStatusModal(true); // Show the modal regardless of its current state
await remote.updateModalStatus({ // Update modal progress and message
    progress : 5,
    msg : "Setting project settings..."
});

/* Set Project Settings (framerate, dropFrame, incode) */
await remote.setProjectName("IFrame Demo Project");
await remote.setProjectDescription("This is a demo project for iframe communication.");
await remote.setProjectFrameRate(29.97);
await remote.setProjectDropFrame(true);
await remote.setProjectIncode("00:00:00:00");

/* Step 2. Loading Subtitle File */
await remote.updateModalStatus({
    progress : 50,
    msg : "Loading Subtitles..."
});

await remote.importSubtitle(subtitleUrl, subtitleProfile);

/* Step 3. Loading Video File */
await remote.updateModalStatus({
    progress : 75,
    msg : "Loading media..."
});

await remote.importMedia(mediaUrl, "https://m.media-amazon.com/images/M/MV5BMTljNGI3NTAtYTU2ZC00ZGQzLTg2ZDAtNzQ1NDk1YzBiY2M5XkEyXkFqcGc@._V1_QL75_UX522_.jpg");

/* Audio Peak Data */
await remote.setMediaPeaksPath(audioPeaksUrl);
await remote.toggleTimeline(true);

await remote.toggleStatusModal(false); // Hide the modal
await remote.alertUser({ // Show a success alert
    title : "Project Import",
    text : "Project imported successfully!",
});
  • This sequence demonstrates calling various functions on the remote object to set up the project, update a status modal, import media, and load subtitles.
  • remote.updateModalTitle(), remote.toggleStatusModal(), and remote.updateModalStatus() are examples of controlling the editor's UI state.
  • remote.setProjectName(), remote.setProjectFrameRate(), etc., configure the project's metadata.
  • remote.importSubtitle() and remote.importMedia() are key functions for loading content into the editor.
  • remote.setMediaPeaksPath() and remote.toggleTimeline() control additional visual elements like audio waveforms.

You now have the fundamental structure for embedding and communicating with the Closed Caption Creator IFrame Remote. Proceed to the Functions Reference to explore all available methods for deeper integration.

Support / Feedback

We are committed to ensuring your successful integration of the Closed Caption Creator IFrame Remote. If you encounter any questions during development, require personalized assistance, or have feedback on our documentation or product, please don't hesitate to reach out. We offer dedicated developer training and direct support to help with all aspects of your integration. Visit our Contact Page to connect with our team.

Functions

The Closed Caption Creator IFrame Remote exposes a set of asynchronous methods that your parent application can call via the PenPal connection. These methods allow you to control various aspects of the editor, from loading content to managing project settings and retrieving data.

All calls to these functions should be awaited, as they return Promises.

This is the complete reference for methods on the Penpal remote object exposed by the Closed Caption Creator iframe editor. After you connect with PenPal (see the IFrame Embed guide), call methods with await remote.methodName(...).

Some methods return a result object shaped like { success, message, ... }; others return void or raw data (a string, array, or object). Always await the call. A typical first workflow is: set project settings → importMedia → importSubtitle → edit → exportSubtitle or getProjectData.

IFrame Remote functions, their arguments, descriptions, and return values
Function Name Arguments Description Return
Modal Control
getVersion() None Returns the current application version. Promise<string>
updateModalStatus(statusInfo) statusInfo: { progress: number, msg: string } Updates the progress bar and status message in the IFrame remote modal. Promise<boolean>
toggleStatusModal(show) show: boolean Controls the IFrame remote status modal. If show is true, displays the modal. If falsy: hides the modal when it is already open, otherwise shows it. Promise<void>
updateModalTitle(title) title: string Updates the title text displayed in the IFrame remote modal header. Promise<boolean>
completeImport() None Displays a success alert and closes the modal after a successful import operation. Promise<void>
failImport(msg) msg: string Displays an error alert with the provided message and closes the modal after a failed import. Promise<void>
alertUser(alertObj) alertObj: { title?: string, text?: string } Displays a SweetAlert2 modal with a custom title and message to alert the user. Promise<void>
Project Settings
setProjectId(id) id: string Sets the unique identifier for the current project. Promise<void>
setProjectName(name) name: string Sets the display name of the current project. Promise<void>
setProjectDescription(description) description: string Sets the description for the current project. Promise<void>
setProjectFrameRate(frameRate) frameRate: number (e.g., 23.976, 25, 29.97, 30) Sets the project's video frame rate. Promise<void>
setProjectDropFrame(dropFrame) dropFrame: boolean Sets whether the project uses drop-frame timecode. Promise<void>
setProjectIncode(incode) incode: string (timecode string, e.g., "00:00:00:00") Sets the project's incode (start timecode). Promise<void>
setProjectState(state) state: object (Partial project state object) Updates multiple project state properties at once. Promise<void>
Display Settings
setDisplaySettings(settings) settings: { displayWidth?: number (1-100), displayHeight?: number (1-50), xPadding?: number (0-50), yPadding?: number (0-50), lineSpacing?: number (0-50) } Updates caption display settings including width, height, padding, and line spacing. Settings are validated and saved to localStorage. Promise<{ success: boolean, message: string, settings?: object }>
enableSubtitleMode() None Enables subtitle preview mode (sets non-CC mode for rendering). Promise<void>
enableCaptionMode() None Enables caption preview mode (sets CC mode for rendering). Promise<void>
setPreviewFontSize(size) size: number Sets the preview font size. Promise<void>
setPreviewFontFamily(family) family: string Sets the preview font family. Promise<void>
App Theme / Branding
setTheme(tokens) tokens: object (aliases such as primary, primaryRgb, bodyBg, bodyBgRgb, bodyColor, bodyColorRgb, secondaryColor, borderColor, fontFamily, linkColor, linkHoverColor, light, danger, warning, success, info — or raw --bs-* CSS variable names) Applies structured UI chrome theme overrides as CSS variables on the document root. Hex primary auto-derives primaryRgb when omitted. Call after the PenPal connection resolves and before media import when possible. Does not recolor the Konva timeline. Separate from caption QC loadStyleGuide. Promise<{ success: boolean, message: string }>
loadCustomStyles(cssText) cssText: string (CSS stylesheet text) Injects or replaces a runtime <style> block for advanced host chrome theming. Prefer setTheme for simple color/font overrides. Does not remove the build-time system theme file. Promise<{ success: boolean, message: string }>
clearTheme() None Removes runtime theme CSS and token overrides applied via setTheme / loadCustomStyles. The build-time assets/css/themes/{system}.css stylesheet (if present) remains loaded. Promise<{ success: boolean, message: string }>
Keyboard Shortcuts
getKeyboardShortcuts() None Retrieves the current keyboard shortcuts. Returns saved shortcuts if available, otherwise returns platform defaults. Promise<{ success: boolean, data: array, count: number, message: string }>
setKeyboardShortcuts(shortcuts) shortcuts: array|string (Array of shortcut groups, or JSON string, with { name: string, shortcuts: [{ name: string, keyCmd: string, description?: string, shortName?: string }] }) Updates matching keyboard shortcuts by shortcut name and saves them to localStorage. Partial shortcut payloads are merged with current shortcuts; unknown shortcut names are ignored. Promise<{ success: boolean, message: string, updatedCount: number, ignoredCount: number, shortcuts?: array }>
Media & Timeline
setMediaPeaksPath(path) path: string (URL to audio peaks JSON) Sets the URL for the audio waveform peaks data to be displayed in the timeline. Promise<void>
setMediaPeaksData(data) data: object (Audio peaks data object) Directly sets the audio waveform peaks data as an object, bypassing a URL fetch. Promise<void>
useFallbackTimeline() None Deprecated. Sets projectState.media.useFallback to true. The legacy fallback timeline no longer exists in the Konva-based timeline; kept as a no-op so external iframe consumers do not crash. Promise<void>
useDefaultTimeline() None Deprecated companion to useFallbackTimeline(). Sets projectState.media.useFallback to false. Promise<void>
toggleTimeline(enable) enable: boolean Controls timeline visibility. If enable is true, shows the timeline. If false, flips the current visibility (does not mean “hide”). Promise<void>
setPoster(thumbnailUrl) thumbnailUrl: string Sets the poster image (thumbnail) for the video player. Promise<void>
importMedia(mediaUrl, thumbnailUrl, mediaType, storageType) mediaUrl: string
thumbnailUrl: string (Optional)
mediaType: string (e.g., "video/mp4", default "video/mp4")
storageType: string (e.g., "Cloud Storage", "Vimeo", "YouTube", "HLS Manifest", default "Cloud Storage")
Loads a video or audio file into the editor from a specified URL. Promise<{ success: boolean, message: string }>
Quick Tools
setQuickToolsSettings(settings) settings: { selected?: string, styles?: boolean, summary?: boolean, search?: boolean, spellCheck?: boolean, timing?: boolean, liveCaptioning?: boolean, qc?: boolean, errorNav?: boolean, templates?: boolean, voices?: boolean, speakers?: boolean, tags?: boolean, notes?: boolean, markers?: boolean, videoFilters?: boolean, manualQc?: boolean, media?: boolean } Toggles Quick Tools panel visibility flags and optionally selects the active panel. Valid selected panel IDs: summary, styles, findAndReplace, spellCheck, timingAndSync, liveCaptioning, qcAndReview, manualQc, errors, speakers, tags, markers, notes, videoFilters, eventTemplates, voices, media. If the requested panel is disabled, falls back to the first visible panel. Promise<{ success: boolean, message?: string, quickTools?: object }>
selectQuickToolsPanel(panelName) panelName: string (case-insensitive panel ID; see setQuickToolsSettings for valid IDs) Selects an enabled Quick Tools panel and switches editor mode (timing for Timing & Sync, edit otherwise). Promise<{ success: boolean, message?: string, selected?: string }>
Transcript Sync
setTranscript(transcript, profile) transcript: object (Transcription data object)
profile: string (Source profile, e.g., "deepgram", default "deepgram")
Sets the raw transcript data to be processed by the editor. Promise<void>
checkSync() None Runs transcript-to-caption alignment against the currently selected event group using transcript data previously set via setTranscript. Does not apply timing changes; use for drift analysis only. Requires a selected event group with timed events and a loaded transcript. Promise<{ success: boolean, message: string, drift: number|null, driftAnalysis: object|null, missingDialogueCount: number }>
resyncEventGroup() None Same prerequisites as checkSync(), but applies aligned start/end times to the selected event group and records an undo history entry. Promise<{ success: boolean, message: string, drift: number|null, driftAnalysis: object|null, missingDialogueCount: number }>
loadTranscript() None Processes the transcript data previously set via setTranscript and imports it into the editor as a new event group. Promise<{ success: boolean, message: string }>
Subtitle Import & Export
importSubtitle(subtitleUrl, profile, decodeOptions, evgOptions, autoFormatOptions, forced, target) subtitleUrl: string (URL to subtitle file)
profile: string (Subtitle format profile, e.g., "subRip", "webVtt")
decodeOptions: object (Optional, format-specific decoding options)
evgOptions: object (Optional, event group options like type, name, language; ignored when target.eventGroupId is set)
autoFormatOptions: { enable?: boolean, maxLines?: number, maxChars?: number, minDuration?: number, allowOrphanWords?: boolean, selective?: boolean } (Optional; on merge, only the newly imported events are formatted)
forced: boolean (Optional, default false; when true, marks all imported events as forced)
target: { eventGroupId?: string, importOption?: "merge"|"replace", track?: "A"|"B"|"C"|"D" } (Optional; import into an existing Event Group instead of creating a new one. importOption defaults to "replace". track is applied to imported events on merge into a non-multiview group)
Loads a subtitle file from a URL into the editor, converting it to the internal project format. Creates a new event group by default, or merges/replaces into an existing group when target.eventGroupId is provided. Optionally applies auto-formatting and a track assignment. Promise<{ success: boolean, message: string, id?: string, index?: number, importedCount?: number }>
exportSubtitle(profile, encodeOptions, saveAsFile, forcedOption, eventGroupId, exportTracks) profile: string (Target subtitle format profile)
encodeOptions: object (Optional, format-specific encoding options)
saveAsFile: boolean (If true, triggers a file download; otherwise, returns data)
forcedOption: string (Optional, default "include"; values: "include", "exclude", "only")
eventGroupId: string (Optional; ID of the Event Group to export. When omitted, uses the currently selected Event Group)
exportTracks: string[] (Optional, default ["none", "A", "B", "C", "D"]; only events on the listed tracks are exported. "none" matches events without a track assignment. An empty array or all values selected exports everything)
Exports captions from a specified Event Group (or the selected Event Group by default) into a specified format, optionally filtered by track. Can return the data or trigger a file download. Promise<{ success: boolean, message: string, data?: string, fileName?: string }>
Markers
getMarkers() None Retrieves the current marker data from the editor, including all marker lists and their markers. Promise<object> (Copy of marker state)
setMarkers(markers) markers: object (Marker state object with lists and selected properties) Sets the entire marker state for the editor, allowing you to load custom markers and lists. Promise<void>
createMarkerList(markerList) markerList: { name: string, color?: string } Creates a new named marker list within the editor. Promise<void>
insertMarker(marker, markerList) marker: object (_Marker object, e.g., { time: number, comment: string })
markerList: number (Optional, 0-based index of the target marker list, default 0)
Inserts a new marker into a specified marker list by list index (not list ID). Promise<void>
Style Guide & QC
loadStyleGuide(styleGuideData) styleGuideData: object (Style guide configuration object) Loads or updates a style guide in the editor's local storage. If a guide with the same ID exists, it will be updated. Promise<{ success: boolean, message: string, id?: string }>
getStyleGuides() None Retrieves all style guides currently stored in local storage. Promise<{ success: boolean, data: array, count: number, message: string }>
qcEventGroup(eventGroupId, styleGuideId) eventGroupId: string (ID of event group to validate)
styleGuideId: string (ID of style guide to use for validation)
Runs quality control validation on a specified event group using a specified style guide. Returns all validation errors found. Promise<{ success: boolean, errors: array, errorCount: number, message: string, eventGroupId?: string, eventGroupName?: string, styleGuideId?: string, styleGuideName?: string }>
Event Groups
getSelectedEventGroupId() None Retrieves the ID and details of the currently selected event group in the editor, including which letter tracks (A–D) are in use. Promise<{ success: boolean, id: string|null, index: number|null, name?: string, type?: string, eventCount?: number, tracks?: string[], message: string }>
getEventGroupInfo() None Returns summary details for all event groups currently loaded in the editor, including which letter tracks (A–D) are in use per group. Promise<array> (Array of { id, name, type, eventCount, tracks })
addEventGroup(eventGroupOptions) eventGroupOptions: object (Optional, Event Group options like { name?: string, type?: string, language?: string, linkedGroup?: string|false, allowOriginalEdit?: boolean, rtl?: boolean, events?: array }) Creates a new event group in the project, selects it, and records the change in history. Promise<{ success: boolean, message: string, id?: string, index?: number, eventCount?: number, eventGroup?: object }>
removeEventGroup(eventGroupId) eventGroupId: string (ID of event group to remove) Removes an event group by ID and resets selection to the first remaining group when available. Returns { success: false, message } when the group is locked; otherwise returns nothing after the group is removed. Promise<void | { success: false, message: string }>
mergeEventGroups(sourceGroups, eventGroupOptions, orderByStart, groupTracks) sourceGroups: string[] (Array of source event group IDs)
eventGroupOptions: object (Optional, properties for the merged event group)
orderByStart: boolean (Optional, default true; sorts merged events by start time)
groupTracks: object (Optional; map of source event group ID to a track letter "A"|"B"|"C"|"D". Events copied from that group are assigned the track in the merged result. Groups omitted from the map keep their existing track values)
Merges events from multiple source event groups into a new event group and selects the merged result. Optionally assigns a letter track per source group. Promise<{ success: boolean, message: string, id?: string, index?: number, eventCount?: number, sourceCount?: number }>
Speakers
addSpeaker(speaker) speaker: string | { id?: string, name: string, colour?: string, color?: string } Adds a speaker to the current project. String input is used as the speaker name; object input may include an id and colour (colour or color both accepted). Promise<{ success: boolean, message: string, speaker?: object }>
getSpeakers() None Retrieves all speakers currently defined in the project. Promise<array> (Array of speaker objects)
Project Data
loadProjectData(ccprj) ccprj: object or string (Closed Caption Project JSON data) Loads a complete Closed Caption Creator project (in .ccprj format) into the editor. Resets the undo stack and marks the loaded state as the saved baseline for hasUnsavedChanges. Promise<{ success: boolean, message: string }>
getHistory() None Returns a lightweight summary of the undo stack (action names and event-group indices only; not full snapshots). Use to inspect undo position without fetching the entire project. Promise<{ position: number, actions: array }> (Each action is { name: string, eventGroup: number })
hasUnsavedChanges() None Returns true when the undo-stack position differs from the last saved baseline. loadProjectData establishes that baseline. Undoing back to the baseline clears the dirty flag. Promise<boolean>
getProjectData() None Retrieves the entire current project data from the editor in .ccprj JSON format. Promise<object> (Complete project JSON)
Event Selection & Navigation
getSelectedEvents() None Retrieves all currently selected events from the active event group. Promise<array> (Array of selected event objects)
selectEventById(id) id: string (Event ID) Selects a specific event in the current event group by its unique ID. Promise<void>
selectEventByIndex(index) index: number | array (Event index or array of indices) Selects one or more events in the current event group by their index position(s). Accepts a single index or an array of indices for multiple selection. Promise<void>
scrollToEventById(id) id: string (Event ID) Scrolls the event list to make the event with the specified ID visible in the editor. Promise<void>
scrollToEventByIndex(index) index: number (Event index) Scrolls the event list to make the event at the specified index visible in the editor. Promise<void>
Lock State
enableVideoLock() None Enables the video lock (video follows captions). Promise<void>
disableVideoLock() None Disables the video lock (video follows captions). Promise<void>
enableCaptionLock() None Enables the caption lock (captions follow video). Promise<void>
disableCaptionLock() None Disables the caption lock (captions follow video). Promise<void>
enablePreviewLock() None Shows the on-screen caption/subtitle preview overlay on the video player. Promise<void>
disablePreviewLock() None Hides the on-screen caption/subtitle preview overlay on the video player. Promise<void>
Auto Formatting & Correction
autoFormat(evgIndex, maxLines, maxChars, minDuration, allowOrphanWords, selective) evgIndex: number (Event group index)
maxLines: number (Maximum lines per event)
maxChars: number (Maximum characters per line)
minDuration: number (Minimum event duration in seconds)
allowOrphanWords: boolean (Whether to allow single words on a line)
selective: boolean (Optional, default false; when true, only formats events that fail line/char checks)
Automatically formats the specified event group by splitting/merging events based on line and character limits. Returns { success: false, message } when the group is locked; otherwise returns nothing after formatting. Promise<void | { success: false, message: string }>
autoCorrectReadingSpeed(maxCps, minDuration, maxDuration, minFrameGap, allowMerge) maxCps: number (Maximum characters per second)
minDuration: number (Minimum event duration in seconds)
maxDuration: number (Maximum event duration in seconds)
minFrameGap: number (Minimum frame gap between events)
allowMerge: boolean (Whether to merge brief neighboring events when they cannot be corrected by extending alone; default true)
Automatically adjusts event timing to meet reading speed requirements using the shared no-cascade correctReadingSpeed algorithm. Optionally merges brief events; events that cannot be corrected are marked unapproved. Promise<void>
fixEventOverlap(evgIndex) evgIndex: number (Optional; event group index, defaults to the currently selected event group) Resolves overlapping events in the specified event group using the project frame rate and minimum frame gap (same behavior as Format > Fix Event Overlap). Promise<void>
setProjectFrameGap(frameGap) frameGap: number (Minimum frame gap between events) Sets the minimum frame gap for the project editor state. Promise<void>
insertBlankFrames(evIndex, frames, minFrameGap, maxFrameGap, shotChangeAware) evIndex: number (Event group index, default 0 — not an event index)
frames: number (Number of blank frames to insert)
minFrameGap: number (Minimum frame gap)
maxFrameGap: number (Maximum frame gap)
shotChangeAware: boolean (Whether to respect shot changes)
Inserts blank frames between events in the specified event group, with optional shot change awareness. Promise<void>

Importing & Exporting Subtitles

A core functionality of the Closed Caption Creator IFrame Remote is the ability to seamlessly import existing subtitle files and export edited captions into various formats. This section details the methods and key parameters involved in these crucial operations.

Importing Subtitles

To load subtitles into the editor, you will use the importSubtitle method. This method is designed to handle a wide array of caption formats and allows for granular control over how the data is interpreted.

importSubtitle(subtitleUrl, profile, decodeOptions, evgOptions, autoFormatOptions, forced, target)

  • subtitleUrl: 
    • This required parameter specifies the direct URL from which the subtitle file will be read. The editor will fetch the content from this location.
  • profile (Source Profile): 
    • This required string parameter instructs Closed Caption Creator on how to correctly parse and decode the incoming caption data from the subtitleUrl. Each profile corresponds to a specific subtitle format (e.g., "subRip" for SRT, "webVtt" for VTT).
    • A comprehensive list of supported source profiles is available via our API documentation:
      https://api.closedcaptionconverter.com/help/profiles/source
  • decodeOptions:
    • This optional object parameter allows you to provide special, profile-specific settings that influence how the caption data is decoded. For instance, a profile might have options for handling specific character encodings or timecode offsets.
    • While optional, understanding these can be crucial for precise imports. A full list of decode options for each profile is detailed in our API documentation: https://api.closedcaptionconverter.com/help/profiles/all
  • evgOptions (Event Group Options):
    • This optional object parameter provides settings for the "Event Group" that will be created or updated within the editor. Event Groups are fundamental for real-time error tracking and validation of subtitle properties.
    • Key properties you can set include:
      • maxChars: Maximum characters per caption event.
      • maxLines: Maximum number of lines per caption event.
      • minDuration: Minimum duration for a caption event.
      • maxDuration: Maximum duration for a caption event.
      • overlap: Defines behavior for overlapping caption events.
      • maxCps: Maximum characters per second (readability check).
      • maxWpm: Maximum words per minute (readability check).
    • Ignored when target.eventGroupId is supplied, since the events are imported into an existing Event Group.
  • autoFormatOptions:
    • This optional object applies auto-formatting to the imported events: { enable, maxLines, maxChars, minDuration, allowOrphanWords, selective }.
    • On a merge import, only the newly imported events are formatted — existing events in the target group are left untouched.
  • forced:
    • This optional boolean (default false) marks every imported event as forced, which is useful for forced-narrative subtitle tracks.
  • target:
    • This optional object imports into an existing Event Group instead of creating a new one: { eventGroupId, importOption, track }.
    • importOption accepts "merge" or "replace" (default "replace").
    • track assigns imported events to letter track "A", "B", "C", or "D" when merging into a non-multiview group.

Exporting Subtitles

To retrieve the edited captions from the editor, you will use the exportSubtitle method. This allows you to generate output in your desired target format.

exportSubtitle(profile, encodeOptions, saveAsFile, forcedOption, eventGroupId, exportTracks)

  • profile (Target Profile):
    • This required string parameter specifies the format in which the editor should encode the current caption data. This must match one of our supported target profiles.
    • A complete list of supported target profiles is available via our API documentation:
      https://api.closedcaptionconverter.com/help/profiles/target
  • encodeOptions: 
    • Similar to decodeOptions, this optional object parameter provides special, profile-specific settings used by the encoder when generating the output subtitle file. These settings can affect output formatting, metadata inclusion, or specific technical requirements of the target format.
    • A full list of encode options for each profile is available via our API documentation:
      https://api.closedcaptionconverter.com/help/profiles/all
  • saveAsFile:
    • This boolean flag dictates the behavior of the exportSubtitle method.
      • If true, the editor will trigger a file download in the user's browser, allowing them to save the exported subtitle file directly.
      • If false (default), the method will return the file contents (as a string or binary data, depending on the format) within the Promise resolution, allowing your parent application to handle the data programmatically (e.g., upload to cloud storage, display in UI).
  • forcedOption:
    • This optional string (default "include") controls how forced events are handled: "include", "exclude", or "only".
  • eventGroupId:
    • This optional string exports a specific Event Group by ID. When omitted, the currently selected Event Group is exported.
  • exportTracks:
    • This optional array of track letters filters the export to specific letter tracks. The default is ["none", "A", "B", "C", "D"], where "none" matches events with no track assignment.
    • An empty array, or all values selected, exports every event regardless of track.

QC & Review Workflows

One of the most powerful features of the Closed Caption Creator IFrame Remote is the ability to implement sophisticated quality control (QC) and review workflows directly within your application. By leveraging custom style guides, you can enforce caption quality standards, catch common errors, and ensure that all subtitles meet your specific requirements before they're finalized or published.

This section introduces the QC capabilities available through the IFrame Embed solution, with a focus on custom style guides and how to integrate validation checks into your approval workflows.

Understanding Custom Style Guides

Custom style guides are configurable rulesets that define quality standards and formatting requirements for your captions. These guides can check for a wide range of issues, including:

  • Reading speed violations - Captions that display too quickly or slowly for comfortable reading
  • Maximum duration limits - Captions that stay on screen longer than recommended
  • Minimum duration limits - Captions that flash by too quickly to be readable
  • Line length restrictions - Captions with too many characters per line
  • Caption count limits - Too many lines displayed simultaneously
  • Gap detection - Excessive gaps between consecutive captions
  • Text formatting rules - Requirements for capitalization, punctuation, and special characters

Style guides provide flexibility for different use cases—whether you're creating captions for broadcast television, online streaming platforms, educational content, or accessibility compliance. Each use case may have unique requirements, and custom style guides allow you to enforce those standards programmatically.

Creating and Loading Style Guides

Style guides can be created and loaded in two primary ways:

1. User-Created Style Guides

Users can create their own style guides directly within the Closed Caption Creator interface, defining rules that match their specific workflow needs. These guides are saved to the user's account and can be applied to any project.

2. Dynamically Loaded Style Guides

For more advanced integrations, your parent application can dynamically create and load style guides based on the specific project, client requirements, or platform standards. This approach is ideal when different projects require different validation rules, or when you want to maintain centralized control over caption quality standards across your platform.

The loadStyleGuide function enables you to programmatically load a style guide into the editor, allowing for automated QC workflows.

Style Guide JSON Schema

Below is the JSON schema that defines the structure of a custom style guide. This schema allows you to specify the rules and thresholds that will be applied during validation:

JSON Schema

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Style Guide Configuration",
  "description": "Configuration object for caption/subtitle style guide validation rules",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "description": "Unique identifier for the style guide (UUID v4 format)",
      "example": "550e8400-e29b-41d4-a716-446655440000"
    },
    "name": {
      "type": "string",
      "description": "Display name for the style guide",
      "default": "Untitled Style Guide",
      "example": "Netflix Style Guide"
    },
    "enabled": {
      "type": "boolean",
      "description": "Whether this style guide is currently active for validation",
      "default": true
    },
    "totalMaxChars": {
      "type": "number",
      "description": "Maximum total characters allowed across all lines in a single caption event",
      "default": 9999,
      "minimum": 0
    },
    "minLines": {
      "type": "number",
      "description": "Minimum number of lines allowed per caption event",
      "default": 1,
      "minimum": 1
    },
    "maxLines": {
      "type": "number",
      "description": "Maximum number of lines allowed per caption event",
      "default": 4,
      "minimum": 1
    },
    "maxChars": {
      "type": "number",
      "description": "Maximum characters allowed per line",
      "default": 32,
      "minimum": 1
    },
    "maxDuration": {
      "type": "number",
      "description": "Maximum duration (in seconds) a caption event can be displayed",
      "default": 6,
      "minimum": 0
    },
    "minDuration": {
      "type": "number",
      "description": "Minimum duration (in seconds) a caption event must be displayed",
      "default": 0.2,
      "minimum": 0
    },
    "minCps": {
      "type": "number",
      "description": "Minimum characters per second reading speed",
      "default": 0,
      "minimum": 0
    },
    "maxCps": {
      "type": "number",
      "description": "Maximum characters per second reading speed (typically 15-20 for comfortable reading)",
      "default": 9999,
      "minimum": 0
    },
    "minWpm": {
      "type": "number",
      "description": "Minimum words per minute reading speed",
      "default": 0,
      "minimum": 0
    },
    "maxWpm": {
      "type": "number",
      "description": "Maximum words per minute reading speed",
      "default": 9999,
      "minimum": 0
    },
    "minWordsPerLine": {
      "type": "number",
      "description": "Minimum number of words allowed per line",
      "default": 1,
      "minimum": 1
    },
    "maxWordsPerLine": {
      "type": "number",
      "description": "Maximum number of words allowed per line",
      "default": 10,
      "minimum": 1
    },
    "minEventGap": {
      "type": "number",
      "description": "Minimum gap (in seconds) required between consecutive caption events",
      "default": 0,
      "minimum": 0
    },
    "maxEventGap": {
      "type": "number",
      "description": "Maximum gap (in seconds) allowed between consecutive caption events",
      "default": 6,
      "minimum": 0
    },
    "minEventGapTolerance": {
      "type": "number",
      "description": "Tolerance threshold (in seconds) for minimum event gap violations",
      "default": 0,
      "minimum": 0
    },
    "maxEventGapTolerance": {
      "type": "number",
      "description": "Tolerance threshold (in seconds) for maximum event gap violations",
      "default": 6,
      "minimum": 0
    },
    "overlap": {
      "type": "boolean",
      "description": "Flag to detect overlapping caption events (when enabled, overlaps are flagged as violations)",
      "default": true
    },
    "illegalChars": {
      "type": "boolean",
      "description": "Flag to detect illegal or unsupported characters in captions",
      "default": false
    },
    "hyphenSpace": {
      "type": "boolean",
      "description": "Flag to detect improper spacing around hyphens",
      "default": false
    },
    "hasUnderscore": {
      "type": "boolean",
      "description": "Flag to detect underscores in caption text (often not allowed)",
      "default": false
    },
    "periods": {
      "type": "boolean",
      "description": "Flag to validate period usage in captions",
      "default": false
    },
    "missingSpeaker": {
      "type": "boolean",
      "description": "Flag to detect captions missing speaker identification",
      "default": false
    },
    "useEllipses": {
      "type": "boolean",
      "description": "Flag to validate ellipses usage in captions",
      "default": false
    },
    "spellNumbers": {
      "type": "boolean",
      "description": "Flag to require numbers to be spelled out as words",
      "default": false
    },
    "spellNumbersAtStart": {
      "type": "boolean",
      "description": "Flag to require numbers at the start of sentences to be spelled out",
      "default": false
    },
    "netflixGlyphs": {
      "type": "boolean",
      "description": "Flag to validate Netflix-specific glyph requirements",
      "default": false
    },
    "partialItalics": {
      "type": "boolean",
      "description": "Flag to detect partial italic formatting within a caption event",
      "default": false
    },
    "fullItalics": {
      "type": "boolean",
      "description": "Flag to detect fully italicized caption events",
      "default": false
    },
    "partialBold": {
      "type": "boolean",
      "description": "Flag to detect partial bold formatting within a caption event",
      "default": false
    },
    "fullBold": {
      "type": "boolean",
      "description": "Flag to detect fully bolded caption events",
      "default": false
    },
    "partialUnderline": {
      "type": "boolean",
      "description": "Flag to detect partial underline formatting within a caption event",
      "default": false
    },
    "fullUnderline": {
      "type": "boolean",
      "description": "Flag to detect fully underlined caption events",
      "default": false
    },
    "repeatWords": {
      "type": "boolean",
      "description": "Flag to detect repeated words in caption text",
      "default": false
    },
    "fitSubtitles": {
      "type": "boolean",
      "description": "Flag to require all subtitles to fit on a single row",
      "default": false
    },
    "leadingTrailingSpace": {
      "type": "boolean",
      "description": "Flag to detect leading or trailing whitespace in caption text",
      "default": false
    },
    "whitespace": {
      "type": "boolean",
      "description": "Flag to detect excessive or improper whitespace in captions",
      "default": false
    },
    "blankLines": {
      "type": "boolean",
      "description": "Flag to detect blank lines within caption events",
      "default": false
    },
    "positionTopLeft": {
      "type": "boolean",
      "description": "Flag to validate captions positioned in the top-left area",
      "default": false
    },
    "positionTopCenter": {
      "type": "boolean",
      "description": "Flag to validate captions positioned in the top-center area",
      "default": false
    },
    "positionTopRight": {
      "type": "boolean",
      "description": "Flag to validate captions positioned in the top-right area",
      "default": false
    },
    "positionCenterLeft": {
      "type": "boolean",
      "description": "Flag to validate captions positioned in the center-left area",
      "default": false
    },
    "positionCenter": {
      "type": "boolean",
      "description": "Flag to validate captions positioned in the center area",
      "default": false
    },
    "positionCenterRight": {
      "type": "boolean",
      "description": "Flag to validate captions positioned in the center-right area",
      "default": false
    },
    "positionBottomLeft": {
      "type": "boolean",
      "description": "Flag to validate captions positioned in the bottom-left area",
      "default": false
    },
    "positionBottomCenter": {
      "type": "boolean",
      "description": "Flag to validate captions positioned in the bottom-center area",
      "default": false
    },
    "positionBottomRight": {
      "type": "boolean",
      "description": "Flag to validate captions positioned in the bottom-right area",
      "default": false
    },
    "positionYOffset": {
      "type": "boolean",
      "description": "Flag to validate vertical position offset values",
      "default": false
    },
    "positionXOffset": {
      "type": "boolean",
      "description": "Flag to validate horizontal position offset values",
      "default": false
    },
    "approvalPassed": {
      "type": "boolean",
      "description": "Flag to filter/validate captions marked as approved",
      "default": false
    },
    "approvalFailed": {
      "type": "boolean",
      "description": "Flag to filter/validate captions marked as failed approval",
      "default": false
    },
    "approvalNotSet": {
      "type": "boolean",
      "description": "Flag to filter/validate captions with no approval status set",
      "default": false
    },
    "notes": {
      "type": "boolean",
      "description": "Flag to validate presence of notes/annotations on captions",
      "default": false
    },
    "tags": {
      "type": "boolean",
      "description": "Flag to validate presence of tags on captions",
      "default": false
    },
    "forced": {
      "type": "boolean",
      "description": "Flag to validate forced narrative captions (non-dialogue captions)",
      "default": false
    }
  },
  "examples": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Netflix Standard",
      "enabled": true,
      "totalMaxChars": 84,
      "minLines": 1,
      "maxLines": 2,
      "maxChars": 42,
      "maxDuration": 7,
      "minDuration": 0.833,
      "minCps": 0,
      "maxCps": 20,
      "minWpm": 0,
      "maxWpm": 9999,
      "minWordsPerLine": 1,
      "maxWordsPerLine": 10,
      "minEventGap": 0.083,
      "maxEventGap": 6,
      "minEventGapTolerance": 0,
      "maxEventGapTolerance": 6,
      "overlap": true,
      "netflixGlyphs": true,
      "leadingTrailingSpace": true,
      "whitespace": true
    },
    {
      "id": "660e8400-e29b-41d4-a716-446655440001",
      "name": "BBC Standard",
      "enabled": true,
      "totalMaxChars": 9999,
      "minLines": 1,
      "maxLines": 2,
      "maxChars": 37,
      "maxDuration": 6,
      "minDuration": 0.3,
      "minCps": 0,
      "maxCps": 17,
      "minWpm": 0,
      "maxWpm": 200,
      "minWordsPerLine": 1,
      "maxWordsPerLine": 10,
      "minEventGap": 0,
      "maxEventGap": 6,
      "overlap": true,
      "illegalChars": true,
      "leadingTrailingSpace": true
    }
  ]
}

    Implementing QC Validation Workflows

    Once a style guide is loaded, the user or the parent application can trigger validation checks and retrieve any errors or warnings that are found. This enables you to build sophisticated approval workflows where captions must pass QC validation before they can be published or saved.

    A typical QC workflow involves these steps:

    1. Load the project - Import media and subtitles as usual
    2. Load the style guide - Use loadStyleGuide() to apply the appropriate quality standards
    3. Trigger validation - Run the style guide checks against the current Event Group
    4. Review results - Retrieve any errors or warnings found during validation
    5. Handle outcomes - Decide whether to allow publishing (if no critical errors) or require corrections (if errors are found)
    6. Provide feedback - Display errors  to the user using the status modal so they can make corrections

    This approach gives you complete control over when and how quality checks are performed, and allows you to integrate caption validation seamlessly into your existing content approval pipelines.

    Frequently asked questions

    IFrame integration questions

    Common developer and product questions about embedding the caption editor with the IFrame Remote.

    The IFrame Remote is an embeddable subtitle and timed-text editor designed to integrate into web-based applications. The parent application talks to the embedded editor through PenPal JS, so it can programmatically load media, import captions and apply settings, and receive updates such as markers, captions and speakers.

    An iframe tag pointing at the editor URL, plus the PenPal JS library for communication. The getting-started flow is three steps: import PenPal JS by NPM or CDN, embed the iframe, then connect and call the remote API. No backend is required.

    No. No backend is required to embed the caption editor. The editor runs in the browser inside the iframe, and communication between the host page and the editor happens client-side over PenPal JS, which keeps integration effort to a few lines of code.

    The embedded editor supports HLS streams, cloud storage, YouTube URLs and Vimeo links, among other sources. Multiple subtitle tracks and languages can be imported, and finished captions are published back to the host platform through the remote API.

    Yes. Theming options are included for seamless brand integration, so the embedded editor can present as a native part of the host platform. Flexible pricing options are available, and the team provides free technical support and training throughout the development process.

    Over 30 closed caption and subtitle formats, from standard SRT files to specialized broadcast formats. The editor is designed for broadcast manufacturers and video platforms that support captioning but lack professional editing capabilities.

    Yes. A free developer video course covers iframe integration, and the IFrame Remote documentation on the same page includes setup guides, API details and code examples. Free technical support is also available during the integration.