Package Exports
- @fly-cut/caption-react
- @fly-cut/caption-react/styles
Readme
@flycut/caption-react
đĨ FlyCut Caption - A complete video subtitle editing React component with AI-powered speech recognition and visual editing capabilities.
⨠Features
- đ¤ AI-Powered Speech Recognition - Automatic subtitle generation using Whisper model
- âī¸ Visual Subtitle Editing - Interactive timeline-based subtitle editing
- đ¨ Customizable Styling - Flexible subtitle appearance configuration
- đŦ Video Processing - Built-in video cutting and processing capabilities
- đ Multi-language Support - i18n support for Chinese and English
- đ Theme Support - Light/dark mode with system preference detection
- đą Responsive Design - Works seamlessly on desktop and mobile devices
- đ TypeScript Ready - Full TypeScript support with type definitions
đĻ Installation
npm install @flycut/caption-react
# or
yarn add @flycut/caption-react
# or
pnpm add @flycut/caption-react
đ Quick Start
Basic Usage
import React from 'react'
import { FlyCutCaption } from '@flycut/caption-react'
import '@flycut/caption-react/styles'
function App() {
return (
<div style={{ height: '100vh' }}>
<FlyCutCaption />
</div>
)
}
With Configuration
import React from 'react'
import { FlyCutCaption } from '@flycut/caption-react'
import '@flycut/caption-react/styles'
function App() {
return (
<div style={{ height: '100vh' }}>
<FlyCutCaption
config={{
theme: 'auto',
language: 'zh-CN',
asrLanguage: 'auto',
enableDragDrop: true,
enableExport: true,
enableVideoProcessing: true,
maxFileSize: 500,
supportedFormats: ['mp4', 'webm', 'avi', 'mov', 'mp3', 'wav', 'ogg']
}}
onReady={() => console.log('FlyCut Caption is ready')}
onFileSelected={(file) => console.log('File selected:', file.name)}
onSubtitleGenerated={(subtitles) => console.log('Subtitles generated:', subtitles.length)}
onSubtitleChanged={(subtitles) => console.log('Subtitles changed:', subtitles.length)}
onVideoProcessed={(blob, filename) => {
// Handle processed video
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
a.click()
}}
onExportComplete={(blob, filename) => {
console.log('Export complete:', filename)
}}
onError={(error) => console.error('Error:', error)}
onProgress={(stage, progress) => console.log(\`\${stage}: \${progress}%\`)}
/>
</div>
)
}
đ Usage Guide
1. Installation & Setup
# Install the package
npm install @flycut/caption-react
# For TypeScript projects, types are included
# No additional @types package needed
2. Import Styles
The component requires CSS styles to work properly:
import '@flycut/caption-react/styles'
// or specific CSS file
import '@flycut/caption-react/dist/caption-react.css'
3. Basic Integration
import { FlyCutCaption } from '@flycut/caption-react'
import '@flycut/caption-react/styles'
function VideoEditor() {
return (
<div className="video-editor-container">
<FlyCutCaption />
</div>
)
}
4. Event Handling
import { FlyCutCaption } from '@flycut/caption-react'
function VideoEditorWithEvents() {
const handleFileSelected = (file: File) => {
console.log('Selected file:', file.name, file.size)
}
const handleSubtitleGenerated = (subtitles: SubtitleChunk[]) => {
console.log('Generated subtitles:', subtitles.length)
// Save subtitles to your backend
saveSubtitles(subtitles)
}
const handleVideoProcessed = (blob: Blob, filename: string) => {
// Handle the processed video
const url = URL.createObjectURL(blob)
// Download or upload to your server
downloadFile(url, filename)
}
const handleError = (error: Error) => {
// Handle errors gracefully
console.error('FlyCut Caption error:', error)
showErrorNotification(error.message)
}
return (
<FlyCutCaption
onFileSelected={handleFileSelected}
onSubtitleGenerated={handleSubtitleGenerated}
onVideoProcessed={handleVideoProcessed}
onError={handleError}
/>
)
}
5. Configuration Options
import { FlyCutCaption } from '@flycut/caption-react'
function ConfiguredEditor() {
const config = {
// Theme settings
theme: 'dark' as const,
// Language settings
language: 'en-US',
asrLanguage: 'en',
// Feature toggles
enableDragDrop: true,
enableExport: true,
enableVideoProcessing: true,
// File constraints
maxFileSize: 1000, // 1GB
supportedFormats: ['mp4', 'webm', 'mov']
}
return (
<FlyCutCaption config={config} />
)
}
6. Custom Styling
import { FlyCutCaption } from '@flycut/caption-react'
import './custom-styles.css'
function StyledEditor() {
return (
<FlyCutCaption
className="my-custom-editor"
style={{
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
}}
/>
)
}
/* custom-styles.css */
.my-custom-editor {
--flycut-primary: #10b981;
--flycut-border-radius: 12px;
}
.my-custom-editor .subtitle-item {
border-radius: var(--flycut-border-radius);
}
7. Integration with State Management
import { useState } from 'react'
import { FlyCutCaption } from '@flycut/caption-react'
import { useStore } from 'zustand'
function EditorWithStore() {
const { setSubtitles, setCurrentVideo } = useStore()
const [isProcessing, setIsProcessing] = useState(false)
return (
<FlyCutCaption
onFileSelected={(file) => {
setCurrentVideo(file)
}}
onSubtitleGenerated={(subtitles) => {
setSubtitles(subtitles)
}}
onProgress={(stage, progress) => {
setIsProcessing(progress < 100)
}}
/>
)
}
đ API Reference
FlyCutCaptionProps
Prop | Type | Default | Description |
---|---|---|---|
className |
string |
undefined |
Custom CSS class name |
style |
CSSProperties |
undefined |
Custom inline styles |
config |
FlyCutCaptionConfig |
defaultConfig |
Component configuration |
locale |
FlyCutCaptionLocale |
undefined |
Custom language pack |
onReady |
() => void |
undefined |
Called when component is ready |
onFileSelected |
(file: File) => void |
undefined |
Called when a file is selected |
onSubtitleGenerated |
(subtitles: SubtitleChunk[]) => void |
undefined |
Called when subtitles are generated |
onSubtitleChanged |
(subtitles: SubtitleChunk[]) => void |
undefined |
Called when subtitles are changed |
onVideoProcessed |
(blob: Blob, filename: string) => void |
undefined |
Called when video processing is complete |
onExportComplete |
(blob: Blob, filename: string) => void |
undefined |
Called when export is complete |
onError |
(error: Error) => void |
undefined |
Called when an error occurs |
onProgress |
(stage: string, progress: number) => void |
undefined |
Called to report progress updates |
onLanguageChange |
(language: string) => void |
undefined |
Called when language changes |
FlyCutCaptionConfig
Property | Type | Default | Description |
---|---|---|---|
theme |
'light' | 'dark' | 'auto' |
'auto' |
Theme mode |
language |
string |
'zh-CN' |
Interface language |
asrLanguage |
string |
'auto' |
ASR recognition language |
enableDragDrop |
boolean |
true |
Enable drag and drop file upload |
enableExport |
boolean |
true |
Enable export functionality |
enableVideoProcessing |
boolean |
true |
Enable video processing functionality |
enableThemeToggle |
boolean |
true |
Enable theme toggle button |
enableLanguageSelector |
boolean |
true |
Enable language selector |
maxFileSize |
number |
500 |
Maximum file size in MB |
supportedFormats |
string[] |
['mp4', 'webm', 'avi', 'mov', 'mp3', 'wav', 'ogg'] |
Supported file formats |
đ¨ Styling
The component comes with built-in styles that you need to import:
import '@flycut/caption-react/styles'
You can also customize the appearance by:
- CSS Custom Properties: Override CSS variables for colors and spacing
- Custom CSS Classes: Use the
className
prop to apply custom styles - Theme Configuration: Use the
theme
config option for light/dark modes
CSS Variables
:root {
--flycut-primary: #3b82f6;
--flycut-background: #ffffff;
--flycut-foreground: #1f2937;
--flycut-muted: #f3f4f6;
--flycut-border: #e5e7eb;
}
.dark {
--flycut-background: #111827;
--flycut-foreground: #f9fafb;
--flycut-muted: #374151;
--flycut-border: #4b5563;
}
đ Internationalization
FlyCut Caption supports componentized internationalization with built-in and custom language packs. The component automatically syncs external language changes with internal UI components.
Built-in Language Packs
import { FlyCutCaption, zhCN, enUS } from '@flycut/caption-react'
// Using built-in Chinese language pack
<FlyCutCaption
config={{ language: 'zh' }}
locale={zhCN}
/>
// Using built-in English language pack
<FlyCutCaption
config={{ language: 'en' }}
locale={enUS}
/>
Custom Language Packs
import { FlyCutCaption, type FlyCutCaptionLocale } from '@flycut/caption-react'
// Create custom language pack (Japanese example)
const customJaJP: FlyCutCaptionLocale = {
common: {
loading: 'čĒãŋčžŧãŋä¸...',
error: 'ã¨ãŠãŧ',
success: 'æå',
confirm: 'įĸēčĒ',
cancel: 'ããŖãŗãģãĢ',
ok: 'OK',
// ... more common translations
},
components: {
fileUpload: {
dragDropText: 'ãããĒããĄã¤ãĢããããĢããŠãã°ããããã¯ãĒãã¯ããĻ鏿',
selectFile: 'ããĄã¤ãĢã鏿',
supportedFormats: 'ãĩããŧãåŊĸåŧīŧ',
// ... more component translations
},
subtitleEditor: {
title: 'ååšã¨ããŖãŋãŧ',
addSubtitle: 'ååšãčŋŊå ',
deleteSelected: '鏿é
įŽãåé¤',
// ... more editor translations
},
// ... other component translations
},
messages: {
fileUpload: {
uploadSuccess: 'ããĄã¤ãĢãĸããããŧãæå',
uploadFailed: 'ããĄã¤ãĢãĸããããŧãå¤ąæ',
// ... more message translations
},
// ... other message translations
}
}
// Use custom language pack
<FlyCutCaption
config={{ language: 'ja' }}
locale={customJaJP}
/>
Componentized Language Switching
The new componentized approach provides better language synchronization between external controls and internal components:
import { useState } from 'react'
import { FlyCutCaption, zhCN, enUS, type FlyCutCaptionLocale } from '@flycut/caption-react'
function App() {
const [currentLanguage, setCurrentLanguage] = useState('zh')
const [currentLocale, setCurrentLocale] = useState<FlyCutCaptionLocale | undefined>(undefined)
const handleLanguageChange = (language: string) => {
console.log('Language changed to:', language)
setCurrentLanguage(language)
// Set appropriate language pack based on language
switch (language) {
case 'zh':
case 'zh-CN':
setCurrentLocale(zhCN)
break
case 'en':
case 'en-US':
setCurrentLocale(enUS)
break
case 'ja':
case 'ja-JP':
setCurrentLocale(customJaJP) // Custom Japanese pack
break
default:
setCurrentLocale(undefined) // Use default language pack
}
}
return (
<div className="min-h-screen bg-background">
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold text-center mb-8">
FlyCut Caption Internationalization Demo
</h1>
{/* External Language Controls */}
<div className="mb-8 text-center space-y-4">
<div>
<h2 className="text-xl font-semibold mb-4">Language Switcher</h2>
<div className="flex justify-center gap-4">
<button
className={`px-4 py-2 rounded ${currentLanguage === 'zh' ? 'bg-primary text-primary-foreground' : 'bg-secondary'}`}
onClick={() => handleLanguageChange('zh')}
>
䏿 (Built-in)
</button>
<button
className={`px-4 py-2 rounded ${currentLanguage === 'en' ? 'bg-primary text-primary-foreground' : 'bg-secondary'}`}
onClick={() => handleLanguageChange('en')}
>
English (Built-in)
</button>
<button
className={`px-4 py-2 rounded ${currentLanguage === 'ja' ? 'bg-primary text-primary-foreground' : 'bg-secondary'}`}
onClick={() => handleLanguageChange('ja')}
>
æĨæŦčĒ (Custom)
</button>
</div>
</div>
<div className="bg-muted p-4 rounded-lg">
<p className="text-sm">
<strong>Current Language:</strong> {currentLanguage}
</p>
<p className="text-sm">
<strong>Locale Type:</strong> {currentLocale ? 'Custom Locale' : 'Built-in Locale'}
</p>
</div>
</div>
{/* FlyCut Caption Component */}
<div className="border rounded-lg p-4">
<h2 className="text-xl font-semibold mb-4">FlyCut Caption Component</h2>
<FlyCutCaption
config={{
theme: 'auto',
language: currentLanguage,
enableThemeToggle: true,
enableLanguageSelector: true // Internal language selector will sync with external changes
}}
locale={currentLocale}
onLanguageChange={handleLanguageChange} // Sync internal changes back to external state
onError={(error) => {
console.error('Component error:', error)
}}
onProgress={(stage, progress) => {
console.log(`Progress: ${stage} - ${progress}%`)
}}
/>
</div>
</div>
</div>
)
}
Available Language Packs
Language | Import | Description |
---|---|---|
Chinese (Simplified) | zhCN |
įŽäŊ䏿 |
English (US) | enUS |
English (United States) |
Default | defaultLocale |
Same as zhCN |
Locale API
// Import locale utilities
import { LocaleProvider, useLocale, useTranslation } from '@flycut/caption-react'
// Use LocaleProvider for nested components
<LocaleProvider language="zh" locale={zhCN}>
<YourComponent />
</LocaleProvider>
// Access locale context
const { t, setLanguage, registerLocale } = useLocale()
// Register custom locale
registerLocale('fr', frenchLocale)
// Switch language programmatically
setLanguage('fr')
đ Detailed internationalization guide: See INTERNATIONALIZATION.md for complete documentation on language packs, custom locales, and advanced i18n features.
đŦ Video Processing
The component supports various video processing features:
Supported Formats
- Video: MP4, WebM, AVI, MOV
- Audio: MP3, WAV, OGG
Processing Options
- Quality: Low, Medium, High
- Format: MP4, WebM
- Subtitle Processing: Burn-in, Separate file
- Audio Preservation: Enabled by default
đą Browser Support
- Chrome 88+
- Firefox 78+
- Safari 14+
- Edge 88+
đĄ Examples & Best Practices
Complete React Application
import React, { useState, useCallback } from 'react'
import { FlyCutCaption, zhCN, enUS, type FlyCutCaptionLocale } from '@flycut/caption-react'
import '@flycut/caption-react/styles'
function VideoEditorApp() {
const [language, setLanguage] = useState<'zh' | 'en'>('zh')
const [subtitles, setSubtitles] = useState([])
const [isProcessing, setIsProcessing] = useState(false)
const locale = language === 'zh' ? zhCN : enUS
const handleLanguageChange = useCallback((newLang: string) => {
setLanguage(newLang as 'zh' | 'en')
}, [])
const handleSubtitleGenerated = useCallback((newSubtitles) => {
setSubtitles(newSubtitles)
// Auto-save to local storage
localStorage.setItem('flycut-subtitles', JSON.stringify(newSubtitles))
}, [])
const handleProgress = useCallback((stage: string, progress: number) => {
setIsProcessing(progress < 100)
}, [])
return (
<div className="min-h-screen bg-gray-50">
<header className="bg-white shadow-sm">
<div className="max-w-7xl mx-auto px-4 py-4">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold">Video Editor</h1>
<div className="flex gap-2">
<button
onClick={() => handleLanguageChange('zh')}
className={language === 'zh' ? 'btn-primary' : 'btn-secondary'}
>
䏿
</button>
<button
onClick={() => handleLanguageChange('en')}
className={language === 'en' ? 'btn-primary' : 'btn-secondary'}
>
English
</button>
</div>
</div>
</div>
</header>
<main className="max-w-7xl mx-auto px-4 py-8">
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
<FlyCutCaption
config={{
theme: 'auto',
language,
enableDragDrop: true,
enableExport: true,
maxFileSize: 1000
}}
locale={locale}
onLanguageChange={handleLanguageChange}
onSubtitleGenerated={handleSubtitleGenerated}
onProgress={handleProgress}
onError={(error) => {
console.error('Error:', error)
// Show user-friendly error message
alert('å¤įčŋį¨ä¸åēį°é蝝īŧ蝎éč¯')
}}
/>
</div>
{isProcessing && (
<div className="mt-4 text-center">
<div className="inline-flex items-center px-4 py-2 bg-blue-100 rounded-lg">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2"></div>
å¤įä¸īŧ蝎į¨å...
</div>
</div>
)}
{subtitles.length > 0 && (
<div className="mt-8 bg-white rounded-lg shadow p-6">
<h2 className="text-lg font-semibold mb-4">įæįååš ({subtitles.length} æĄ)</h2>
<div className="text-sm text-gray-600">
ååšåˇ˛čĒå¨äŋåå°æŦå°åå¨
</div>
</div>
)}
</main>
</div>
)
}
export default VideoEditorApp
Next.js Integration
// pages/editor.tsx
import dynamic from 'next/dynamic'
import { useState } from 'react'
// Dynamically import to avoid SSR issues
const FlyCutCaption = dynamic(
() => import('@flycut/caption-react').then(mod => mod.FlyCutCaption),
{ ssr: false }
)
export default function EditorPage() {
return (
<div style={{ height: '100vh' }}>
<FlyCutCaption
config={{
theme: 'auto',
language: 'zh'
}}
onVideoProcessed={(blob, filename) => {
// Handle video processing result
const url = URL.createObjectURL(blob)
window.open(url, '_blank')
}}
/>
</div>
)
}
Vite Configuration
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
optimizeDeps: {
exclude: ['@huggingface/transformers']
},
server: {
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
}
}
})
Best Practices
- Always import styles: The component requires CSS to work properly
- Handle errors gracefully: Implement proper error boundaries and user feedback
- Optimize for performance: Use dynamic imports for SSR applications
- Provide user feedback: Show loading states and progress indicators
- Responsive design: Ensure your container has appropriate height/width
- Accessibility: The component includes ARIA labels and keyboard navigation
- Memory management: Clean up blob URLs when components unmount
Common Integration Patterns
// With error boundary
import { ErrorBoundary } from 'react-error-boundary'
function EditorWithErrorBoundary() {
return (
<ErrorBoundary fallback={<div>Something went wrong with the video editor</div>}>
<FlyCutCaption />
</ErrorBoundary>
)
}
// With loading state
function EditorWithLoading() {
const [isReady, setIsReady] = useState(false)
if (!isReady) {
return <div>Loading video editor...</div>
}
return (
<FlyCutCaption
onReady={() => setIsReady(true)}
/>
)
}
// With custom container
function ResponsiveEditor() {
return (
<div className="w-full max-w-6xl mx-auto">
<div className="aspect-video bg-gray-100 rounded-lg overflow-hidden">
<FlyCutCaption />
</div>
</div>
)
}
đ§ Development
Prerequisites
- Node.js 18+
- pnpm 8+
Setup
git clone https://github.com/your-username/fly-cut-caption.git
cd fly-cut-caption
pnpm install
Development
# Start development server
pnpm dev
# Build library
pnpm run build:lib
# Build demo
pnpm run build:demo
# Lint code
pnpm lint
# Run test app
cd test-app && pnpm dev
đ License
MIT Š FlyCut Team
đ¤ Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
đ Support
- đ§ Email: support@flycut.dev
- đ Issues: GitHub Issues
- đ Documentation: API Docs
Made with â¤ī¸ by the FlyCut Team