ugcinc-render 1.6.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +165 -1
- package/dist/index.d.ts +165 -1
- package/dist/index.js +349 -16
- package/dist/index.mjs +331 -6
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -441,6 +441,10 @@ interface VideoEditorBaseSegment {
|
|
|
441
441
|
startTrim?: number;
|
|
442
442
|
/** Trim from end in milliseconds */
|
|
443
443
|
endTrim?: number;
|
|
444
|
+
/** UI-only: reference to input for dynamic start trim value - resolved to startTrim before rendering */
|
|
445
|
+
startTrimInputRef?: string;
|
|
446
|
+
/** UI-only: reference to input for dynamic end trim value - resolved to endTrim before rendering */
|
|
447
|
+
endTrimInputRef?: string;
|
|
444
448
|
/** UI-only: helper for timing mode display - stripped before rendering */
|
|
445
449
|
timeMode?: TimeMode;
|
|
446
450
|
/** Parent segment ID for overlays */
|
|
@@ -1067,6 +1071,112 @@ declare function isDeduplicationLevel(input: DeduplicationInput): input is Dedup
|
|
|
1067
1071
|
*/
|
|
1068
1072
|
declare function resolveDeduplicationConfig(input: DeduplicationInput): DeduplicationConfig;
|
|
1069
1073
|
|
|
1074
|
+
/**
|
|
1075
|
+
* Caption types for auto-captioning functionality
|
|
1076
|
+
*
|
|
1077
|
+
* These types are used by both the CaptionOverlay component (client + server)
|
|
1078
|
+
* and the AutoCaptionComposition for rendering captioned videos.
|
|
1079
|
+
*/
|
|
1080
|
+
/**
|
|
1081
|
+
* Caption word with timing information from transcription
|
|
1082
|
+
*/
|
|
1083
|
+
interface CaptionWord {
|
|
1084
|
+
/** The word text */
|
|
1085
|
+
word: string;
|
|
1086
|
+
/** Start time in milliseconds */
|
|
1087
|
+
startMs: number;
|
|
1088
|
+
/** End time in milliseconds */
|
|
1089
|
+
endMs: number;
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Caption page - a group of words displayed together
|
|
1093
|
+
*/
|
|
1094
|
+
interface CaptionPage {
|
|
1095
|
+
/** Words in this page */
|
|
1096
|
+
words: CaptionWord[];
|
|
1097
|
+
/** Start time of first word in ms */
|
|
1098
|
+
startMs: number;
|
|
1099
|
+
/** End time of last word in ms */
|
|
1100
|
+
endMs: number;
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Caption style preset names
|
|
1104
|
+
*/
|
|
1105
|
+
type CaptionPreset = 'hormozi' | 'minimal' | 'bold-pop' | 'clean' | 'neon';
|
|
1106
|
+
/**
|
|
1107
|
+
* Caption vertical position on screen
|
|
1108
|
+
*/
|
|
1109
|
+
type CaptionPosition = 'top' | 'center' | 'bottom';
|
|
1110
|
+
/**
|
|
1111
|
+
* Caption font weight options
|
|
1112
|
+
*/
|
|
1113
|
+
type CaptionFontWeight = 'normal' | 'bold' | 'black';
|
|
1114
|
+
/**
|
|
1115
|
+
* Full caption style configuration
|
|
1116
|
+
*/
|
|
1117
|
+
interface CaptionStyle {
|
|
1118
|
+
/** Preset name (if used, provides defaults for other fields) */
|
|
1119
|
+
preset?: CaptionPreset;
|
|
1120
|
+
/** Google Font name (e.g., 'Montserrat', 'Inter') */
|
|
1121
|
+
fontName: string;
|
|
1122
|
+
/** Font size in pixels (relative to 1920p height) */
|
|
1123
|
+
fontSize: number;
|
|
1124
|
+
/** Font weight */
|
|
1125
|
+
fontWeight: CaptionFontWeight;
|
|
1126
|
+
/** Text color for inactive words */
|
|
1127
|
+
fontColor: string;
|
|
1128
|
+
/** Text color for the currently active/highlighted word */
|
|
1129
|
+
highlightColor: string;
|
|
1130
|
+
/** Stroke/outline color */
|
|
1131
|
+
strokeColor: string;
|
|
1132
|
+
/** Stroke width in pixels */
|
|
1133
|
+
strokeWidth: number;
|
|
1134
|
+
/** Background color (hex) - omit for transparent */
|
|
1135
|
+
backgroundColor?: string;
|
|
1136
|
+
/** Background opacity (0-1) */
|
|
1137
|
+
backgroundOpacity?: number;
|
|
1138
|
+
/** Background border radius in pixels */
|
|
1139
|
+
backgroundBorderRadius?: number;
|
|
1140
|
+
/** Vertical position on screen */
|
|
1141
|
+
position: CaptionPosition;
|
|
1142
|
+
/** Vertical offset in pixels from position edge */
|
|
1143
|
+
yOffset: number;
|
|
1144
|
+
/** Max width as percentage of video width (0-100) */
|
|
1145
|
+
maxWidth: number;
|
|
1146
|
+
/** Enable bounce/scale animation on active word */
|
|
1147
|
+
enableAnimation: boolean;
|
|
1148
|
+
/** Maximum words per caption page/screen */
|
|
1149
|
+
wordsPerPage: number;
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Props for AutoCaptionComposition
|
|
1153
|
+
*/
|
|
1154
|
+
interface AutoCaptionCompositionProps {
|
|
1155
|
+
/** Video source URL */
|
|
1156
|
+
videoUrl: string;
|
|
1157
|
+
/** Video width in pixels */
|
|
1158
|
+
width: number;
|
|
1159
|
+
/** Video height in pixels */
|
|
1160
|
+
height: number;
|
|
1161
|
+
/** Video duration in milliseconds */
|
|
1162
|
+
durationMs: number;
|
|
1163
|
+
/** Caption words with timing */
|
|
1164
|
+
captions: CaptionWord[];
|
|
1165
|
+
/** Caption styling configuration */
|
|
1166
|
+
style: CaptionStyle;
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Props for CaptionOverlay component
|
|
1170
|
+
*/
|
|
1171
|
+
interface CaptionOverlayProps {
|
|
1172
|
+
/** Caption words with timing */
|
|
1173
|
+
captions: CaptionWord[];
|
|
1174
|
+
/** Caption styling configuration */
|
|
1175
|
+
style: CaptionStyle;
|
|
1176
|
+
/** Override current time for preview mode (ms) */
|
|
1177
|
+
previewTimeMs?: number;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1070
1180
|
interface ImageEditorCompositionProps {
|
|
1071
1181
|
/** The editor configuration to render (legacy format with pre-resolved segments) */
|
|
1072
1182
|
config?: ImageEditorConfig;
|
|
@@ -1167,6 +1277,16 @@ interface VideoEditorCompositionProps {
|
|
|
1167
1277
|
*/
|
|
1168
1278
|
declare function VideoEditorComposition({ config, sources, textContent, }: VideoEditorCompositionProps): react_jsx_runtime.JSX.Element;
|
|
1169
1279
|
|
|
1280
|
+
/**
|
|
1281
|
+
* AutoCaptionComposition renders a video with synchronized caption overlay
|
|
1282
|
+
*/
|
|
1283
|
+
declare function AutoCaptionComposition({ videoUrl, captions, style, }: AutoCaptionCompositionProps): react_jsx_runtime.JSX.Element;
|
|
1284
|
+
/**
|
|
1285
|
+
* AutoCaptionComposition with standard Video component
|
|
1286
|
+
* Use this variant for client-side preview where OffthreadVideo may not work
|
|
1287
|
+
*/
|
|
1288
|
+
declare function AutoCaptionCompositionWithVideo({ videoUrl, captions, style, }: AutoCaptionCompositionProps): react_jsx_runtime.JSX.Element;
|
|
1289
|
+
|
|
1170
1290
|
interface TextElementProps {
|
|
1171
1291
|
segment: TextSegment;
|
|
1172
1292
|
/** Optional scale for high-DPI rendering */
|
|
@@ -1232,6 +1352,11 @@ interface VideoElementProps {
|
|
|
1232
1352
|
*/
|
|
1233
1353
|
declare function VideoElement({ segment, src, startFrame, durationInFrames, scale }: VideoElementProps): react_jsx_runtime.JSX.Element;
|
|
1234
1354
|
|
|
1355
|
+
/**
|
|
1356
|
+
* CaptionOverlay renders animated captions synchronized with video playback
|
|
1357
|
+
*/
|
|
1358
|
+
declare function CaptionOverlay({ captions, style, previewTimeMs }: CaptionOverlayProps): react_jsx_runtime.JSX.Element | null;
|
|
1359
|
+
|
|
1235
1360
|
/**
|
|
1236
1361
|
* Font utilities for the rendering system
|
|
1237
1362
|
*/
|
|
@@ -1588,6 +1713,45 @@ declare function generateSegmentId(): string;
|
|
|
1588
1713
|
*/
|
|
1589
1714
|
declare function generateOverlayId(): string;
|
|
1590
1715
|
|
|
1716
|
+
/**
|
|
1717
|
+
* Caption style presets and resolution utilities
|
|
1718
|
+
*
|
|
1719
|
+
* Provides TikTok-style caption presets and a function to resolve
|
|
1720
|
+
* partial config into a complete CaptionStyle object.
|
|
1721
|
+
*/
|
|
1722
|
+
|
|
1723
|
+
/**
|
|
1724
|
+
* Predefined caption style presets
|
|
1725
|
+
*
|
|
1726
|
+
* Font sizes are designed for 1920p height and scale proportionally.
|
|
1727
|
+
* maxWidth is a percentage of video width (0-100).
|
|
1728
|
+
*/
|
|
1729
|
+
declare const CAPTION_PRESETS: Record<CaptionPreset, Omit<CaptionStyle, 'preset'>>;
|
|
1730
|
+
/**
|
|
1731
|
+
* Default caption style (used when no preset or custom values provided)
|
|
1732
|
+
*/
|
|
1733
|
+
declare const DEFAULT_CAPTION_STYLE: Omit<CaptionStyle, "preset">;
|
|
1734
|
+
/**
|
|
1735
|
+
* Resolve a partial caption style config into a complete CaptionStyle object
|
|
1736
|
+
*
|
|
1737
|
+
* Priority:
|
|
1738
|
+
* 1. Explicit values in the input style
|
|
1739
|
+
* 2. Preset values (if preset is specified)
|
|
1740
|
+
* 3. Default values (hormozi preset)
|
|
1741
|
+
*
|
|
1742
|
+
* @param style - Partial caption style configuration
|
|
1743
|
+
* @returns Complete CaptionStyle object with all fields populated
|
|
1744
|
+
*/
|
|
1745
|
+
declare function resolveCaptionStyle(style: Partial<CaptionStyle> | undefined): CaptionStyle;
|
|
1746
|
+
/**
|
|
1747
|
+
* Get all available preset names
|
|
1748
|
+
*/
|
|
1749
|
+
declare function getCaptionPresetNames(): CaptionPreset[];
|
|
1750
|
+
/**
|
|
1751
|
+
* Check if a string is a valid caption preset name
|
|
1752
|
+
*/
|
|
1753
|
+
declare function isValidCaptionPreset(name: string): name is CaptionPreset;
|
|
1754
|
+
|
|
1591
1755
|
/**
|
|
1592
1756
|
* Hook exports for ugcinc-render
|
|
1593
1757
|
*
|
|
@@ -1650,4 +1814,4 @@ declare function useResolvedPositions(elements: ImageEditorElement[], textValues
|
|
|
1650
1814
|
|
|
1651
1815
|
declare const RenderRoot: React.FC;
|
|
1652
1816
|
|
|
1653
|
-
export { APPLE_EMOJI_FONT, type Acceleration, type AudioConfig, type AudioSegment, type BaseEditorConfig, type BaseSegment, type BlendMode, type BorderRadiusConfig, type Channel, type ColorFilterConfig, type ColorShiftConfig, type CropAxisConfig, type CropBoundary, type CropBounds, type CropConfig, type CuttingConfig, DEDUPLICATION_LEVELS, DIMENSION_PRESETS, type DeduplicationConfig, type DeduplicationInput, type DeduplicationLevel, type DiagonalFilterConfig, type DiagonalFilterType, type DimensionPreset, type DimensionPresetKey, type DynamicCropConfig, type EditorConfig, type EditorSegment, type EnhanceLevel, type EnhancementConfig, FONT_FAMILIES, FONT_URLS, type FitDimensions, type FitMode, type FontType, type FontWeight, type FrameManipulationConfig, type GifOverlayConfig, type GifSource, type GradientOverlayConfig, type HorizontalAnchor, type HorizontalSelfAnchor, type Hyphenation, IMAGE_DEFAULTS, ImageEditorComposition, type ImageEditorCompositionProps, type ImageEditorConfig, type ImageEditorElement, type ImageEditorNodeConfig, ImageElement, type ImageElementProps, type ImageSegment, LEVEL_1_CONFIG, LEVEL_2_CONFIG, LEVEL_3_CONFIG, LEVEL_4_CONFIG, LEVEL_5_CONFIG, type LensCorrectionConfig, type MobileEncodingConfig, type ParticleOverlayConfig, type PhoneConfig, type PhoneModel, type PhoneType, type PictureSegment, type PositionResolutionError, type PositionResolutionResult, type RelativePositionConfigX, type RelativePositionConfigY, RenderRoot, type Segment, type SegmentTimelinePosition, type SegmentType, type SpeedConfig, type StaticSegment, TEXT_DEFAULTS, type TextAlignment, type TextDirection, TextElement, type TextElementProps, type TextOverflow, type TextSegment, type TextStyleProperties, type TextWrap, type TimeMode, type TimeValue, type TraceRemovalConfig, VIDEO_DEFAULTS, VISUAL_DEFAULTS, type VerticalAlignment, type VerticalAnchor, type VerticalSelfAnchor, type VideoCodec, type VideoEditorAudioSegment, type VideoEditorBaseSegment, type VideoEditorChannel, VideoEditorComposition, type VideoEditorCompositionProps, type VideoEditorConfig, type VideoEditorImageSegment, type VideoEditorNodeConfig, type VideoEditorSegment, type VideoEditorTextSegment, type VideoEditorVideoSegment, type VideoEditorVisualSegment, VideoElement, type VideoElementProps, type VideoExtension, type VideoSegment, type VisualAdjustmentsConfig, type VisualSegment, type VisualSegmentUnion, type WordBreak, applyImageDefaults, applyTextDefaults, applyVideoDefaults, areFontsLoaded, buildFontString, calculateAutoWidthDimensions, calculateCropBounds, calculateEstimatedDuration, calculateFitDimensions, calculateLineWidth, calculateTimelineContentEnd, canSetAsReference, debugFontStatus, defaultOffset, formatTime, generateOverlayId, generateSegmentId, getBaseSegments, getBorderRadii, getDependentElements, getFontFamily, getOverlays, getReferenceElementX, getReferenceElementY, getSegmentTimelinePosition, hexToRgba, isDeduplicationLevel, isDynamicCropEnabled, isSegmentVisibleAtTime, parseHexColor, parseTime, preloadFonts, resolveDeduplicationConfig, resolveElementPositions, useFontsLoaded, useImageLoader, useImagePreloader, useResolvedPositions, wrapText };
|
|
1817
|
+
export { APPLE_EMOJI_FONT, type Acceleration, type AudioConfig, type AudioSegment, AutoCaptionComposition, type AutoCaptionCompositionProps, AutoCaptionCompositionWithVideo, type BaseEditorConfig, type BaseSegment, type BlendMode, type BorderRadiusConfig, CAPTION_PRESETS, type CaptionFontWeight, CaptionOverlay, type CaptionOverlayProps, type CaptionPage, type CaptionPosition, type CaptionPreset, type CaptionStyle, type CaptionWord, type Channel, type ColorFilterConfig, type ColorShiftConfig, type CropAxisConfig, type CropBoundary, type CropBounds, type CropConfig, type CuttingConfig, DEDUPLICATION_LEVELS, DEFAULT_CAPTION_STYLE, DIMENSION_PRESETS, type DeduplicationConfig, type DeduplicationInput, type DeduplicationLevel, type DiagonalFilterConfig, type DiagonalFilterType, type DimensionPreset, type DimensionPresetKey, type DynamicCropConfig, type EditorConfig, type EditorSegment, type EnhanceLevel, type EnhancementConfig, FONT_FAMILIES, FONT_URLS, type FitDimensions, type FitMode, type FontType, type FontWeight, type FrameManipulationConfig, type GifOverlayConfig, type GifSource, type GradientOverlayConfig, type HorizontalAnchor, type HorizontalSelfAnchor, type Hyphenation, IMAGE_DEFAULTS, ImageEditorComposition, type ImageEditorCompositionProps, type ImageEditorConfig, type ImageEditorElement, type ImageEditorNodeConfig, ImageElement, type ImageElementProps, type ImageSegment, LEVEL_1_CONFIG, LEVEL_2_CONFIG, LEVEL_3_CONFIG, LEVEL_4_CONFIG, LEVEL_5_CONFIG, type LensCorrectionConfig, type MobileEncodingConfig, type ParticleOverlayConfig, type PhoneConfig, type PhoneModel, type PhoneType, type PictureSegment, type PositionResolutionError, type PositionResolutionResult, type RelativePositionConfigX, type RelativePositionConfigY, RenderRoot, type Segment, type SegmentTimelinePosition, type SegmentType, type SpeedConfig, type StaticSegment, TEXT_DEFAULTS, type TextAlignment, type TextDirection, TextElement, type TextElementProps, type TextOverflow, type TextSegment, type TextStyleProperties, type TextWrap, type TimeMode, type TimeValue, type TraceRemovalConfig, VIDEO_DEFAULTS, VISUAL_DEFAULTS, type VerticalAlignment, type VerticalAnchor, type VerticalSelfAnchor, type VideoCodec, type VideoEditorAudioSegment, type VideoEditorBaseSegment, type VideoEditorChannel, VideoEditorComposition, type VideoEditorCompositionProps, type VideoEditorConfig, type VideoEditorImageSegment, type VideoEditorNodeConfig, type VideoEditorSegment, type VideoEditorTextSegment, type VideoEditorVideoSegment, type VideoEditorVisualSegment, VideoElement, type VideoElementProps, type VideoExtension, type VideoSegment, type VisualAdjustmentsConfig, type VisualSegment, type VisualSegmentUnion, type WordBreak, applyImageDefaults, applyTextDefaults, applyVideoDefaults, areFontsLoaded, buildFontString, calculateAutoWidthDimensions, calculateCropBounds, calculateEstimatedDuration, calculateFitDimensions, calculateLineWidth, calculateTimelineContentEnd, canSetAsReference, debugFontStatus, defaultOffset, formatTime, generateOverlayId, generateSegmentId, getBaseSegments, getBorderRadii, getCaptionPresetNames, getDependentElements, getFontFamily, getOverlays, getReferenceElementX, getReferenceElementY, getSegmentTimelinePosition, hexToRgba, isDeduplicationLevel, isDynamicCropEnabled, isSegmentVisibleAtTime, isValidCaptionPreset, parseHexColor, parseTime, preloadFonts, resolveCaptionStyle, resolveDeduplicationConfig, resolveElementPositions, useFontsLoaded, useImageLoader, useImagePreloader, useResolvedPositions, wrapText };
|
package/dist/index.d.ts
CHANGED
|
@@ -441,6 +441,10 @@ interface VideoEditorBaseSegment {
|
|
|
441
441
|
startTrim?: number;
|
|
442
442
|
/** Trim from end in milliseconds */
|
|
443
443
|
endTrim?: number;
|
|
444
|
+
/** UI-only: reference to input for dynamic start trim value - resolved to startTrim before rendering */
|
|
445
|
+
startTrimInputRef?: string;
|
|
446
|
+
/** UI-only: reference to input for dynamic end trim value - resolved to endTrim before rendering */
|
|
447
|
+
endTrimInputRef?: string;
|
|
444
448
|
/** UI-only: helper for timing mode display - stripped before rendering */
|
|
445
449
|
timeMode?: TimeMode;
|
|
446
450
|
/** Parent segment ID for overlays */
|
|
@@ -1067,6 +1071,112 @@ declare function isDeduplicationLevel(input: DeduplicationInput): input is Dedup
|
|
|
1067
1071
|
*/
|
|
1068
1072
|
declare function resolveDeduplicationConfig(input: DeduplicationInput): DeduplicationConfig;
|
|
1069
1073
|
|
|
1074
|
+
/**
|
|
1075
|
+
* Caption types for auto-captioning functionality
|
|
1076
|
+
*
|
|
1077
|
+
* These types are used by both the CaptionOverlay component (client + server)
|
|
1078
|
+
* and the AutoCaptionComposition for rendering captioned videos.
|
|
1079
|
+
*/
|
|
1080
|
+
/**
|
|
1081
|
+
* Caption word with timing information from transcription
|
|
1082
|
+
*/
|
|
1083
|
+
interface CaptionWord {
|
|
1084
|
+
/** The word text */
|
|
1085
|
+
word: string;
|
|
1086
|
+
/** Start time in milliseconds */
|
|
1087
|
+
startMs: number;
|
|
1088
|
+
/** End time in milliseconds */
|
|
1089
|
+
endMs: number;
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Caption page - a group of words displayed together
|
|
1093
|
+
*/
|
|
1094
|
+
interface CaptionPage {
|
|
1095
|
+
/** Words in this page */
|
|
1096
|
+
words: CaptionWord[];
|
|
1097
|
+
/** Start time of first word in ms */
|
|
1098
|
+
startMs: number;
|
|
1099
|
+
/** End time of last word in ms */
|
|
1100
|
+
endMs: number;
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Caption style preset names
|
|
1104
|
+
*/
|
|
1105
|
+
type CaptionPreset = 'hormozi' | 'minimal' | 'bold-pop' | 'clean' | 'neon';
|
|
1106
|
+
/**
|
|
1107
|
+
* Caption vertical position on screen
|
|
1108
|
+
*/
|
|
1109
|
+
type CaptionPosition = 'top' | 'center' | 'bottom';
|
|
1110
|
+
/**
|
|
1111
|
+
* Caption font weight options
|
|
1112
|
+
*/
|
|
1113
|
+
type CaptionFontWeight = 'normal' | 'bold' | 'black';
|
|
1114
|
+
/**
|
|
1115
|
+
* Full caption style configuration
|
|
1116
|
+
*/
|
|
1117
|
+
interface CaptionStyle {
|
|
1118
|
+
/** Preset name (if used, provides defaults for other fields) */
|
|
1119
|
+
preset?: CaptionPreset;
|
|
1120
|
+
/** Google Font name (e.g., 'Montserrat', 'Inter') */
|
|
1121
|
+
fontName: string;
|
|
1122
|
+
/** Font size in pixels (relative to 1920p height) */
|
|
1123
|
+
fontSize: number;
|
|
1124
|
+
/** Font weight */
|
|
1125
|
+
fontWeight: CaptionFontWeight;
|
|
1126
|
+
/** Text color for inactive words */
|
|
1127
|
+
fontColor: string;
|
|
1128
|
+
/** Text color for the currently active/highlighted word */
|
|
1129
|
+
highlightColor: string;
|
|
1130
|
+
/** Stroke/outline color */
|
|
1131
|
+
strokeColor: string;
|
|
1132
|
+
/** Stroke width in pixels */
|
|
1133
|
+
strokeWidth: number;
|
|
1134
|
+
/** Background color (hex) - omit for transparent */
|
|
1135
|
+
backgroundColor?: string;
|
|
1136
|
+
/** Background opacity (0-1) */
|
|
1137
|
+
backgroundOpacity?: number;
|
|
1138
|
+
/** Background border radius in pixels */
|
|
1139
|
+
backgroundBorderRadius?: number;
|
|
1140
|
+
/** Vertical position on screen */
|
|
1141
|
+
position: CaptionPosition;
|
|
1142
|
+
/** Vertical offset in pixels from position edge */
|
|
1143
|
+
yOffset: number;
|
|
1144
|
+
/** Max width as percentage of video width (0-100) */
|
|
1145
|
+
maxWidth: number;
|
|
1146
|
+
/** Enable bounce/scale animation on active word */
|
|
1147
|
+
enableAnimation: boolean;
|
|
1148
|
+
/** Maximum words per caption page/screen */
|
|
1149
|
+
wordsPerPage: number;
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Props for AutoCaptionComposition
|
|
1153
|
+
*/
|
|
1154
|
+
interface AutoCaptionCompositionProps {
|
|
1155
|
+
/** Video source URL */
|
|
1156
|
+
videoUrl: string;
|
|
1157
|
+
/** Video width in pixels */
|
|
1158
|
+
width: number;
|
|
1159
|
+
/** Video height in pixels */
|
|
1160
|
+
height: number;
|
|
1161
|
+
/** Video duration in milliseconds */
|
|
1162
|
+
durationMs: number;
|
|
1163
|
+
/** Caption words with timing */
|
|
1164
|
+
captions: CaptionWord[];
|
|
1165
|
+
/** Caption styling configuration */
|
|
1166
|
+
style: CaptionStyle;
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Props for CaptionOverlay component
|
|
1170
|
+
*/
|
|
1171
|
+
interface CaptionOverlayProps {
|
|
1172
|
+
/** Caption words with timing */
|
|
1173
|
+
captions: CaptionWord[];
|
|
1174
|
+
/** Caption styling configuration */
|
|
1175
|
+
style: CaptionStyle;
|
|
1176
|
+
/** Override current time for preview mode (ms) */
|
|
1177
|
+
previewTimeMs?: number;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1070
1180
|
interface ImageEditorCompositionProps {
|
|
1071
1181
|
/** The editor configuration to render (legacy format with pre-resolved segments) */
|
|
1072
1182
|
config?: ImageEditorConfig;
|
|
@@ -1167,6 +1277,16 @@ interface VideoEditorCompositionProps {
|
|
|
1167
1277
|
*/
|
|
1168
1278
|
declare function VideoEditorComposition({ config, sources, textContent, }: VideoEditorCompositionProps): react_jsx_runtime.JSX.Element;
|
|
1169
1279
|
|
|
1280
|
+
/**
|
|
1281
|
+
* AutoCaptionComposition renders a video with synchronized caption overlay
|
|
1282
|
+
*/
|
|
1283
|
+
declare function AutoCaptionComposition({ videoUrl, captions, style, }: AutoCaptionCompositionProps): react_jsx_runtime.JSX.Element;
|
|
1284
|
+
/**
|
|
1285
|
+
* AutoCaptionComposition with standard Video component
|
|
1286
|
+
* Use this variant for client-side preview where OffthreadVideo may not work
|
|
1287
|
+
*/
|
|
1288
|
+
declare function AutoCaptionCompositionWithVideo({ videoUrl, captions, style, }: AutoCaptionCompositionProps): react_jsx_runtime.JSX.Element;
|
|
1289
|
+
|
|
1170
1290
|
interface TextElementProps {
|
|
1171
1291
|
segment: TextSegment;
|
|
1172
1292
|
/** Optional scale for high-DPI rendering */
|
|
@@ -1232,6 +1352,11 @@ interface VideoElementProps {
|
|
|
1232
1352
|
*/
|
|
1233
1353
|
declare function VideoElement({ segment, src, startFrame, durationInFrames, scale }: VideoElementProps): react_jsx_runtime.JSX.Element;
|
|
1234
1354
|
|
|
1355
|
+
/**
|
|
1356
|
+
* CaptionOverlay renders animated captions synchronized with video playback
|
|
1357
|
+
*/
|
|
1358
|
+
declare function CaptionOverlay({ captions, style, previewTimeMs }: CaptionOverlayProps): react_jsx_runtime.JSX.Element | null;
|
|
1359
|
+
|
|
1235
1360
|
/**
|
|
1236
1361
|
* Font utilities for the rendering system
|
|
1237
1362
|
*/
|
|
@@ -1588,6 +1713,45 @@ declare function generateSegmentId(): string;
|
|
|
1588
1713
|
*/
|
|
1589
1714
|
declare function generateOverlayId(): string;
|
|
1590
1715
|
|
|
1716
|
+
/**
|
|
1717
|
+
* Caption style presets and resolution utilities
|
|
1718
|
+
*
|
|
1719
|
+
* Provides TikTok-style caption presets and a function to resolve
|
|
1720
|
+
* partial config into a complete CaptionStyle object.
|
|
1721
|
+
*/
|
|
1722
|
+
|
|
1723
|
+
/**
|
|
1724
|
+
* Predefined caption style presets
|
|
1725
|
+
*
|
|
1726
|
+
* Font sizes are designed for 1920p height and scale proportionally.
|
|
1727
|
+
* maxWidth is a percentage of video width (0-100).
|
|
1728
|
+
*/
|
|
1729
|
+
declare const CAPTION_PRESETS: Record<CaptionPreset, Omit<CaptionStyle, 'preset'>>;
|
|
1730
|
+
/**
|
|
1731
|
+
* Default caption style (used when no preset or custom values provided)
|
|
1732
|
+
*/
|
|
1733
|
+
declare const DEFAULT_CAPTION_STYLE: Omit<CaptionStyle, "preset">;
|
|
1734
|
+
/**
|
|
1735
|
+
* Resolve a partial caption style config into a complete CaptionStyle object
|
|
1736
|
+
*
|
|
1737
|
+
* Priority:
|
|
1738
|
+
* 1. Explicit values in the input style
|
|
1739
|
+
* 2. Preset values (if preset is specified)
|
|
1740
|
+
* 3. Default values (hormozi preset)
|
|
1741
|
+
*
|
|
1742
|
+
* @param style - Partial caption style configuration
|
|
1743
|
+
* @returns Complete CaptionStyle object with all fields populated
|
|
1744
|
+
*/
|
|
1745
|
+
declare function resolveCaptionStyle(style: Partial<CaptionStyle> | undefined): CaptionStyle;
|
|
1746
|
+
/**
|
|
1747
|
+
* Get all available preset names
|
|
1748
|
+
*/
|
|
1749
|
+
declare function getCaptionPresetNames(): CaptionPreset[];
|
|
1750
|
+
/**
|
|
1751
|
+
* Check if a string is a valid caption preset name
|
|
1752
|
+
*/
|
|
1753
|
+
declare function isValidCaptionPreset(name: string): name is CaptionPreset;
|
|
1754
|
+
|
|
1591
1755
|
/**
|
|
1592
1756
|
* Hook exports for ugcinc-render
|
|
1593
1757
|
*
|
|
@@ -1650,4 +1814,4 @@ declare function useResolvedPositions(elements: ImageEditorElement[], textValues
|
|
|
1650
1814
|
|
|
1651
1815
|
declare const RenderRoot: React.FC;
|
|
1652
1816
|
|
|
1653
|
-
export { APPLE_EMOJI_FONT, type Acceleration, type AudioConfig, type AudioSegment, type BaseEditorConfig, type BaseSegment, type BlendMode, type BorderRadiusConfig, type Channel, type ColorFilterConfig, type ColorShiftConfig, type CropAxisConfig, type CropBoundary, type CropBounds, type CropConfig, type CuttingConfig, DEDUPLICATION_LEVELS, DIMENSION_PRESETS, type DeduplicationConfig, type DeduplicationInput, type DeduplicationLevel, type DiagonalFilterConfig, type DiagonalFilterType, type DimensionPreset, type DimensionPresetKey, type DynamicCropConfig, type EditorConfig, type EditorSegment, type EnhanceLevel, type EnhancementConfig, FONT_FAMILIES, FONT_URLS, type FitDimensions, type FitMode, type FontType, type FontWeight, type FrameManipulationConfig, type GifOverlayConfig, type GifSource, type GradientOverlayConfig, type HorizontalAnchor, type HorizontalSelfAnchor, type Hyphenation, IMAGE_DEFAULTS, ImageEditorComposition, type ImageEditorCompositionProps, type ImageEditorConfig, type ImageEditorElement, type ImageEditorNodeConfig, ImageElement, type ImageElementProps, type ImageSegment, LEVEL_1_CONFIG, LEVEL_2_CONFIG, LEVEL_3_CONFIG, LEVEL_4_CONFIG, LEVEL_5_CONFIG, type LensCorrectionConfig, type MobileEncodingConfig, type ParticleOverlayConfig, type PhoneConfig, type PhoneModel, type PhoneType, type PictureSegment, type PositionResolutionError, type PositionResolutionResult, type RelativePositionConfigX, type RelativePositionConfigY, RenderRoot, type Segment, type SegmentTimelinePosition, type SegmentType, type SpeedConfig, type StaticSegment, TEXT_DEFAULTS, type TextAlignment, type TextDirection, TextElement, type TextElementProps, type TextOverflow, type TextSegment, type TextStyleProperties, type TextWrap, type TimeMode, type TimeValue, type TraceRemovalConfig, VIDEO_DEFAULTS, VISUAL_DEFAULTS, type VerticalAlignment, type VerticalAnchor, type VerticalSelfAnchor, type VideoCodec, type VideoEditorAudioSegment, type VideoEditorBaseSegment, type VideoEditorChannel, VideoEditorComposition, type VideoEditorCompositionProps, type VideoEditorConfig, type VideoEditorImageSegment, type VideoEditorNodeConfig, type VideoEditorSegment, type VideoEditorTextSegment, type VideoEditorVideoSegment, type VideoEditorVisualSegment, VideoElement, type VideoElementProps, type VideoExtension, type VideoSegment, type VisualAdjustmentsConfig, type VisualSegment, type VisualSegmentUnion, type WordBreak, applyImageDefaults, applyTextDefaults, applyVideoDefaults, areFontsLoaded, buildFontString, calculateAutoWidthDimensions, calculateCropBounds, calculateEstimatedDuration, calculateFitDimensions, calculateLineWidth, calculateTimelineContentEnd, canSetAsReference, debugFontStatus, defaultOffset, formatTime, generateOverlayId, generateSegmentId, getBaseSegments, getBorderRadii, getDependentElements, getFontFamily, getOverlays, getReferenceElementX, getReferenceElementY, getSegmentTimelinePosition, hexToRgba, isDeduplicationLevel, isDynamicCropEnabled, isSegmentVisibleAtTime, parseHexColor, parseTime, preloadFonts, resolveDeduplicationConfig, resolveElementPositions, useFontsLoaded, useImageLoader, useImagePreloader, useResolvedPositions, wrapText };
|
|
1817
|
+
export { APPLE_EMOJI_FONT, type Acceleration, type AudioConfig, type AudioSegment, AutoCaptionComposition, type AutoCaptionCompositionProps, AutoCaptionCompositionWithVideo, type BaseEditorConfig, type BaseSegment, type BlendMode, type BorderRadiusConfig, CAPTION_PRESETS, type CaptionFontWeight, CaptionOverlay, type CaptionOverlayProps, type CaptionPage, type CaptionPosition, type CaptionPreset, type CaptionStyle, type CaptionWord, type Channel, type ColorFilterConfig, type ColorShiftConfig, type CropAxisConfig, type CropBoundary, type CropBounds, type CropConfig, type CuttingConfig, DEDUPLICATION_LEVELS, DEFAULT_CAPTION_STYLE, DIMENSION_PRESETS, type DeduplicationConfig, type DeduplicationInput, type DeduplicationLevel, type DiagonalFilterConfig, type DiagonalFilterType, type DimensionPreset, type DimensionPresetKey, type DynamicCropConfig, type EditorConfig, type EditorSegment, type EnhanceLevel, type EnhancementConfig, FONT_FAMILIES, FONT_URLS, type FitDimensions, type FitMode, type FontType, type FontWeight, type FrameManipulationConfig, type GifOverlayConfig, type GifSource, type GradientOverlayConfig, type HorizontalAnchor, type HorizontalSelfAnchor, type Hyphenation, IMAGE_DEFAULTS, ImageEditorComposition, type ImageEditorCompositionProps, type ImageEditorConfig, type ImageEditorElement, type ImageEditorNodeConfig, ImageElement, type ImageElementProps, type ImageSegment, LEVEL_1_CONFIG, LEVEL_2_CONFIG, LEVEL_3_CONFIG, LEVEL_4_CONFIG, LEVEL_5_CONFIG, type LensCorrectionConfig, type MobileEncodingConfig, type ParticleOverlayConfig, type PhoneConfig, type PhoneModel, type PhoneType, type PictureSegment, type PositionResolutionError, type PositionResolutionResult, type RelativePositionConfigX, type RelativePositionConfigY, RenderRoot, type Segment, type SegmentTimelinePosition, type SegmentType, type SpeedConfig, type StaticSegment, TEXT_DEFAULTS, type TextAlignment, type TextDirection, TextElement, type TextElementProps, type TextOverflow, type TextSegment, type TextStyleProperties, type TextWrap, type TimeMode, type TimeValue, type TraceRemovalConfig, VIDEO_DEFAULTS, VISUAL_DEFAULTS, type VerticalAlignment, type VerticalAnchor, type VerticalSelfAnchor, type VideoCodec, type VideoEditorAudioSegment, type VideoEditorBaseSegment, type VideoEditorChannel, VideoEditorComposition, type VideoEditorCompositionProps, type VideoEditorConfig, type VideoEditorImageSegment, type VideoEditorNodeConfig, type VideoEditorSegment, type VideoEditorTextSegment, type VideoEditorVideoSegment, type VideoEditorVisualSegment, VideoElement, type VideoElementProps, type VideoExtension, type VideoSegment, type VisualAdjustmentsConfig, type VisualSegment, type VisualSegmentUnion, type WordBreak, applyImageDefaults, applyTextDefaults, applyVideoDefaults, areFontsLoaded, buildFontString, calculateAutoWidthDimensions, calculateCropBounds, calculateEstimatedDuration, calculateFitDimensions, calculateLineWidth, calculateTimelineContentEnd, canSetAsReference, debugFontStatus, defaultOffset, formatTime, generateOverlayId, generateSegmentId, getBaseSegments, getBorderRadii, getCaptionPresetNames, getDependentElements, getFontFamily, getOverlays, getReferenceElementX, getReferenceElementY, getSegmentTimelinePosition, hexToRgba, isDeduplicationLevel, isDynamicCropEnabled, isSegmentVisibleAtTime, isValidCaptionPreset, parseHexColor, parseTime, preloadFonts, resolveCaptionStyle, resolveDeduplicationConfig, resolveElementPositions, useFontsLoaded, useImageLoader, useImagePreloader, useResolvedPositions, wrapText };
|
package/dist/index.js
CHANGED
|
@@ -31,7 +31,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
APPLE_EMOJI_FONT: () => APPLE_EMOJI_FONT,
|
|
34
|
+
AutoCaptionComposition: () => AutoCaptionComposition,
|
|
35
|
+
AutoCaptionCompositionWithVideo: () => AutoCaptionCompositionWithVideo,
|
|
36
|
+
CAPTION_PRESETS: () => CAPTION_PRESETS,
|
|
37
|
+
CaptionOverlay: () => CaptionOverlay,
|
|
34
38
|
DEDUPLICATION_LEVELS: () => DEDUPLICATION_LEVELS,
|
|
39
|
+
DEFAULT_CAPTION_STYLE: () => DEFAULT_CAPTION_STYLE,
|
|
35
40
|
DIMENSION_PRESETS: () => DIMENSION_PRESETS,
|
|
36
41
|
FONT_FAMILIES: () => FONT_FAMILIES,
|
|
37
42
|
FONT_URLS: () => FONT_URLS,
|
|
@@ -69,6 +74,7 @@ __export(index_exports, {
|
|
|
69
74
|
generateSegmentId: () => generateSegmentId,
|
|
70
75
|
getBaseSegments: () => getBaseSegments,
|
|
71
76
|
getBorderRadii: () => getBorderRadii,
|
|
77
|
+
getCaptionPresetNames: () => getCaptionPresetNames,
|
|
72
78
|
getDependentElements: () => getDependentElements,
|
|
73
79
|
getFontFamily: () => getFontFamily,
|
|
74
80
|
getOverlays: () => getOverlays,
|
|
@@ -79,9 +85,11 @@ __export(index_exports, {
|
|
|
79
85
|
isDeduplicationLevel: () => isDeduplicationLevel,
|
|
80
86
|
isDynamicCropEnabled: () => isDynamicCropEnabled,
|
|
81
87
|
isSegmentVisibleAtTime: () => isSegmentVisibleAtTime,
|
|
88
|
+
isValidCaptionPreset: () => isValidCaptionPreset,
|
|
82
89
|
parseHexColor: () => parseHexColor,
|
|
83
90
|
parseTime: () => parseTime,
|
|
84
91
|
preloadFonts: () => preloadFonts,
|
|
92
|
+
resolveCaptionStyle: () => resolveCaptionStyle,
|
|
85
93
|
resolveDeduplicationConfig: () => resolveDeduplicationConfig,
|
|
86
94
|
resolveElementPositions: () => resolveElementPositions,
|
|
87
95
|
useFontsLoaded: () => useFontsLoaded,
|
|
@@ -2051,6 +2059,194 @@ function VideoEditorComposition({
|
|
|
2051
2059
|
] });
|
|
2052
2060
|
}
|
|
2053
2061
|
|
|
2062
|
+
// src/compositions/AutoCaptionComposition.tsx
|
|
2063
|
+
var import_remotion6 = require("remotion");
|
|
2064
|
+
|
|
2065
|
+
// src/components/CaptionOverlay.tsx
|
|
2066
|
+
var import_react6 = require("react");
|
|
2067
|
+
var import_remotion5 = require("remotion");
|
|
2068
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2069
|
+
function createPages(words, wordsPerPage) {
|
|
2070
|
+
const pages = [];
|
|
2071
|
+
for (let i = 0; i < words.length; i += wordsPerPage) {
|
|
2072
|
+
const pageWords = words.slice(i, i + wordsPerPage);
|
|
2073
|
+
if (pageWords.length > 0) {
|
|
2074
|
+
pages.push({
|
|
2075
|
+
words: pageWords,
|
|
2076
|
+
startMs: pageWords[0].startMs,
|
|
2077
|
+
endMs: pageWords[pageWords.length - 1].endMs
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
return pages;
|
|
2082
|
+
}
|
|
2083
|
+
function hexToRgba2(hex, opacity) {
|
|
2084
|
+
const cleanHex = hex.replace("#", "");
|
|
2085
|
+
const r = parseInt(cleanHex.substring(0, 2), 16);
|
|
2086
|
+
const g = parseInt(cleanHex.substring(2, 4), 16);
|
|
2087
|
+
const b = parseInt(cleanHex.substring(4, 6), 16);
|
|
2088
|
+
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|
2089
|
+
}
|
|
2090
|
+
function CaptionOverlay({ captions, style, previewTimeMs }) {
|
|
2091
|
+
const frame = (0, import_remotion5.useCurrentFrame)();
|
|
2092
|
+
const { fps, width, height } = (0, import_remotion5.useVideoConfig)();
|
|
2093
|
+
const currentMs = previewTimeMs ?? frame / fps * 1e3;
|
|
2094
|
+
const pages = (0, import_react6.useMemo)(
|
|
2095
|
+
() => createPages(captions, style.wordsPerPage),
|
|
2096
|
+
[captions, style.wordsPerPage]
|
|
2097
|
+
);
|
|
2098
|
+
const currentPage = pages.find(
|
|
2099
|
+
(page) => currentMs >= page.startMs && currentMs <= page.endMs
|
|
2100
|
+
);
|
|
2101
|
+
if (!currentPage) return null;
|
|
2102
|
+
const maxWidthPx = style.maxWidth / 100 * width;
|
|
2103
|
+
const scaleFactor = height / 1920;
|
|
2104
|
+
const scaledFontSize = style.fontSize * scaleFactor;
|
|
2105
|
+
const scaledStrokeWidth = style.strokeWidth * scaleFactor;
|
|
2106
|
+
const scaledYOffset = style.yOffset * scaleFactor;
|
|
2107
|
+
const scaledBorderRadius = (style.backgroundBorderRadius ?? 0) * scaleFactor;
|
|
2108
|
+
const getPositionStyles = () => {
|
|
2109
|
+
const base = {
|
|
2110
|
+
position: "absolute",
|
|
2111
|
+
left: "50%"
|
|
2112
|
+
};
|
|
2113
|
+
switch (style.position) {
|
|
2114
|
+
case "top":
|
|
2115
|
+
return {
|
|
2116
|
+
...base,
|
|
2117
|
+
top: scaledYOffset,
|
|
2118
|
+
transform: "translateX(-50%)"
|
|
2119
|
+
};
|
|
2120
|
+
case "center":
|
|
2121
|
+
return {
|
|
2122
|
+
...base,
|
|
2123
|
+
top: "50%",
|
|
2124
|
+
transform: "translate(-50%, -50%)"
|
|
2125
|
+
};
|
|
2126
|
+
case "bottom":
|
|
2127
|
+
default:
|
|
2128
|
+
return {
|
|
2129
|
+
...base,
|
|
2130
|
+
bottom: scaledYOffset,
|
|
2131
|
+
transform: "translateX(-50%)"
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
};
|
|
2135
|
+
const containerStyles = {
|
|
2136
|
+
...getPositionStyles(),
|
|
2137
|
+
maxWidth: maxWidthPx,
|
|
2138
|
+
textAlign: "center",
|
|
2139
|
+
// Add padding if background is enabled
|
|
2140
|
+
padding: style.backgroundColor ? `${scaledFontSize * 0.3}px ${scaledFontSize * 0.5}px` : 0,
|
|
2141
|
+
// Background styling
|
|
2142
|
+
backgroundColor: style.backgroundColor ? hexToRgba2(style.backgroundColor, style.backgroundOpacity ?? 0.7) : "transparent",
|
|
2143
|
+
borderRadius: scaledBorderRadius
|
|
2144
|
+
};
|
|
2145
|
+
const numericFontWeight = style.fontWeight === "black" ? 900 : style.fontWeight === "bold" ? 700 : 400;
|
|
2146
|
+
const buildStrokeShadow = () => {
|
|
2147
|
+
if (scaledStrokeWidth <= 0) return "none";
|
|
2148
|
+
const sw = scaledStrokeWidth;
|
|
2149
|
+
const sc = style.strokeColor;
|
|
2150
|
+
return `
|
|
2151
|
+
-${sw}px -${sw}px 0 ${sc},
|
|
2152
|
+
${sw}px -${sw}px 0 ${sc},
|
|
2153
|
+
-${sw}px ${sw}px 0 ${sc},
|
|
2154
|
+
${sw}px ${sw}px 0 ${sc},
|
|
2155
|
+
0 -${sw}px 0 ${sc},
|
|
2156
|
+
0 ${sw}px 0 ${sc},
|
|
2157
|
+
-${sw}px 0 0 ${sc},
|
|
2158
|
+
${sw}px 0 0 ${sc}
|
|
2159
|
+
`;
|
|
2160
|
+
};
|
|
2161
|
+
const textContainerStyles = {
|
|
2162
|
+
fontFamily: `"${style.fontName}", sans-serif`,
|
|
2163
|
+
fontSize: scaledFontSize,
|
|
2164
|
+
fontWeight: numericFontWeight,
|
|
2165
|
+
lineHeight: 1.2,
|
|
2166
|
+
wordBreak: "break-word",
|
|
2167
|
+
textShadow: buildStrokeShadow()
|
|
2168
|
+
};
|
|
2169
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: containerStyles, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: textContainerStyles, children: currentPage.words.map((word, index) => {
|
|
2170
|
+
const isActive = currentMs >= word.startMs && currentMs <= word.endMs;
|
|
2171
|
+
const color = isActive ? style.highlightColor : style.fontColor;
|
|
2172
|
+
let scale = 1;
|
|
2173
|
+
if (style.enableAnimation && isActive) {
|
|
2174
|
+
const wordDuration = word.endMs - word.startMs;
|
|
2175
|
+
if (wordDuration > 0) {
|
|
2176
|
+
const progress = (currentMs - word.startMs) / wordDuration;
|
|
2177
|
+
scale = (0, import_remotion5.interpolate)(
|
|
2178
|
+
progress,
|
|
2179
|
+
[0, 0.15, 0.3, 1],
|
|
2180
|
+
[1, 1.15, 1.05, 1],
|
|
2181
|
+
{
|
|
2182
|
+
extrapolateLeft: "clamp",
|
|
2183
|
+
extrapolateRight: "clamp"
|
|
2184
|
+
}
|
|
2185
|
+
);
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
2189
|
+
"span",
|
|
2190
|
+
{
|
|
2191
|
+
style: {
|
|
2192
|
+
color,
|
|
2193
|
+
display: "inline-block",
|
|
2194
|
+
transform: `scale(${scale})`,
|
|
2195
|
+
transformOrigin: "center center"
|
|
2196
|
+
},
|
|
2197
|
+
children: [
|
|
2198
|
+
word.word,
|
|
2199
|
+
index < currentPage.words.length - 1 ? " " : ""
|
|
2200
|
+
]
|
|
2201
|
+
},
|
|
2202
|
+
`${word.word}-${word.startMs}-${index}`
|
|
2203
|
+
);
|
|
2204
|
+
}) }) });
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
// src/compositions/AutoCaptionComposition.tsx
|
|
2208
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2209
|
+
function AutoCaptionComposition({
|
|
2210
|
+
videoUrl,
|
|
2211
|
+
captions,
|
|
2212
|
+
style
|
|
2213
|
+
}) {
|
|
2214
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_remotion6.AbsoluteFill, { style: { backgroundColor: "#000000" }, children: [
|
|
2215
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2216
|
+
import_remotion6.OffthreadVideo,
|
|
2217
|
+
{
|
|
2218
|
+
src: videoUrl,
|
|
2219
|
+
style: {
|
|
2220
|
+
width: "100%",
|
|
2221
|
+
height: "100%",
|
|
2222
|
+
objectFit: "contain"
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
),
|
|
2226
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CaptionOverlay, { captions, style })
|
|
2227
|
+
] });
|
|
2228
|
+
}
|
|
2229
|
+
function AutoCaptionCompositionWithVideo({
|
|
2230
|
+
videoUrl,
|
|
2231
|
+
captions,
|
|
2232
|
+
style
|
|
2233
|
+
}) {
|
|
2234
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_remotion6.AbsoluteFill, { style: { backgroundColor: "#000000" }, children: [
|
|
2235
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2236
|
+
import_remotion6.Video,
|
|
2237
|
+
{
|
|
2238
|
+
src: videoUrl,
|
|
2239
|
+
style: {
|
|
2240
|
+
width: "100%",
|
|
2241
|
+
height: "100%",
|
|
2242
|
+
objectFit: "contain"
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
),
|
|
2246
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CaptionOverlay, { captions, style })
|
|
2247
|
+
] });
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2054
2250
|
// src/utils/fit.ts
|
|
2055
2251
|
function calculateFitDimensions({
|
|
2056
2252
|
sourceWidth,
|
|
@@ -2205,11 +2401,140 @@ function generateOverlayId() {
|
|
|
2205
2401
|
return `overlay-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
2206
2402
|
}
|
|
2207
2403
|
|
|
2404
|
+
// src/utils/captionPresets.ts
|
|
2405
|
+
var CAPTION_PRESETS = {
|
|
2406
|
+
/**
|
|
2407
|
+
* Hormozi style - bold, attention-grabbing captions
|
|
2408
|
+
* Yellow highlight on white text with thick black stroke
|
|
2409
|
+
*/
|
|
2410
|
+
hormozi: {
|
|
2411
|
+
fontName: "Montserrat",
|
|
2412
|
+
fontSize: 80,
|
|
2413
|
+
fontWeight: "black",
|
|
2414
|
+
fontColor: "#FFFFFF",
|
|
2415
|
+
highlightColor: "#FFFF00",
|
|
2416
|
+
strokeColor: "#000000",
|
|
2417
|
+
strokeWidth: 3,
|
|
2418
|
+
position: "bottom",
|
|
2419
|
+
yOffset: 100,
|
|
2420
|
+
maxWidth: 90,
|
|
2421
|
+
enableAnimation: true,
|
|
2422
|
+
wordsPerPage: 3
|
|
2423
|
+
},
|
|
2424
|
+
/**
|
|
2425
|
+
* Minimal style - clean and understated
|
|
2426
|
+
* White on white (no highlight distinction), smaller text
|
|
2427
|
+
*/
|
|
2428
|
+
minimal: {
|
|
2429
|
+
fontName: "Inter",
|
|
2430
|
+
fontSize: 60,
|
|
2431
|
+
fontWeight: "bold",
|
|
2432
|
+
fontColor: "#FFFFFF",
|
|
2433
|
+
highlightColor: "#FFFFFF",
|
|
2434
|
+
strokeColor: "#000000",
|
|
2435
|
+
strokeWidth: 2,
|
|
2436
|
+
position: "bottom",
|
|
2437
|
+
yOffset: 80,
|
|
2438
|
+
maxWidth: 85,
|
|
2439
|
+
enableAnimation: false,
|
|
2440
|
+
wordsPerPage: 5
|
|
2441
|
+
},
|
|
2442
|
+
/**
|
|
2443
|
+
* Bold Pop style - maximum impact, one word at a time
|
|
2444
|
+
* Yellow text with red highlight, centered position
|
|
2445
|
+
*/
|
|
2446
|
+
"bold-pop": {
|
|
2447
|
+
fontName: "Bangers",
|
|
2448
|
+
fontSize: 90,
|
|
2449
|
+
fontWeight: "bold",
|
|
2450
|
+
fontColor: "#FFFF00",
|
|
2451
|
+
highlightColor: "#FF0000",
|
|
2452
|
+
strokeColor: "#000000",
|
|
2453
|
+
strokeWidth: 4,
|
|
2454
|
+
position: "center",
|
|
2455
|
+
yOffset: 0,
|
|
2456
|
+
maxWidth: 80,
|
|
2457
|
+
enableAnimation: true,
|
|
2458
|
+
wordsPerPage: 2
|
|
2459
|
+
},
|
|
2460
|
+
/**
|
|
2461
|
+
* Clean style - professional look with background
|
|
2462
|
+
* White text with blue highlight, semi-transparent background
|
|
2463
|
+
*/
|
|
2464
|
+
clean: {
|
|
2465
|
+
fontName: "Roboto",
|
|
2466
|
+
fontSize: 55,
|
|
2467
|
+
fontWeight: "normal",
|
|
2468
|
+
fontColor: "#FFFFFF",
|
|
2469
|
+
highlightColor: "#3B82F6",
|
|
2470
|
+
strokeColor: "#000000",
|
|
2471
|
+
strokeWidth: 0,
|
|
2472
|
+
backgroundColor: "#000000",
|
|
2473
|
+
backgroundOpacity: 0.7,
|
|
2474
|
+
backgroundBorderRadius: 8,
|
|
2475
|
+
position: "bottom",
|
|
2476
|
+
yOffset: 80,
|
|
2477
|
+
maxWidth: 90,
|
|
2478
|
+
enableAnimation: false,
|
|
2479
|
+
wordsPerPage: 6
|
|
2480
|
+
},
|
|
2481
|
+
/**
|
|
2482
|
+
* Neon style - vibrant, eye-catching colors
|
|
2483
|
+
* Cyan text with magenta highlight
|
|
2484
|
+
*/
|
|
2485
|
+
neon: {
|
|
2486
|
+
fontName: "Poppins",
|
|
2487
|
+
fontSize: 70,
|
|
2488
|
+
fontWeight: "bold",
|
|
2489
|
+
fontColor: "#00FFFF",
|
|
2490
|
+
highlightColor: "#FF00FF",
|
|
2491
|
+
strokeColor: "#000000",
|
|
2492
|
+
strokeWidth: 3,
|
|
2493
|
+
position: "bottom",
|
|
2494
|
+
yOffset: 100,
|
|
2495
|
+
maxWidth: 85,
|
|
2496
|
+
enableAnimation: true,
|
|
2497
|
+
wordsPerPage: 3
|
|
2498
|
+
}
|
|
2499
|
+
};
|
|
2500
|
+
var DEFAULT_CAPTION_STYLE = CAPTION_PRESETS.hormozi;
|
|
2501
|
+
function resolveCaptionStyle(style) {
|
|
2502
|
+
if (!style) {
|
|
2503
|
+
return { ...DEFAULT_CAPTION_STYLE };
|
|
2504
|
+
}
|
|
2505
|
+
const presetValues = style.preset ? CAPTION_PRESETS[style.preset] : {};
|
|
2506
|
+
const defaults = DEFAULT_CAPTION_STYLE;
|
|
2507
|
+
return {
|
|
2508
|
+
preset: style.preset,
|
|
2509
|
+
fontName: style.fontName ?? presetValues.fontName ?? defaults.fontName,
|
|
2510
|
+
fontSize: style.fontSize ?? presetValues.fontSize ?? defaults.fontSize,
|
|
2511
|
+
fontWeight: style.fontWeight ?? presetValues.fontWeight ?? defaults.fontWeight,
|
|
2512
|
+
fontColor: style.fontColor ?? presetValues.fontColor ?? defaults.fontColor,
|
|
2513
|
+
highlightColor: style.highlightColor ?? presetValues.highlightColor ?? defaults.highlightColor,
|
|
2514
|
+
strokeColor: style.strokeColor ?? presetValues.strokeColor ?? defaults.strokeColor,
|
|
2515
|
+
strokeWidth: style.strokeWidth ?? presetValues.strokeWidth ?? defaults.strokeWidth,
|
|
2516
|
+
backgroundColor: style.backgroundColor ?? presetValues.backgroundColor,
|
|
2517
|
+
backgroundOpacity: style.backgroundOpacity ?? presetValues.backgroundOpacity,
|
|
2518
|
+
backgroundBorderRadius: style.backgroundBorderRadius ?? presetValues.backgroundBorderRadius,
|
|
2519
|
+
position: style.position ?? presetValues.position ?? defaults.position,
|
|
2520
|
+
yOffset: style.yOffset ?? presetValues.yOffset ?? defaults.yOffset,
|
|
2521
|
+
maxWidth: style.maxWidth ?? presetValues.maxWidth ?? defaults.maxWidth,
|
|
2522
|
+
enableAnimation: style.enableAnimation ?? presetValues.enableAnimation ?? defaults.enableAnimation,
|
|
2523
|
+
wordsPerPage: style.wordsPerPage ?? presetValues.wordsPerPage ?? defaults.wordsPerPage
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
function getCaptionPresetNames() {
|
|
2527
|
+
return Object.keys(CAPTION_PRESETS);
|
|
2528
|
+
}
|
|
2529
|
+
function isValidCaptionPreset(name) {
|
|
2530
|
+
return name in CAPTION_PRESETS;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2208
2533
|
// src/hooks/index.ts
|
|
2209
|
-
var
|
|
2534
|
+
var import_react7 = require("react");
|
|
2210
2535
|
function useFontsLoaded() {
|
|
2211
|
-
const [loaded, setLoaded] = (0,
|
|
2212
|
-
(0,
|
|
2536
|
+
const [loaded, setLoaded] = (0, import_react7.useState)(areFontsLoaded());
|
|
2537
|
+
(0, import_react7.useEffect)(() => {
|
|
2213
2538
|
if (!loaded) {
|
|
2214
2539
|
preloadFonts().then(() => setLoaded(true)).catch(console.error);
|
|
2215
2540
|
}
|
|
@@ -2217,8 +2542,8 @@ function useFontsLoaded() {
|
|
|
2217
2542
|
return loaded;
|
|
2218
2543
|
}
|
|
2219
2544
|
function useImageLoader(src) {
|
|
2220
|
-
const [image, setImage] = (0,
|
|
2221
|
-
(0,
|
|
2545
|
+
const [image, setImage] = (0, import_react7.useState)(null);
|
|
2546
|
+
(0, import_react7.useEffect)(() => {
|
|
2222
2547
|
if (!src) {
|
|
2223
2548
|
setImage(null);
|
|
2224
2549
|
return;
|
|
@@ -2238,9 +2563,9 @@ function useImageLoader(src) {
|
|
|
2238
2563
|
return image;
|
|
2239
2564
|
}
|
|
2240
2565
|
function useImagePreloader(sources) {
|
|
2241
|
-
const [images, setImages] = (0,
|
|
2242
|
-
const [loaded, setLoaded] = (0,
|
|
2243
|
-
(0,
|
|
2566
|
+
const [images, setImages] = (0, import_react7.useState)({});
|
|
2567
|
+
const [loaded, setLoaded] = (0, import_react7.useState)(false);
|
|
2568
|
+
(0, import_react7.useEffect)(() => {
|
|
2244
2569
|
const entries = Object.entries(sources);
|
|
2245
2570
|
if (entries.length === 0) {
|
|
2246
2571
|
setLoaded(true);
|
|
@@ -2278,7 +2603,7 @@ function useImagePreloader(sources) {
|
|
|
2278
2603
|
return { loaded, images };
|
|
2279
2604
|
}
|
|
2280
2605
|
function useResolvedPositions(elements, textValues) {
|
|
2281
|
-
return (0,
|
|
2606
|
+
return (0, import_react7.useMemo)(() => {
|
|
2282
2607
|
if (elements.length === 0) {
|
|
2283
2608
|
return { elements: [], errors: [] };
|
|
2284
2609
|
}
|
|
@@ -2287,8 +2612,8 @@ function useResolvedPositions(elements, textValues) {
|
|
|
2287
2612
|
}
|
|
2288
2613
|
|
|
2289
2614
|
// src/Root.tsx
|
|
2290
|
-
var
|
|
2291
|
-
var
|
|
2615
|
+
var import_remotion7 = require("remotion");
|
|
2616
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
2292
2617
|
var defaultImageProps = {
|
|
2293
2618
|
config: {
|
|
2294
2619
|
width: 1080,
|
|
@@ -2361,9 +2686,9 @@ var calculateImageMetadata = async ({ props }) => {
|
|
|
2361
2686
|
};
|
|
2362
2687
|
};
|
|
2363
2688
|
var RenderRoot = () => {
|
|
2364
|
-
return /* @__PURE__ */ (0,
|
|
2365
|
-
/* @__PURE__ */ (0,
|
|
2366
|
-
|
|
2689
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
2690
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2691
|
+
import_remotion7.Composition,
|
|
2367
2692
|
{
|
|
2368
2693
|
id: "ImageEditorComposition",
|
|
2369
2694
|
component: ImageComp,
|
|
@@ -2375,8 +2700,8 @@ var RenderRoot = () => {
|
|
|
2375
2700
|
defaultProps: defaultImageProps
|
|
2376
2701
|
}
|
|
2377
2702
|
),
|
|
2378
|
-
/* @__PURE__ */ (0,
|
|
2379
|
-
|
|
2703
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2704
|
+
import_remotion7.Composition,
|
|
2380
2705
|
{
|
|
2381
2706
|
id: "VideoEditorComposition",
|
|
2382
2707
|
component: VideoComp,
|
|
@@ -2392,7 +2717,12 @@ var RenderRoot = () => {
|
|
|
2392
2717
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2393
2718
|
0 && (module.exports = {
|
|
2394
2719
|
APPLE_EMOJI_FONT,
|
|
2720
|
+
AutoCaptionComposition,
|
|
2721
|
+
AutoCaptionCompositionWithVideo,
|
|
2722
|
+
CAPTION_PRESETS,
|
|
2723
|
+
CaptionOverlay,
|
|
2395
2724
|
DEDUPLICATION_LEVELS,
|
|
2725
|
+
DEFAULT_CAPTION_STYLE,
|
|
2396
2726
|
DIMENSION_PRESETS,
|
|
2397
2727
|
FONT_FAMILIES,
|
|
2398
2728
|
FONT_URLS,
|
|
@@ -2430,6 +2760,7 @@ var RenderRoot = () => {
|
|
|
2430
2760
|
generateSegmentId,
|
|
2431
2761
|
getBaseSegments,
|
|
2432
2762
|
getBorderRadii,
|
|
2763
|
+
getCaptionPresetNames,
|
|
2433
2764
|
getDependentElements,
|
|
2434
2765
|
getFontFamily,
|
|
2435
2766
|
getOverlays,
|
|
@@ -2440,9 +2771,11 @@ var RenderRoot = () => {
|
|
|
2440
2771
|
isDeduplicationLevel,
|
|
2441
2772
|
isDynamicCropEnabled,
|
|
2442
2773
|
isSegmentVisibleAtTime,
|
|
2774
|
+
isValidCaptionPreset,
|
|
2443
2775
|
parseHexColor,
|
|
2444
2776
|
parseTime,
|
|
2445
2777
|
preloadFonts,
|
|
2778
|
+
resolveCaptionStyle,
|
|
2446
2779
|
resolveDeduplicationConfig,
|
|
2447
2780
|
resolveElementPositions,
|
|
2448
2781
|
useFontsLoaded,
|
package/dist/index.mjs
CHANGED
|
@@ -1957,6 +1957,194 @@ function VideoEditorComposition({
|
|
|
1957
1957
|
] });
|
|
1958
1958
|
}
|
|
1959
1959
|
|
|
1960
|
+
// src/compositions/AutoCaptionComposition.tsx
|
|
1961
|
+
import { AbsoluteFill as AbsoluteFill3, Video, OffthreadVideo as OffthreadVideo2 } from "remotion";
|
|
1962
|
+
|
|
1963
|
+
// src/components/CaptionOverlay.tsx
|
|
1964
|
+
import { useMemo as useMemo6 } from "react";
|
|
1965
|
+
import { useCurrentFrame as useCurrentFrame4, useVideoConfig as useVideoConfig4, interpolate } from "remotion";
|
|
1966
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1967
|
+
function createPages(words, wordsPerPage) {
|
|
1968
|
+
const pages = [];
|
|
1969
|
+
for (let i = 0; i < words.length; i += wordsPerPage) {
|
|
1970
|
+
const pageWords = words.slice(i, i + wordsPerPage);
|
|
1971
|
+
if (pageWords.length > 0) {
|
|
1972
|
+
pages.push({
|
|
1973
|
+
words: pageWords,
|
|
1974
|
+
startMs: pageWords[0].startMs,
|
|
1975
|
+
endMs: pageWords[pageWords.length - 1].endMs
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
return pages;
|
|
1980
|
+
}
|
|
1981
|
+
function hexToRgba2(hex, opacity) {
|
|
1982
|
+
const cleanHex = hex.replace("#", "");
|
|
1983
|
+
const r = parseInt(cleanHex.substring(0, 2), 16);
|
|
1984
|
+
const g = parseInt(cleanHex.substring(2, 4), 16);
|
|
1985
|
+
const b = parseInt(cleanHex.substring(4, 6), 16);
|
|
1986
|
+
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|
1987
|
+
}
|
|
1988
|
+
function CaptionOverlay({ captions, style, previewTimeMs }) {
|
|
1989
|
+
const frame = useCurrentFrame4();
|
|
1990
|
+
const { fps, width, height } = useVideoConfig4();
|
|
1991
|
+
const currentMs = previewTimeMs ?? frame / fps * 1e3;
|
|
1992
|
+
const pages = useMemo6(
|
|
1993
|
+
() => createPages(captions, style.wordsPerPage),
|
|
1994
|
+
[captions, style.wordsPerPage]
|
|
1995
|
+
);
|
|
1996
|
+
const currentPage = pages.find(
|
|
1997
|
+
(page) => currentMs >= page.startMs && currentMs <= page.endMs
|
|
1998
|
+
);
|
|
1999
|
+
if (!currentPage) return null;
|
|
2000
|
+
const maxWidthPx = style.maxWidth / 100 * width;
|
|
2001
|
+
const scaleFactor = height / 1920;
|
|
2002
|
+
const scaledFontSize = style.fontSize * scaleFactor;
|
|
2003
|
+
const scaledStrokeWidth = style.strokeWidth * scaleFactor;
|
|
2004
|
+
const scaledYOffset = style.yOffset * scaleFactor;
|
|
2005
|
+
const scaledBorderRadius = (style.backgroundBorderRadius ?? 0) * scaleFactor;
|
|
2006
|
+
const getPositionStyles = () => {
|
|
2007
|
+
const base = {
|
|
2008
|
+
position: "absolute",
|
|
2009
|
+
left: "50%"
|
|
2010
|
+
};
|
|
2011
|
+
switch (style.position) {
|
|
2012
|
+
case "top":
|
|
2013
|
+
return {
|
|
2014
|
+
...base,
|
|
2015
|
+
top: scaledYOffset,
|
|
2016
|
+
transform: "translateX(-50%)"
|
|
2017
|
+
};
|
|
2018
|
+
case "center":
|
|
2019
|
+
return {
|
|
2020
|
+
...base,
|
|
2021
|
+
top: "50%",
|
|
2022
|
+
transform: "translate(-50%, -50%)"
|
|
2023
|
+
};
|
|
2024
|
+
case "bottom":
|
|
2025
|
+
default:
|
|
2026
|
+
return {
|
|
2027
|
+
...base,
|
|
2028
|
+
bottom: scaledYOffset,
|
|
2029
|
+
transform: "translateX(-50%)"
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
};
|
|
2033
|
+
const containerStyles = {
|
|
2034
|
+
...getPositionStyles(),
|
|
2035
|
+
maxWidth: maxWidthPx,
|
|
2036
|
+
textAlign: "center",
|
|
2037
|
+
// Add padding if background is enabled
|
|
2038
|
+
padding: style.backgroundColor ? `${scaledFontSize * 0.3}px ${scaledFontSize * 0.5}px` : 0,
|
|
2039
|
+
// Background styling
|
|
2040
|
+
backgroundColor: style.backgroundColor ? hexToRgba2(style.backgroundColor, style.backgroundOpacity ?? 0.7) : "transparent",
|
|
2041
|
+
borderRadius: scaledBorderRadius
|
|
2042
|
+
};
|
|
2043
|
+
const numericFontWeight = style.fontWeight === "black" ? 900 : style.fontWeight === "bold" ? 700 : 400;
|
|
2044
|
+
const buildStrokeShadow = () => {
|
|
2045
|
+
if (scaledStrokeWidth <= 0) return "none";
|
|
2046
|
+
const sw = scaledStrokeWidth;
|
|
2047
|
+
const sc = style.strokeColor;
|
|
2048
|
+
return `
|
|
2049
|
+
-${sw}px -${sw}px 0 ${sc},
|
|
2050
|
+
${sw}px -${sw}px 0 ${sc},
|
|
2051
|
+
-${sw}px ${sw}px 0 ${sc},
|
|
2052
|
+
${sw}px ${sw}px 0 ${sc},
|
|
2053
|
+
0 -${sw}px 0 ${sc},
|
|
2054
|
+
0 ${sw}px 0 ${sc},
|
|
2055
|
+
-${sw}px 0 0 ${sc},
|
|
2056
|
+
${sw}px 0 0 ${sc}
|
|
2057
|
+
`;
|
|
2058
|
+
};
|
|
2059
|
+
const textContainerStyles = {
|
|
2060
|
+
fontFamily: `"${style.fontName}", sans-serif`,
|
|
2061
|
+
fontSize: scaledFontSize,
|
|
2062
|
+
fontWeight: numericFontWeight,
|
|
2063
|
+
lineHeight: 1.2,
|
|
2064
|
+
wordBreak: "break-word",
|
|
2065
|
+
textShadow: buildStrokeShadow()
|
|
2066
|
+
};
|
|
2067
|
+
return /* @__PURE__ */ jsx6("div", { style: containerStyles, children: /* @__PURE__ */ jsx6("div", { style: textContainerStyles, children: currentPage.words.map((word, index) => {
|
|
2068
|
+
const isActive = currentMs >= word.startMs && currentMs <= word.endMs;
|
|
2069
|
+
const color = isActive ? style.highlightColor : style.fontColor;
|
|
2070
|
+
let scale = 1;
|
|
2071
|
+
if (style.enableAnimation && isActive) {
|
|
2072
|
+
const wordDuration = word.endMs - word.startMs;
|
|
2073
|
+
if (wordDuration > 0) {
|
|
2074
|
+
const progress = (currentMs - word.startMs) / wordDuration;
|
|
2075
|
+
scale = interpolate(
|
|
2076
|
+
progress,
|
|
2077
|
+
[0, 0.15, 0.3, 1],
|
|
2078
|
+
[1, 1.15, 1.05, 1],
|
|
2079
|
+
{
|
|
2080
|
+
extrapolateLeft: "clamp",
|
|
2081
|
+
extrapolateRight: "clamp"
|
|
2082
|
+
}
|
|
2083
|
+
);
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
return /* @__PURE__ */ jsxs4(
|
|
2087
|
+
"span",
|
|
2088
|
+
{
|
|
2089
|
+
style: {
|
|
2090
|
+
color,
|
|
2091
|
+
display: "inline-block",
|
|
2092
|
+
transform: `scale(${scale})`,
|
|
2093
|
+
transformOrigin: "center center"
|
|
2094
|
+
},
|
|
2095
|
+
children: [
|
|
2096
|
+
word.word,
|
|
2097
|
+
index < currentPage.words.length - 1 ? " " : ""
|
|
2098
|
+
]
|
|
2099
|
+
},
|
|
2100
|
+
`${word.word}-${word.startMs}-${index}`
|
|
2101
|
+
);
|
|
2102
|
+
}) }) });
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
// src/compositions/AutoCaptionComposition.tsx
|
|
2106
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2107
|
+
function AutoCaptionComposition({
|
|
2108
|
+
videoUrl,
|
|
2109
|
+
captions,
|
|
2110
|
+
style
|
|
2111
|
+
}) {
|
|
2112
|
+
return /* @__PURE__ */ jsxs5(AbsoluteFill3, { style: { backgroundColor: "#000000" }, children: [
|
|
2113
|
+
/* @__PURE__ */ jsx7(
|
|
2114
|
+
OffthreadVideo2,
|
|
2115
|
+
{
|
|
2116
|
+
src: videoUrl,
|
|
2117
|
+
style: {
|
|
2118
|
+
width: "100%",
|
|
2119
|
+
height: "100%",
|
|
2120
|
+
objectFit: "contain"
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
),
|
|
2124
|
+
/* @__PURE__ */ jsx7(CaptionOverlay, { captions, style })
|
|
2125
|
+
] });
|
|
2126
|
+
}
|
|
2127
|
+
function AutoCaptionCompositionWithVideo({
|
|
2128
|
+
videoUrl,
|
|
2129
|
+
captions,
|
|
2130
|
+
style
|
|
2131
|
+
}) {
|
|
2132
|
+
return /* @__PURE__ */ jsxs5(AbsoluteFill3, { style: { backgroundColor: "#000000" }, children: [
|
|
2133
|
+
/* @__PURE__ */ jsx7(
|
|
2134
|
+
Video,
|
|
2135
|
+
{
|
|
2136
|
+
src: videoUrl,
|
|
2137
|
+
style: {
|
|
2138
|
+
width: "100%",
|
|
2139
|
+
height: "100%",
|
|
2140
|
+
objectFit: "contain"
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
),
|
|
2144
|
+
/* @__PURE__ */ jsx7(CaptionOverlay, { captions, style })
|
|
2145
|
+
] });
|
|
2146
|
+
}
|
|
2147
|
+
|
|
1960
2148
|
// src/utils/fit.ts
|
|
1961
2149
|
function calculateFitDimensions({
|
|
1962
2150
|
sourceWidth,
|
|
@@ -2111,8 +2299,137 @@ function generateOverlayId() {
|
|
|
2111
2299
|
return `overlay-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
2112
2300
|
}
|
|
2113
2301
|
|
|
2302
|
+
// src/utils/captionPresets.ts
|
|
2303
|
+
var CAPTION_PRESETS = {
|
|
2304
|
+
/**
|
|
2305
|
+
* Hormozi style - bold, attention-grabbing captions
|
|
2306
|
+
* Yellow highlight on white text with thick black stroke
|
|
2307
|
+
*/
|
|
2308
|
+
hormozi: {
|
|
2309
|
+
fontName: "Montserrat",
|
|
2310
|
+
fontSize: 80,
|
|
2311
|
+
fontWeight: "black",
|
|
2312
|
+
fontColor: "#FFFFFF",
|
|
2313
|
+
highlightColor: "#FFFF00",
|
|
2314
|
+
strokeColor: "#000000",
|
|
2315
|
+
strokeWidth: 3,
|
|
2316
|
+
position: "bottom",
|
|
2317
|
+
yOffset: 100,
|
|
2318
|
+
maxWidth: 90,
|
|
2319
|
+
enableAnimation: true,
|
|
2320
|
+
wordsPerPage: 3
|
|
2321
|
+
},
|
|
2322
|
+
/**
|
|
2323
|
+
* Minimal style - clean and understated
|
|
2324
|
+
* White on white (no highlight distinction), smaller text
|
|
2325
|
+
*/
|
|
2326
|
+
minimal: {
|
|
2327
|
+
fontName: "Inter",
|
|
2328
|
+
fontSize: 60,
|
|
2329
|
+
fontWeight: "bold",
|
|
2330
|
+
fontColor: "#FFFFFF",
|
|
2331
|
+
highlightColor: "#FFFFFF",
|
|
2332
|
+
strokeColor: "#000000",
|
|
2333
|
+
strokeWidth: 2,
|
|
2334
|
+
position: "bottom",
|
|
2335
|
+
yOffset: 80,
|
|
2336
|
+
maxWidth: 85,
|
|
2337
|
+
enableAnimation: false,
|
|
2338
|
+
wordsPerPage: 5
|
|
2339
|
+
},
|
|
2340
|
+
/**
|
|
2341
|
+
* Bold Pop style - maximum impact, one word at a time
|
|
2342
|
+
* Yellow text with red highlight, centered position
|
|
2343
|
+
*/
|
|
2344
|
+
"bold-pop": {
|
|
2345
|
+
fontName: "Bangers",
|
|
2346
|
+
fontSize: 90,
|
|
2347
|
+
fontWeight: "bold",
|
|
2348
|
+
fontColor: "#FFFF00",
|
|
2349
|
+
highlightColor: "#FF0000",
|
|
2350
|
+
strokeColor: "#000000",
|
|
2351
|
+
strokeWidth: 4,
|
|
2352
|
+
position: "center",
|
|
2353
|
+
yOffset: 0,
|
|
2354
|
+
maxWidth: 80,
|
|
2355
|
+
enableAnimation: true,
|
|
2356
|
+
wordsPerPage: 2
|
|
2357
|
+
},
|
|
2358
|
+
/**
|
|
2359
|
+
* Clean style - professional look with background
|
|
2360
|
+
* White text with blue highlight, semi-transparent background
|
|
2361
|
+
*/
|
|
2362
|
+
clean: {
|
|
2363
|
+
fontName: "Roboto",
|
|
2364
|
+
fontSize: 55,
|
|
2365
|
+
fontWeight: "normal",
|
|
2366
|
+
fontColor: "#FFFFFF",
|
|
2367
|
+
highlightColor: "#3B82F6",
|
|
2368
|
+
strokeColor: "#000000",
|
|
2369
|
+
strokeWidth: 0,
|
|
2370
|
+
backgroundColor: "#000000",
|
|
2371
|
+
backgroundOpacity: 0.7,
|
|
2372
|
+
backgroundBorderRadius: 8,
|
|
2373
|
+
position: "bottom",
|
|
2374
|
+
yOffset: 80,
|
|
2375
|
+
maxWidth: 90,
|
|
2376
|
+
enableAnimation: false,
|
|
2377
|
+
wordsPerPage: 6
|
|
2378
|
+
},
|
|
2379
|
+
/**
|
|
2380
|
+
* Neon style - vibrant, eye-catching colors
|
|
2381
|
+
* Cyan text with magenta highlight
|
|
2382
|
+
*/
|
|
2383
|
+
neon: {
|
|
2384
|
+
fontName: "Poppins",
|
|
2385
|
+
fontSize: 70,
|
|
2386
|
+
fontWeight: "bold",
|
|
2387
|
+
fontColor: "#00FFFF",
|
|
2388
|
+
highlightColor: "#FF00FF",
|
|
2389
|
+
strokeColor: "#000000",
|
|
2390
|
+
strokeWidth: 3,
|
|
2391
|
+
position: "bottom",
|
|
2392
|
+
yOffset: 100,
|
|
2393
|
+
maxWidth: 85,
|
|
2394
|
+
enableAnimation: true,
|
|
2395
|
+
wordsPerPage: 3
|
|
2396
|
+
}
|
|
2397
|
+
};
|
|
2398
|
+
var DEFAULT_CAPTION_STYLE = CAPTION_PRESETS.hormozi;
|
|
2399
|
+
function resolveCaptionStyle(style) {
|
|
2400
|
+
if (!style) {
|
|
2401
|
+
return { ...DEFAULT_CAPTION_STYLE };
|
|
2402
|
+
}
|
|
2403
|
+
const presetValues = style.preset ? CAPTION_PRESETS[style.preset] : {};
|
|
2404
|
+
const defaults = DEFAULT_CAPTION_STYLE;
|
|
2405
|
+
return {
|
|
2406
|
+
preset: style.preset,
|
|
2407
|
+
fontName: style.fontName ?? presetValues.fontName ?? defaults.fontName,
|
|
2408
|
+
fontSize: style.fontSize ?? presetValues.fontSize ?? defaults.fontSize,
|
|
2409
|
+
fontWeight: style.fontWeight ?? presetValues.fontWeight ?? defaults.fontWeight,
|
|
2410
|
+
fontColor: style.fontColor ?? presetValues.fontColor ?? defaults.fontColor,
|
|
2411
|
+
highlightColor: style.highlightColor ?? presetValues.highlightColor ?? defaults.highlightColor,
|
|
2412
|
+
strokeColor: style.strokeColor ?? presetValues.strokeColor ?? defaults.strokeColor,
|
|
2413
|
+
strokeWidth: style.strokeWidth ?? presetValues.strokeWidth ?? defaults.strokeWidth,
|
|
2414
|
+
backgroundColor: style.backgroundColor ?? presetValues.backgroundColor,
|
|
2415
|
+
backgroundOpacity: style.backgroundOpacity ?? presetValues.backgroundOpacity,
|
|
2416
|
+
backgroundBorderRadius: style.backgroundBorderRadius ?? presetValues.backgroundBorderRadius,
|
|
2417
|
+
position: style.position ?? presetValues.position ?? defaults.position,
|
|
2418
|
+
yOffset: style.yOffset ?? presetValues.yOffset ?? defaults.yOffset,
|
|
2419
|
+
maxWidth: style.maxWidth ?? presetValues.maxWidth ?? defaults.maxWidth,
|
|
2420
|
+
enableAnimation: style.enableAnimation ?? presetValues.enableAnimation ?? defaults.enableAnimation,
|
|
2421
|
+
wordsPerPage: style.wordsPerPage ?? presetValues.wordsPerPage ?? defaults.wordsPerPage
|
|
2422
|
+
};
|
|
2423
|
+
}
|
|
2424
|
+
function getCaptionPresetNames() {
|
|
2425
|
+
return Object.keys(CAPTION_PRESETS);
|
|
2426
|
+
}
|
|
2427
|
+
function isValidCaptionPreset(name) {
|
|
2428
|
+
return name in CAPTION_PRESETS;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2114
2431
|
// src/hooks/index.ts
|
|
2115
|
-
import { useEffect as useEffect2, useState as useState2, useMemo as
|
|
2432
|
+
import { useEffect as useEffect2, useState as useState2, useMemo as useMemo7 } from "react";
|
|
2116
2433
|
function useFontsLoaded() {
|
|
2117
2434
|
const [loaded, setLoaded] = useState2(areFontsLoaded());
|
|
2118
2435
|
useEffect2(() => {
|
|
@@ -2184,7 +2501,7 @@ function useImagePreloader(sources) {
|
|
|
2184
2501
|
return { loaded, images };
|
|
2185
2502
|
}
|
|
2186
2503
|
function useResolvedPositions(elements, textValues) {
|
|
2187
|
-
return
|
|
2504
|
+
return useMemo7(() => {
|
|
2188
2505
|
if (elements.length === 0) {
|
|
2189
2506
|
return { elements: [], errors: [] };
|
|
2190
2507
|
}
|
|
@@ -2194,7 +2511,7 @@ function useResolvedPositions(elements, textValues) {
|
|
|
2194
2511
|
|
|
2195
2512
|
// src/Root.tsx
|
|
2196
2513
|
import { Composition } from "remotion";
|
|
2197
|
-
import { Fragment, jsx as
|
|
2514
|
+
import { Fragment, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2198
2515
|
var defaultImageProps = {
|
|
2199
2516
|
config: {
|
|
2200
2517
|
width: 1080,
|
|
@@ -2267,8 +2584,8 @@ var calculateImageMetadata = async ({ props }) => {
|
|
|
2267
2584
|
};
|
|
2268
2585
|
};
|
|
2269
2586
|
var RenderRoot = () => {
|
|
2270
|
-
return /* @__PURE__ */
|
|
2271
|
-
/* @__PURE__ */
|
|
2587
|
+
return /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
2588
|
+
/* @__PURE__ */ jsx8(
|
|
2272
2589
|
Composition,
|
|
2273
2590
|
{
|
|
2274
2591
|
id: "ImageEditorComposition",
|
|
@@ -2281,7 +2598,7 @@ var RenderRoot = () => {
|
|
|
2281
2598
|
defaultProps: defaultImageProps
|
|
2282
2599
|
}
|
|
2283
2600
|
),
|
|
2284
|
-
/* @__PURE__ */
|
|
2601
|
+
/* @__PURE__ */ jsx8(
|
|
2285
2602
|
Composition,
|
|
2286
2603
|
{
|
|
2287
2604
|
id: "VideoEditorComposition",
|
|
@@ -2297,7 +2614,12 @@ var RenderRoot = () => {
|
|
|
2297
2614
|
};
|
|
2298
2615
|
export {
|
|
2299
2616
|
APPLE_EMOJI_FONT,
|
|
2617
|
+
AutoCaptionComposition,
|
|
2618
|
+
AutoCaptionCompositionWithVideo,
|
|
2619
|
+
CAPTION_PRESETS,
|
|
2620
|
+
CaptionOverlay,
|
|
2300
2621
|
DEDUPLICATION_LEVELS,
|
|
2622
|
+
DEFAULT_CAPTION_STYLE,
|
|
2301
2623
|
DIMENSION_PRESETS,
|
|
2302
2624
|
FONT_FAMILIES,
|
|
2303
2625
|
FONT_URLS,
|
|
@@ -2335,6 +2657,7 @@ export {
|
|
|
2335
2657
|
generateSegmentId,
|
|
2336
2658
|
getBaseSegments,
|
|
2337
2659
|
getBorderRadii,
|
|
2660
|
+
getCaptionPresetNames,
|
|
2338
2661
|
getDependentElements,
|
|
2339
2662
|
getFontFamily,
|
|
2340
2663
|
getOverlays,
|
|
@@ -2345,9 +2668,11 @@ export {
|
|
|
2345
2668
|
isDeduplicationLevel,
|
|
2346
2669
|
isDynamicCropEnabled,
|
|
2347
2670
|
isSegmentVisibleAtTime,
|
|
2671
|
+
isValidCaptionPreset,
|
|
2348
2672
|
parseHexColor,
|
|
2349
2673
|
parseTime,
|
|
2350
2674
|
preloadFonts,
|
|
2675
|
+
resolveCaptionStyle,
|
|
2351
2676
|
resolveDeduplicationConfig,
|
|
2352
2677
|
resolveElementPositions,
|
|
2353
2678
|
useFontsLoaded,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ugcinc-render",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Unified rendering package for UGC Inc - shared types, components, and compositions for pixel-perfect client/server rendering",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|