ugcinc-render 1.7.0 → 1.8.1
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 +161 -1
- package/dist/index.d.ts +161 -1
- package/dist/index.js +383 -16
- package/dist/index.mjs +365 -6
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1071,6 +1071,112 @@ declare function isDeduplicationLevel(input: DeduplicationInput): input is Dedup
|
|
|
1071
1071
|
*/
|
|
1072
1072
|
declare function resolveDeduplicationConfig(input: DeduplicationInput): DeduplicationConfig;
|
|
1073
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
|
+
|
|
1074
1180
|
interface ImageEditorCompositionProps {
|
|
1075
1181
|
/** The editor configuration to render (legacy format with pre-resolved segments) */
|
|
1076
1182
|
config?: ImageEditorConfig;
|
|
@@ -1171,6 +1277,16 @@ interface VideoEditorCompositionProps {
|
|
|
1171
1277
|
*/
|
|
1172
1278
|
declare function VideoEditorComposition({ config, sources, textContent, }: VideoEditorCompositionProps): react_jsx_runtime.JSX.Element;
|
|
1173
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
|
+
|
|
1174
1290
|
interface TextElementProps {
|
|
1175
1291
|
segment: TextSegment;
|
|
1176
1292
|
/** Optional scale for high-DPI rendering */
|
|
@@ -1236,6 +1352,11 @@ interface VideoElementProps {
|
|
|
1236
1352
|
*/
|
|
1237
1353
|
declare function VideoElement({ segment, src, startFrame, durationInFrames, scale }: VideoElementProps): react_jsx_runtime.JSX.Element;
|
|
1238
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
|
+
|
|
1239
1360
|
/**
|
|
1240
1361
|
* Font utilities for the rendering system
|
|
1241
1362
|
*/
|
|
@@ -1592,6 +1713,45 @@ declare function generateSegmentId(): string;
|
|
|
1592
1713
|
*/
|
|
1593
1714
|
declare function generateOverlayId(): string;
|
|
1594
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
|
+
|
|
1595
1755
|
/**
|
|
1596
1756
|
* Hook exports for ugcinc-render
|
|
1597
1757
|
*
|
|
@@ -1654,4 +1814,4 @@ declare function useResolvedPositions(elements: ImageEditorElement[], textValues
|
|
|
1654
1814
|
|
|
1655
1815
|
declare const RenderRoot: React.FC;
|
|
1656
1816
|
|
|
1657
|
-
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
|
@@ -1071,6 +1071,112 @@ declare function isDeduplicationLevel(input: DeduplicationInput): input is Dedup
|
|
|
1071
1071
|
*/
|
|
1072
1072
|
declare function resolveDeduplicationConfig(input: DeduplicationInput): DeduplicationConfig;
|
|
1073
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
|
+
|
|
1074
1180
|
interface ImageEditorCompositionProps {
|
|
1075
1181
|
/** The editor configuration to render (legacy format with pre-resolved segments) */
|
|
1076
1182
|
config?: ImageEditorConfig;
|
|
@@ -1171,6 +1277,16 @@ interface VideoEditorCompositionProps {
|
|
|
1171
1277
|
*/
|
|
1172
1278
|
declare function VideoEditorComposition({ config, sources, textContent, }: VideoEditorCompositionProps): react_jsx_runtime.JSX.Element;
|
|
1173
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
|
+
|
|
1174
1290
|
interface TextElementProps {
|
|
1175
1291
|
segment: TextSegment;
|
|
1176
1292
|
/** Optional scale for high-DPI rendering */
|
|
@@ -1236,6 +1352,11 @@ interface VideoElementProps {
|
|
|
1236
1352
|
*/
|
|
1237
1353
|
declare function VideoElement({ segment, src, startFrame, durationInFrames, scale }: VideoElementProps): react_jsx_runtime.JSX.Element;
|
|
1238
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
|
+
|
|
1239
1360
|
/**
|
|
1240
1361
|
* Font utilities for the rendering system
|
|
1241
1362
|
*/
|
|
@@ -1592,6 +1713,45 @@ declare function generateSegmentId(): string;
|
|
|
1592
1713
|
*/
|
|
1593
1714
|
declare function generateOverlayId(): string;
|
|
1594
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
|
+
|
|
1595
1755
|
/**
|
|
1596
1756
|
* Hook exports for ugcinc-render
|
|
1597
1757
|
*
|
|
@@ -1654,4 +1814,4 @@ declare function useResolvedPositions(elements: ImageEditorElement[], textValues
|
|
|
1654
1814
|
|
|
1655
1815
|
declare const RenderRoot: React.FC;
|
|
1656
1816
|
|
|
1657
|
-
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,
|
|
@@ -2309,8 +2634,30 @@ var defaultVideoProps = {
|
|
|
2309
2634
|
sources: {},
|
|
2310
2635
|
scale: 1
|
|
2311
2636
|
};
|
|
2637
|
+
var defaultAutoCaptionProps = {
|
|
2638
|
+
videoUrl: "",
|
|
2639
|
+
width: 1080,
|
|
2640
|
+
height: 1920,
|
|
2641
|
+
durationMs: 1e3,
|
|
2642
|
+
captions: [],
|
|
2643
|
+
style: {
|
|
2644
|
+
fontName: "Montserrat",
|
|
2645
|
+
fontSize: 80,
|
|
2646
|
+
fontWeight: "black",
|
|
2647
|
+
fontColor: "#FFFFFF",
|
|
2648
|
+
highlightColor: "#FFFF00",
|
|
2649
|
+
strokeColor: "#000000",
|
|
2650
|
+
strokeWidth: 3,
|
|
2651
|
+
position: "bottom",
|
|
2652
|
+
yOffset: 100,
|
|
2653
|
+
maxWidth: 90,
|
|
2654
|
+
enableAnimation: true,
|
|
2655
|
+
wordsPerPage: 3
|
|
2656
|
+
}
|
|
2657
|
+
};
|
|
2312
2658
|
var ImageComp = ImageEditorComposition;
|
|
2313
2659
|
var VideoComp = VideoEditorComposition;
|
|
2660
|
+
var AutoCaptionComp = AutoCaptionComposition;
|
|
2314
2661
|
var calculateImageMetadata = async ({ props }) => {
|
|
2315
2662
|
console.log("[calculateMetadata] Starting metadata calculation...");
|
|
2316
2663
|
const canvasWidth = props.width ?? props.config?.width ?? 1080;
|
|
@@ -2361,9 +2708,9 @@ var calculateImageMetadata = async ({ props }) => {
|
|
|
2361
2708
|
};
|
|
2362
2709
|
};
|
|
2363
2710
|
var RenderRoot = () => {
|
|
2364
|
-
return /* @__PURE__ */ (0,
|
|
2365
|
-
/* @__PURE__ */ (0,
|
|
2366
|
-
|
|
2711
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
2712
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2713
|
+
import_remotion7.Composition,
|
|
2367
2714
|
{
|
|
2368
2715
|
id: "ImageEditorComposition",
|
|
2369
2716
|
component: ImageComp,
|
|
@@ -2375,8 +2722,8 @@ var RenderRoot = () => {
|
|
|
2375
2722
|
defaultProps: defaultImageProps
|
|
2376
2723
|
}
|
|
2377
2724
|
),
|
|
2378
|
-
/* @__PURE__ */ (0,
|
|
2379
|
-
|
|
2725
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2726
|
+
import_remotion7.Composition,
|
|
2380
2727
|
{
|
|
2381
2728
|
id: "VideoEditorComposition",
|
|
2382
2729
|
component: VideoComp,
|
|
@@ -2386,13 +2733,30 @@ var RenderRoot = () => {
|
|
|
2386
2733
|
height: 1920,
|
|
2387
2734
|
defaultProps: defaultVideoProps
|
|
2388
2735
|
}
|
|
2736
|
+
),
|
|
2737
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2738
|
+
import_remotion7.Composition,
|
|
2739
|
+
{
|
|
2740
|
+
id: "AutoCaptionComposition",
|
|
2741
|
+
component: AutoCaptionComp,
|
|
2742
|
+
durationInFrames: 300,
|
|
2743
|
+
fps: 30,
|
|
2744
|
+
width: 1080,
|
|
2745
|
+
height: 1920,
|
|
2746
|
+
defaultProps: defaultAutoCaptionProps
|
|
2747
|
+
}
|
|
2389
2748
|
)
|
|
2390
2749
|
] });
|
|
2391
2750
|
};
|
|
2392
2751
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2393
2752
|
0 && (module.exports = {
|
|
2394
2753
|
APPLE_EMOJI_FONT,
|
|
2754
|
+
AutoCaptionComposition,
|
|
2755
|
+
AutoCaptionCompositionWithVideo,
|
|
2756
|
+
CAPTION_PRESETS,
|
|
2757
|
+
CaptionOverlay,
|
|
2395
2758
|
DEDUPLICATION_LEVELS,
|
|
2759
|
+
DEFAULT_CAPTION_STYLE,
|
|
2396
2760
|
DIMENSION_PRESETS,
|
|
2397
2761
|
FONT_FAMILIES,
|
|
2398
2762
|
FONT_URLS,
|
|
@@ -2430,6 +2794,7 @@ var RenderRoot = () => {
|
|
|
2430
2794
|
generateSegmentId,
|
|
2431
2795
|
getBaseSegments,
|
|
2432
2796
|
getBorderRadii,
|
|
2797
|
+
getCaptionPresetNames,
|
|
2433
2798
|
getDependentElements,
|
|
2434
2799
|
getFontFamily,
|
|
2435
2800
|
getOverlays,
|
|
@@ -2440,9 +2805,11 @@ var RenderRoot = () => {
|
|
|
2440
2805
|
isDeduplicationLevel,
|
|
2441
2806
|
isDynamicCropEnabled,
|
|
2442
2807
|
isSegmentVisibleAtTime,
|
|
2808
|
+
isValidCaptionPreset,
|
|
2443
2809
|
parseHexColor,
|
|
2444
2810
|
parseTime,
|
|
2445
2811
|
preloadFonts,
|
|
2812
|
+
resolveCaptionStyle,
|
|
2446
2813
|
resolveDeduplicationConfig,
|
|
2447
2814
|
resolveElementPositions,
|
|
2448
2815
|
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,
|
|
@@ -2215,8 +2532,30 @@ var defaultVideoProps = {
|
|
|
2215
2532
|
sources: {},
|
|
2216
2533
|
scale: 1
|
|
2217
2534
|
};
|
|
2535
|
+
var defaultAutoCaptionProps = {
|
|
2536
|
+
videoUrl: "",
|
|
2537
|
+
width: 1080,
|
|
2538
|
+
height: 1920,
|
|
2539
|
+
durationMs: 1e3,
|
|
2540
|
+
captions: [],
|
|
2541
|
+
style: {
|
|
2542
|
+
fontName: "Montserrat",
|
|
2543
|
+
fontSize: 80,
|
|
2544
|
+
fontWeight: "black",
|
|
2545
|
+
fontColor: "#FFFFFF",
|
|
2546
|
+
highlightColor: "#FFFF00",
|
|
2547
|
+
strokeColor: "#000000",
|
|
2548
|
+
strokeWidth: 3,
|
|
2549
|
+
position: "bottom",
|
|
2550
|
+
yOffset: 100,
|
|
2551
|
+
maxWidth: 90,
|
|
2552
|
+
enableAnimation: true,
|
|
2553
|
+
wordsPerPage: 3
|
|
2554
|
+
}
|
|
2555
|
+
};
|
|
2218
2556
|
var ImageComp = ImageEditorComposition;
|
|
2219
2557
|
var VideoComp = VideoEditorComposition;
|
|
2558
|
+
var AutoCaptionComp = AutoCaptionComposition;
|
|
2220
2559
|
var calculateImageMetadata = async ({ props }) => {
|
|
2221
2560
|
console.log("[calculateMetadata] Starting metadata calculation...");
|
|
2222
2561
|
const canvasWidth = props.width ?? props.config?.width ?? 1080;
|
|
@@ -2267,8 +2606,8 @@ var calculateImageMetadata = async ({ props }) => {
|
|
|
2267
2606
|
};
|
|
2268
2607
|
};
|
|
2269
2608
|
var RenderRoot = () => {
|
|
2270
|
-
return /* @__PURE__ */
|
|
2271
|
-
/* @__PURE__ */
|
|
2609
|
+
return /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
2610
|
+
/* @__PURE__ */ jsx8(
|
|
2272
2611
|
Composition,
|
|
2273
2612
|
{
|
|
2274
2613
|
id: "ImageEditorComposition",
|
|
@@ -2281,7 +2620,7 @@ var RenderRoot = () => {
|
|
|
2281
2620
|
defaultProps: defaultImageProps
|
|
2282
2621
|
}
|
|
2283
2622
|
),
|
|
2284
|
-
/* @__PURE__ */
|
|
2623
|
+
/* @__PURE__ */ jsx8(
|
|
2285
2624
|
Composition,
|
|
2286
2625
|
{
|
|
2287
2626
|
id: "VideoEditorComposition",
|
|
@@ -2292,12 +2631,29 @@ var RenderRoot = () => {
|
|
|
2292
2631
|
height: 1920,
|
|
2293
2632
|
defaultProps: defaultVideoProps
|
|
2294
2633
|
}
|
|
2634
|
+
),
|
|
2635
|
+
/* @__PURE__ */ jsx8(
|
|
2636
|
+
Composition,
|
|
2637
|
+
{
|
|
2638
|
+
id: "AutoCaptionComposition",
|
|
2639
|
+
component: AutoCaptionComp,
|
|
2640
|
+
durationInFrames: 300,
|
|
2641
|
+
fps: 30,
|
|
2642
|
+
width: 1080,
|
|
2643
|
+
height: 1920,
|
|
2644
|
+
defaultProps: defaultAutoCaptionProps
|
|
2645
|
+
}
|
|
2295
2646
|
)
|
|
2296
2647
|
] });
|
|
2297
2648
|
};
|
|
2298
2649
|
export {
|
|
2299
2650
|
APPLE_EMOJI_FONT,
|
|
2651
|
+
AutoCaptionComposition,
|
|
2652
|
+
AutoCaptionCompositionWithVideo,
|
|
2653
|
+
CAPTION_PRESETS,
|
|
2654
|
+
CaptionOverlay,
|
|
2300
2655
|
DEDUPLICATION_LEVELS,
|
|
2656
|
+
DEFAULT_CAPTION_STYLE,
|
|
2301
2657
|
DIMENSION_PRESETS,
|
|
2302
2658
|
FONT_FAMILIES,
|
|
2303
2659
|
FONT_URLS,
|
|
@@ -2335,6 +2691,7 @@ export {
|
|
|
2335
2691
|
generateSegmentId,
|
|
2336
2692
|
getBaseSegments,
|
|
2337
2693
|
getBorderRadii,
|
|
2694
|
+
getCaptionPresetNames,
|
|
2338
2695
|
getDependentElements,
|
|
2339
2696
|
getFontFamily,
|
|
2340
2697
|
getOverlays,
|
|
@@ -2345,9 +2702,11 @@ export {
|
|
|
2345
2702
|
isDeduplicationLevel,
|
|
2346
2703
|
isDynamicCropEnabled,
|
|
2347
2704
|
isSegmentVisibleAtTime,
|
|
2705
|
+
isValidCaptionPreset,
|
|
2348
2706
|
parseHexColor,
|
|
2349
2707
|
parseTime,
|
|
2350
2708
|
preloadFonts,
|
|
2709
|
+
resolveCaptionStyle,
|
|
2351
2710
|
resolveDeduplicationConfig,
|
|
2352
2711
|
resolveElementPositions,
|
|
2353
2712
|
useFontsLoaded,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ugcinc-render",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
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",
|