diff --git a/live-wallpaper-creator/.gitignore b/live-wallpaper-creator/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/live-wallpaper-creator/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/live-wallpaper-creator/README.md b/live-wallpaper-creator/README.md
new file mode 100644
index 0000000..40b38e0
--- /dev/null
+++ b/live-wallpaper-creator/README.md
@@ -0,0 +1,91 @@
+# Live Wallpaper Creator
+
+A web-based tool for creating animated wallpapers with customizable overlays. Built with React and FFmpeg.
+
+## Features
+
+- 🖼️ Upload and manipulate background images
+- 🎞️ Export as GIF or MP4
+- ⌨️ Keyboard shortcuts for common actions
+- 📊 Progress tracking for exports
+- 🎨 Modern dark theme UI
+- ♿ Accessibility features
+
+## Keyboard Shortcuts
+
+- `Ctrl/⌘ + O`: Open file picker
+- `Ctrl/⌘ + S`: Save as GIF
+- `Ctrl/⌘ + Shift + S`: Save as MP4
+- `?`: Toggle help dialog
+
+## Browser Requirements
+
+- Modern browser (Chrome, Firefox, or Edge)
+- HTTPS connection (required for SharedArrayBuffer)
+- WebAssembly support
+
+## Development
+
+### Prerequisites
+
+- Node.js 16+
+- npm or yarn
+
+### Setup
+
+1. Clone the repository
+2. Install dependencies:
+ ```bash
+ npm install
+ ```
+3. Start development server:
+ ```bash
+ npm run dev
+ ```
+
+### Building
+
+```bash
+npm run build
+```
+
+The built files will be in the `dist` directory.
+
+## Technical Details
+
+- Built with:
+ - React
+ - Material-UI
+ - FFmpeg.wasm
+ - Vite
+
+- Features:
+ - Client-side video processing
+ - Drag and drop interface
+ - Progress tracking
+ - Accessibility support
+ - Keyboard shortcuts
+
+## Browser Support
+
+The app requires:
+- SharedArrayBuffer support
+- WebAssembly support
+- Secure context (HTTPS)
+
+For best results, use:
+- Chrome (latest)
+- Firefox (latest)
+- Edge (latest)
+
+## Contributing
+
+1. Fork the repository
+2. Create your feature branch
+3. Commit your changes
+4. Push to the branch
+5. Create a Pull Request
+
+## License
+
+MIT License
diff --git a/live-wallpaper-creator/eslint.config.js b/live-wallpaper-creator/eslint.config.js
new file mode 100644
index 0000000..238d2e4
--- /dev/null
+++ b/live-wallpaper-creator/eslint.config.js
@@ -0,0 +1,38 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import react from 'eslint-plugin-react'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+
+export default [
+ { ignores: ['dist'] },
+ {
+ files: ['**/*.{js,jsx}'],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ parserOptions: {
+ ecmaVersion: 'latest',
+ ecmaFeatures: { jsx: true },
+ sourceType: 'module',
+ },
+ },
+ settings: { react: { version: '18.3' } },
+ plugins: {
+ react,
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...js.configs.recommended.rules,
+ ...react.configs.recommended.rules,
+ ...react.configs['jsx-runtime'].rules,
+ ...reactHooks.configs.recommended.rules,
+ 'react/jsx-no-target-blank': 'off',
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+ },
+]
diff --git a/live-wallpaper-creator/index.html b/live-wallpaper-creator/index.html
new file mode 100644
index 0000000..1322bad
--- /dev/null
+++ b/live-wallpaper-creator/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+ Live Wallpaper Creator
+
+
+
+
+
+
diff --git a/live-wallpaper-creator/package.json b/live-wallpaper-creator/package.json
new file mode 100644
index 0000000..af84799
--- /dev/null
+++ b/live-wallpaper-creator/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "live-wallpaper-creator",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@emotion/react": "^11.14.0",
+ "@emotion/styled": "^11.14.0",
+ "@ffmpeg/core": "^0.12.6",
+ "@ffmpeg/ffmpeg": "^0.12.7",
+ "@ffmpeg/util": "^0.12.1",
+ "@mui/icons-material": "^6.4.2",
+ "@mui/material": "^6.4.2",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-draggable": "^4.4.6"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.17.0",
+ "@types/react": "^18.3.18",
+ "@types/react-dom": "^18.3.5",
+ "@vitejs/plugin-react": "^4.3.4",
+ "eslint": "^9.17.0",
+ "eslint-plugin-react": "^7.37.2",
+ "eslint-plugin-react-hooks": "^5.0.0",
+ "eslint-plugin-react-refresh": "^0.4.16",
+ "globals": "^15.14.0",
+ "vite": "^6.0.5"
+ }
+}
diff --git a/live-wallpaper-creator/public/overlays/README.md b/live-wallpaper-creator/public/overlays/README.md
new file mode 100644
index 0000000..ce8557e
--- /dev/null
+++ b/live-wallpaper-creator/public/overlays/README.md
@@ -0,0 +1,36 @@
+# Overlay Assets
+
+This directory contains overlay assets for the Live Wallpaper Creator.
+
+## Structure
+
+```
+overlays/
+├── sparkles/ # Sparkle and glitter effects
+├── effects/ # General visual effects
+├── nature/ # Nature-themed animations
+└── animals/ # Animal animations
+```
+
+## Adding New Overlays
+
+1. Add your GIF/APNG file to the appropriate category directory
+2. Update overlays.json with the new overlay information:
+
+```json
+{
+ "category": [
+ {
+ "name": "Display Name",
+ "file": "filename.gif",
+ "description": "Brief description"
+ }
+ ]
+}
+```
+
+## Requirements
+
+- Files should be transparent GIF or APNG format
+- Keep file sizes reasonable (< 1MB recommended)
+- Use appropriate category or create new one if needed
diff --git a/live-wallpaper-creator/public/overlays/overlays.json b/live-wallpaper-creator/public/overlays/overlays.json
new file mode 100644
index 0000000..739868c
--- /dev/null
+++ b/live-wallpaper-creator/public/overlays/overlays.json
@@ -0,0 +1,34 @@
+{
+ "sparkles": [
+ {
+ "name": "Sparkle Effect",
+ "file": "sparkle.gif",
+ "preview": "previews/sparkle.svg",
+ "description": "Add sparkle effects to your wallpaper"
+ }
+ ],
+ "effects": [
+ {
+ "name": "Glow Effect",
+ "file": "glow.gif",
+ "preview": "previews/glow.svg",
+ "description": "Add a glowing effect"
+ }
+ ],
+ "nature": [
+ {
+ "name": "Falling Leaves",
+ "file": "leaves.gif",
+ "preview": "previews/leaf.svg",
+ "description": "Animated falling leaves effect"
+ }
+ ],
+ "animals": [
+ {
+ "name": "Butterfly",
+ "file": "butterfly.gif",
+ "preview": "previews/butterfly.svg",
+ "description": "Animated butterfly overlay"
+ }
+ ]
+}
diff --git a/live-wallpaper-creator/public/overlays/previews/butterfly.svg b/live-wallpaper-creator/public/overlays/previews/butterfly.svg
new file mode 100644
index 0000000..e8a8ce2
--- /dev/null
+++ b/live-wallpaper-creator/public/overlays/previews/butterfly.svg
@@ -0,0 +1,13 @@
+
diff --git a/live-wallpaper-creator/public/overlays/previews/glow.svg b/live-wallpaper-creator/public/overlays/previews/glow.svg
new file mode 100644
index 0000000..0ea48e6
--- /dev/null
+++ b/live-wallpaper-creator/public/overlays/previews/glow.svg
@@ -0,0 +1,11 @@
+
diff --git a/live-wallpaper-creator/public/overlays/previews/leaf.svg b/live-wallpaper-creator/public/overlays/previews/leaf.svg
new file mode 100644
index 0000000..ae7faf3
--- /dev/null
+++ b/live-wallpaper-creator/public/overlays/previews/leaf.svg
@@ -0,0 +1,12 @@
+
diff --git a/live-wallpaper-creator/public/overlays/previews/sparkle.svg b/live-wallpaper-creator/public/overlays/previews/sparkle.svg
new file mode 100644
index 0000000..7c32eed
--- /dev/null
+++ b/live-wallpaper-creator/public/overlays/previews/sparkle.svg
@@ -0,0 +1,12 @@
+
diff --git a/live-wallpaper-creator/public/vite.svg b/live-wallpaper-creator/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/live-wallpaper-creator/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/live-wallpaper-creator/src/App.css b/live-wallpaper-creator/src/App.css
new file mode 100644
index 0000000..f376002
--- /dev/null
+++ b/live-wallpaper-creator/src/App.css
@@ -0,0 +1,53 @@
+#root {
+ height: 100vh;
+ width: 100vw;
+ overflow: hidden;
+}
+
+.canvas-container {
+ position: relative;
+ flex-grow: 1;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: rgba(0, 0, 0, 0.2);
+ border-radius: 8px;
+ margin: 16px;
+}
+
+.overlay-element {
+ position: absolute;
+ cursor: move;
+ user-select: none;
+}
+
+.overlay-element img {
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+
+.drag-handle {
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ background-color: #90caf9;
+ border-radius: 50%;
+ cursor: pointer;
+}
+
+.drag-handle.top-left { top: -5px; left: -5px; }
+.drag-handle.top-right { top: -5px; right: -5px; }
+.drag-handle.bottom-left { bottom: -5px; left: -5px; }
+.drag-handle.bottom-right { bottom: -5px; right: -5px; }
+
+.overlay-controls {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ display: flex;
+ gap: 4px;
+ background-color: rgba(0, 0, 0, 0.5);
+ padding: 4px;
+ border-radius: 4px;
+}
diff --git a/live-wallpaper-creator/src/App.jsx b/live-wallpaper-creator/src/App.jsx
new file mode 100644
index 0000000..b6b04ea
--- /dev/null
+++ b/live-wallpaper-creator/src/App.jsx
@@ -0,0 +1,113 @@
+import { useState, useEffect } from 'react';
+import { ThemeProvider, CssBaseline, Box, AppBar, Toolbar, Typography, Container, IconButton, Tooltip, LinearProgress } from '@mui/material';
+import { createTheme } from '@mui/material/styles';
+import HelpIcon from '@mui/icons-material/Help';
+import Canvas from './components/Canvas';
+import Sidebar from './components/Sidebar';
+import Controls from './components/Controls';
+import BrowserWarning from './components/BrowserWarning';
+import HelpDialog from './components/HelpDialog';
+import WelcomeDialog from './components/WelcomeDialog';
+import ErrorBoundary from './components/ErrorBoundary';
+import { checkBrowserCompatibility } from './utils/browser-check';
+
+const theme = createTheme({
+ palette: {
+ mode: 'dark',
+ primary: {
+ main: '#90caf9',
+ },
+ secondary: {
+ main: '#f48fb1',
+ },
+ background: {
+ default: '#121212',
+ paper: '#1e1e1e',
+ },
+ },
+});
+
+function App() {
+ const [resolution, setResolution] = useState(null);
+ const [backgroundImage, setBackgroundImage] = useState(null);
+ const [overlays, setOverlays] = useState([]);
+ const [browserIssues, setBrowserIssues] = useState([]);
+ const [helpOpen, setHelpOpen] = useState(false);
+ const [welcomeOpen, setWelcomeOpen] = useState(true);
+ const [ffmpegLoading, setFfmpegLoading] = useState(false);
+
+ useEffect(() => {
+ const { isCompatible, issues } = checkBrowserCompatibility();
+ if (!isCompatible) {
+ setBrowserIssues(issues);
+ }
+
+ const handleKeyPress = (e) => {
+ if (e.key === '?' && !e.ctrlKey && !e.metaKey && !e.altKey) {
+ setHelpOpen(prev => !prev);
+ }
+ };
+
+ window.addEventListener('keydown', handleKeyPress);
+ return () => window.removeEventListener('keydown', handleKeyPress);
+ }, []);
+
+ return (
+
+
+
+ {browserIssues.length > 0 ? (
+
+ ) : !resolution ? (
+ <>
+
+ setWelcomeOpen(false)} />
+ >
+ ) : (
+
+
+
+
+ Live Wallpaper Creator
+
+
+ setHelpOpen(true)}
+ aria-label="Show help"
+ >
+
+
+
+
+ {ffmpegLoading && (
+
+ )}
+
+
+
+ setOverlays([...overlays, overlay])}
+ />
+
+
+
+ setHelpOpen(false)} />
+
+ )}
+
+
+ );
+}
+
+export default App;
diff --git a/live-wallpaper-creator/src/assets/react.svg b/live-wallpaper-creator/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/live-wallpaper-creator/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/live-wallpaper-creator/src/components/BrowserWarning.jsx b/live-wallpaper-creator/src/components/BrowserWarning.jsx
new file mode 100644
index 0000000..c38375b
--- /dev/null
+++ b/live-wallpaper-creator/src/components/BrowserWarning.jsx
@@ -0,0 +1,102 @@
+import { Alert, Box, Typography, List, ListItem, Paper, Divider } from '@mui/material';
+import ErrorIcon from '@mui/icons-material/Error';
+import WarningIcon from '@mui/icons-material/Warning';
+
+const BrowserWarning = ({ issues }) => {
+ const criticalIssues = issues.filter(issue => issue.critical);
+ const nonCriticalIssues = issues.filter(issue => !issue.critical);
+
+ return (
+
+
+ }
+ sx={{
+ mb: 3,
+ '& .MuiAlert-message': { width: '100%' }
+ }}
+ >
+
+ Browser Compatibility Issues Detected
+
+
+ Your browser doesn't meet all the requirements needed to run this application.
+ Please address the following issues:
+
+
+
+ {criticalIssues.length > 0 && (
+
+
+
+ Critical Issues
+
+
+ {criticalIssues.map((issue, index) => (
+
+
+ {issue.feature}
+
+
+ {issue.message}
+
+
+ Solution: {issue.solution}
+
+
+ ))}
+
+
+ )}
+
+ {nonCriticalIssues.length > 0 && (
+ <>
+ {criticalIssues.length > 0 && }
+
+
+
+ Performance Warnings
+
+
+ {nonCriticalIssues.map((issue, index) => (
+
+
+ {issue.feature}
+
+
+ {issue.message}
+
+
+ Solution: {issue.solution}
+
+
+ ))}
+
+
+ >
+ )}
+
+
+
+ For optimal performance, please use Chrome, Firefox, or Edge (latest versions) with HTTPS enabled.
+
+
+
+
+ );
+};
+
+export default BrowserWarning;
diff --git a/live-wallpaper-creator/src/components/Canvas.jsx b/live-wallpaper-creator/src/components/Canvas.jsx
new file mode 100644
index 0000000..37d7602
--- /dev/null
+++ b/live-wallpaper-creator/src/components/Canvas.jsx
@@ -0,0 +1,260 @@
+import { useRef, useEffect, useState, useCallback } from 'react';
+import { Box, Button, ButtonGroup, Tooltip, CircularProgress, Alert, Snackbar, IconButton } from '@mui/material';
+import { styled } from '@mui/material/styles';
+import Draggable from 'react-draggable';
+import { exportAsGif, exportAsMP4 } from '../utils/export';
+import SaveIcon from '@mui/icons-material/Save';
+import MovieIcon from '@mui/icons-material/Movie';
+import CloudUploadIcon from '@mui/icons-material/CloudUpload';
+import LinearProgress from '@mui/material/LinearProgress';
+
+const Input = styled('input')({
+ display: 'none',
+});
+
+const Canvas = ({ resolution, backgroundImage, overlays, onBackgroundSet, onOverlaysChange }) => {
+ const canvasRef = useRef(null);
+ const fileInputRef = useRef(null);
+ const [scale, setScale] = useState(1);
+ const [exporting, setExporting] = useState(false);
+ const [error, setError] = useState(null);
+ const [progress, setProgress] = useState(0);
+
+ useEffect(() => {
+ const canvas = canvasRef.current;
+ const ctx = canvas.getContext('2d');
+ const containerWidth = canvas.parentElement.clientWidth;
+ const containerHeight = canvas.parentElement.clientHeight;
+
+ const scaleX = containerWidth / resolution.width;
+ const scaleY = containerHeight / resolution.height;
+ const newScale = Math.min(scaleX, scaleY) * 0.9;
+ setScale(newScale);
+
+ canvas.style.width = `${resolution.width * newScale}px`;
+ canvas.style.height = `${resolution.height * newScale}px`;
+
+ canvas.width = resolution.width;
+ canvas.height = resolution.height;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ if (backgroundImage) {
+ ctx.drawImage(backgroundImage, 0, 0, canvas.width, canvas.height);
+ }
+
+ overlays.forEach(overlay => {
+ if (overlay.image) {
+ ctx.drawImage(
+ overlay.image,
+ overlay.x,
+ overlay.y,
+ overlay.width,
+ overlay.height
+ );
+ }
+ });
+ }, [resolution, backgroundImage, overlays]);
+
+ const handleKeyPress = useCallback((e) => {
+ if (e.key === 'o' && (e.ctrlKey || e.metaKey)) {
+ e.preventDefault();
+ fileInputRef.current?.click();
+ } else if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
+ e.preventDefault();
+ if (!exporting && backgroundImage) {
+ handleExport(e.shiftKey ? 'mp4' : 'gif');
+ }
+ }
+ }, [exporting, backgroundImage]);
+
+ useEffect(() => {
+ window.addEventListener('keydown', handleKeyPress);
+ return () => window.removeEventListener('keydown', handleKeyPress);
+ }, [handleKeyPress]);
+
+ const handleFileInput = (e) => {
+ const file = e.target.files[0];
+ if (file && file.type.startsWith('image/')) {
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ const img = new Image();
+ img.onload = () => onBackgroundSet(img);
+ img.src = event.target.result;
+ };
+ reader.readAsDataURL(file);
+ }
+ };
+
+ const handleDrop = (e) => {
+ e.preventDefault();
+ const file = e.dataTransfer.files[0];
+ if (file && file.type.startsWith('image/')) {
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ const img = new Image();
+ img.onload = () => onBackgroundSet(img);
+ img.src = event.target.result;
+ };
+ reader.readAsDataURL(file);
+ }
+ };
+
+ const handleExport = async (type) => {
+ try {
+ setExporting(true);
+ setError(null);
+ setProgress(0);
+ const canvas = canvasRef.current;
+ let blob;
+
+ const updateProgress = (p) => {
+ setProgress(Math.round(p * 100));
+ };
+
+ if (type === 'gif') {
+ blob = await exportAsGif(canvas, 30, updateProgress);
+ } else {
+ blob = await exportAsMP4(canvas, 30, updateProgress);
+ }
+
+ // Check file size
+ if (blob.size > 100 * 1024 * 1024) { // 100MB
+ setError('Warning: The exported file is quite large. You might want to reduce the resolution or duration.');
+ }
+
+ // Create download link
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `wallpaper.${type}`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ } catch (error) {
+ console.error(`${type.toUpperCase()} export failed:`, error);
+ setError(`Failed to export ${type.toUpperCase()}: ${error.message}`);
+ } finally {
+ setExporting(false);
+ setProgress(0);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ {exporting && (
+
+
+
+ )}
+ e.preventDefault()}
+ role="region"
+ aria-label="Canvas area"
+ >
+ {!backgroundImage && (
+
+
+ Drag and drop an image or click to upload (Ctrl/⌘ + O)
+
+ )}
+
+
+ setError(null)}
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
+ >
+ setError(null)}
+ severity="error"
+ sx={{ width: '100%' }}
+ role="alert"
+ >
+ {error}
+
+
+
+ );
+};
+
+export default Canvas;
diff --git a/live-wallpaper-creator/src/components/Controls.jsx b/live-wallpaper-creator/src/components/Controls.jsx
new file mode 100644
index 0000000..e6b1f1e
--- /dev/null
+++ b/live-wallpaper-creator/src/components/Controls.jsx
@@ -0,0 +1,83 @@
+import { useState } from 'react';
+import {
+ Box,
+ Container,
+ Paper,
+ Typography,
+ Button,
+ Select,
+ MenuItem,
+ FormControl,
+ InputLabel,
+} from '@mui/material';
+
+const COMMON_RESOLUTIONS = [
+ { label: '1920x1080 (Full HD)', width: 1920, height: 1080 },
+ { label: '2560x1440 (2K)', width: 2560, height: 1440 },
+ { label: '3840x2160 (4K)', width: 3840, height: 2160 },
+ { label: '1280x720 (HD)', width: 1280, height: 720 },
+];
+
+const Controls = ({ onResolutionSet }) => {
+ const [selectedResolution, setSelectedResolution] = useState('');
+
+ const handleResolutionSelect = (event) => {
+ setSelectedResolution(event.target.value);
+ };
+
+ const handleContinue = () => {
+ const resolution = COMMON_RESOLUTIONS.find(
+ (res) => res.label === selectedResolution
+ );
+ if (resolution) {
+ onResolutionSet({ width: resolution.width, height: resolution.height });
+ }
+ };
+
+ return (
+
+
+
+ Live Wallpaper Creator
+
+
+ Choose your wallpaper resolution to get started
+
+
+ Select Resolution
+
+
+
+
+
+ );
+};
+
+export default Controls;
diff --git a/live-wallpaper-creator/src/components/ErrorBoundary.jsx b/live-wallpaper-creator/src/components/ErrorBoundary.jsx
new file mode 100644
index 0000000..53edb98
--- /dev/null
+++ b/live-wallpaper-creator/src/components/ErrorBoundary.jsx
@@ -0,0 +1,123 @@
+import React from 'react';
+import { Box, Typography, Button, Alert, Link } from '@mui/material';
+
+class ErrorBoundary extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = { hasError: false, error: null, errorInfo: null };
+ }
+
+ static getDerivedStateFromError(error) {
+ return { hasError: true };
+ }
+
+ componentDidCatch(error, errorInfo) {
+ this.setState({
+ error: error,
+ errorInfo: errorInfo
+ });
+ console.error('Error caught by boundary:', error, errorInfo);
+ }
+
+ getErrorMessage() {
+ const { error } = this.state;
+ if (!error) return 'An unexpected error occurred';
+
+ switch (error.name) {
+ case 'FFmpegError':
+ return 'Failed to initialize FFmpeg. This could be due to browser compatibility issues.';
+ case 'BrowserCompatibilityError':
+ return `Your browser is missing required features: ${error.features?.join(', ')}`;
+ case 'ExportError':
+ return `Failed to export ${error.type.toUpperCase()}: ${error.message}`;
+ default:
+ return error.message || 'An unexpected error occurred';
+ }
+ }
+
+ getErrorDetails() {
+ const { error } = this.state;
+ if (!error) return null;
+
+ switch (error.name) {
+ case 'FFmpegError':
+ return 'Please make sure you\'re using a modern browser with SharedArrayBuffer support and HTTPS enabled.';
+ case 'BrowserCompatibilityError':
+ return 'Please try using Chrome, Firefox, or Edge (latest versions) with HTTPS enabled.';
+ case 'ExportError':
+ return 'Try reducing the resolution or duration of your wallpaper.';
+ default:
+ return null;
+ }
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return (
+
+
+
+ Something went wrong
+
+
+ {this.getErrorMessage()}
+
+ {this.getErrorDetails() && (
+
+ {this.getErrorDetails()}
+
+ )}
+ {this.state.error?.name === 'BrowserCompatibilityError' && (
+
+ Learn more about{' '}
+
+ browser requirements
+
+
+ )}
+
+
+ {process.env.NODE_ENV === 'development' && (
+
+
+ {this.state.errorInfo?.componentStack}
+
+
+ )}
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
+
+export default ErrorBoundary;
diff --git a/live-wallpaper-creator/src/components/HelpDialog.jsx b/live-wallpaper-creator/src/components/HelpDialog.jsx
new file mode 100644
index 0000000..5a4489f
--- /dev/null
+++ b/live-wallpaper-creator/src/components/HelpDialog.jsx
@@ -0,0 +1,99 @@
+import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography, List, ListItem, ListItemText, Divider } from '@mui/material';
+
+const HelpDialog = ({ open, onClose }) => {
+ return (
+
+ );
+};
+
+export default HelpDialog;
diff --git a/live-wallpaper-creator/src/components/Sidebar.jsx b/live-wallpaper-creator/src/components/Sidebar.jsx
new file mode 100644
index 0000000..894c1a4
--- /dev/null
+++ b/live-wallpaper-creator/src/components/Sidebar.jsx
@@ -0,0 +1,167 @@
+import { useState, useEffect } from 'react';
+import {
+ Paper,
+ Box,
+ Typography,
+ List,
+ ListItem,
+ ListItemText,
+ ListItemIcon,
+ Collapse,
+ IconButton,
+ CircularProgress,
+ Alert,
+} from '@mui/material';
+import ExpandLess from '@mui/icons-material/ExpandLess';
+import ExpandMore from '@mui/icons-material/ExpandMore';
+
+const CATEGORIES = {
+ sparkles: 'Sparkles',
+ effects: 'Effects',
+ nature: 'Nature',
+ animals: 'Animals',
+};
+
+const Sidebar = ({ onOverlaySelect }) => {
+ const [openCategory, setOpenCategory] = useState(null);
+ const [overlays, setOverlays] = useState({});
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const loadOverlays = async () => {
+ try {
+ const response = await fetch('./overlays/overlays.json');
+ if (!response.ok) throw new Error('Failed to load overlays');
+ const data = await response.json();
+ setOverlays(data);
+ } catch (err) {
+ console.error('Failed to load overlays:', err);
+ setError('Failed to load overlays. Please try again later.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ loadOverlays();
+ }, []);
+
+ const handleCategoryClick = (category) => {
+ setOpenCategory(openCategory === category ? null : category);
+ };
+
+ const handleOverlayClick = async (category, overlay) => {
+ try {
+ const response = await fetch(`./overlays/${category}/${overlay.file}`);
+ if (!response.ok) throw new Error('Failed to load overlay');
+ const blob = await response.blob();
+ const url = URL.createObjectURL(blob);
+ const img = new Image();
+ img.onload = () => {
+ onOverlaySelect({
+ image: img,
+ x: 0,
+ y: 0,
+ width: img.width,
+ height: img.height,
+ name: overlay.name,
+ });
+ };
+ img.src = url;
+ } catch (err) {
+ console.error('Failed to load overlay:', err);
+ setError('Failed to load overlay. Please try again.');
+ }
+ };
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ Overlay Elements
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+ {Object.entries(CATEGORIES).map(([key, label]) => (
+
+ handleCategoryClick(key)}
+ sx={{ bgcolor: openCategory === key ? 'action.selected' : 'inherit' }}
+ >
+
+ {openCategory === key ? : }
+
+
+
+ {overlays[key]?.map((overlay, index) => (
+ handleOverlayClick(key, overlay)}
+ >
+ {overlay.preview && (
+
+
+
+ )}
+
+
+ )) || (
+
+
+
+ )}
+
+
+
+ ))}
+
+
+ );
+};
+
+export default Sidebar;
diff --git a/live-wallpaper-creator/src/components/WelcomeDialog.jsx b/live-wallpaper-creator/src/components/WelcomeDialog.jsx
new file mode 100644
index 0000000..b70b83f
--- /dev/null
+++ b/live-wallpaper-creator/src/components/WelcomeDialog.jsx
@@ -0,0 +1,618 @@
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ Typography,
+ List,
+ ListItem,
+ ListItemIcon,
+ ListItemText,
+ Divider,
+ Box,
+ Link,
+ CircularProgress
+} from '@mui/material';
+import CloudUploadIcon from '@mui/icons-material/CloudUpload';
+import AddIcon from '@mui/icons-material/Add';
+import SaveIcon from '@mui/icons-material/Save';
+import HelpIcon from '@mui/icons-material/Help';
+import WarningIcon from '@mui/icons-material/Warning';
+import CheckCircleIcon from '@mui/icons-material/CheckCircle';
+import LaptopIcon from '@mui/icons-material/Laptop';
+
+const WelcomeDialog = ({ open, onClose }) => {
+ const getStartedRef = React.useRef(null);
+ const [loading, setLoading] = React.useState(true);
+
+ // Focus the "Get Started" button when dialog opens
+ React.useEffect(() => {
+ if (open && getStartedRef.current) {
+ setTimeout(() => {
+ getStartedRef.current.focus();
+ }, 100);
+ }
+ }, [open]);
+
+ const [error, setError] = React.useState(null);
+ const [retryCount, setRetryCount] = React.useState(0);
+ const maxRetries = 3;
+
+ const [checkProgress, setCheckProgress] = React.useState(0);
+ const [currentCheck, setCurrentCheck] = React.useState('');
+
+ const checkFeatures = async () => {
+ const features = [
+ { name: 'SharedArrayBuffer', check: () => typeof SharedArrayBuffer !== 'undefined', label: 'Checking memory support...' },
+ { name: 'WebAssembly', check: () => typeof WebAssembly !== 'undefined', label: 'Checking WebAssembly support...' },
+ { name: 'Canvas', check: () => typeof HTMLCanvasElement !== 'undefined', label: 'Checking canvas support...' },
+ { name: 'Blob', check: () => typeof Blob !== 'undefined', label: 'Checking file support...' },
+ { name: 'File API', check: () => typeof File !== 'undefined' && typeof FileReader !== 'undefined', label: 'Checking file API support...' }
+ ];
+
+ const missingFeatures = [];
+ for (let i = 0; i < features.length; i++) {
+ const feature = features[i];
+ setCurrentCheck(feature.label);
+ setCheckProgress((i + 1) / (features.length + 1));
+ await new Promise(resolve => setTimeout(resolve, 200));
+
+ if (!feature.check()) {
+ missingFeatures.push(feature.name);
+ }
+ }
+
+ if (missingFeatures.length > 0) {
+ throw new Error(`Missing required features: ${missingFeatures.join(', ')}`);
+ }
+
+ // Check for secure context
+ setCurrentCheck('Checking security context...');
+ setCheckProgress(1);
+ await new Promise(resolve => setTimeout(resolve, 200));
+
+ if (!window.isSecureContext) {
+ throw new Error('This app requires a secure context (HTTPS) to function properly.');
+ }
+ };
+
+ const handleRetry = () => {
+ if (retryCount < maxRetries) {
+ setRetryCount(prev => prev + 1);
+ setLoading(true);
+ setError(null);
+ }
+ };
+
+ // Check browser compatibility
+ React.useEffect(() => {
+ if (open) {
+ const runCheck = async () => {
+ try {
+ await checkFeatures();
+ setLoading(false);
+ } catch (err) {
+ setError(err.message);
+ setLoading(false);
+ }
+ };
+ runCheck();
+ }
+ }, [open, retryCount]);
+
+ const handleKeyDown = (event) => {
+ // Close dialog on Escape
+ if (event.key === 'Escape') {
+ onClose();
+ }
+ };
+
+ const handleGetStarted = () => {
+ if (!loading) {
+ onClose();
+ }
+ };
+ return (
+
+ );
+};
+
+export default WelcomeDialog;
diff --git a/live-wallpaper-creator/src/index.css b/live-wallpaper-creator/src/index.css
new file mode 100644
index 0000000..19bc3ad
--- /dev/null
+++ b/live-wallpaper-creator/src/index.css
@@ -0,0 +1,44 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ overflow: hidden;
+}
+
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: rgba(0, 0, 0, 0.1);
+}
+
+::-webkit-scrollbar-thumb {
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: rgba(255, 255, 255, 0.3);
+}
+
+.MuiPaper-root {
+ backdrop-filter: blur(8px);
+ background-color: rgba(30, 30, 30, 0.8) !important;
+}
+
+.MuiAppBar-root {
+ background-color: rgba(18, 18, 18, 0.95) !important;
+ backdrop-filter: blur(8px);
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+}
diff --git a/live-wallpaper-creator/src/main.jsx b/live-wallpaper-creator/src/main.jsx
new file mode 100644
index 0000000..b9a1a6d
--- /dev/null
+++ b/live-wallpaper-creator/src/main.jsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.jsx'
+
+createRoot(document.getElementById('root')).render(
+
+
+ ,
+)
diff --git a/live-wallpaper-creator/src/utils/browser-check.js b/live-wallpaper-creator/src/utils/browser-check.js
new file mode 100644
index 0000000..9b8f619
--- /dev/null
+++ b/live-wallpaper-creator/src/utils/browser-check.js
@@ -0,0 +1,161 @@
+const getBrowserInfo = () => {
+ const ua = navigator.userAgent;
+ const browsers = {
+ chrome: /chrome|chromium|crios/i.test(ua),
+ firefox: /firefox|fxios/i.test(ua),
+ safari: /safari/i.test(ua),
+ edge: /edg/i.test(ua),
+ ie: /msie|trident/i.test(ua),
+ mobile: /mobile|android|iphone|ipad|ipod/i.test(ua),
+ };
+
+ // Chrome detection needs to filter out other Chromium-based browsers
+ if (browsers.chrome) {
+ browsers.chrome = !browsers.edge;
+ }
+
+ // Safari detection needs to filter out Chrome and other browsers on iOS
+ if (browsers.safari) {
+ browsers.safari = !browsers.chrome && !browsers.firefox && !browsers.edge;
+ }
+
+ // Get browser version
+ let version = '';
+ if (browsers.chrome) {
+ version = ua.match(/(?:chrome|chromium|crios)\/(\d+)/i)?.[1] || '';
+ } else if (browsers.firefox) {
+ version = ua.match(/(?:firefox|fxios)\/(\d+)/i)?.[1] || '';
+ } else if (browsers.safari) {
+ version = ua.match(/version\/(\d+)/i)?.[1] || '';
+ } else if (browsers.edge) {
+ version = ua.match(/edg\/(\d+)/i)?.[1] || '';
+ }
+
+ return {
+ ...browsers,
+ version: parseInt(version, 10) || 0,
+ isMobile: browsers.mobile
+ };
+};
+
+const MINIMUM_VERSIONS = {
+ chrome: 90,
+ firefox: 85,
+ edge: 90,
+ safari: 15
+};
+
+const getRecommendedBrowser = (currentBrowser) => {
+ if (currentBrowser.isMobile) {
+ return 'This app is optimized for desktop browsers. Please use a desktop computer for the best experience.';
+ }
+ if (currentBrowser.ie) {
+ return 'Internet Explorer is not supported. Please upgrade to a modern browser like Chrome, Firefox, or Edge.';
+ }
+ if (currentBrowser.safari) {
+ return 'Safari has limited support for some features. Please use Chrome or Firefox for best compatibility.';
+ }
+
+ const browserName = Object.keys(MINIMUM_VERSIONS).find(name => currentBrowser[name]) || '';
+ if (browserName && currentBrowser.version < MINIMUM_VERSIONS[browserName]) {
+ return `Your ${browserName} version (${currentBrowser.version}) is outdated. Please update to version ${MINIMUM_VERSIONS[browserName]} or later.`;
+ }
+
+ return 'Please use the latest version of Chrome, Firefox, or Edge.';
+};
+
+export const checkBrowserCompatibility = () => {
+ const issues = [];
+ const browser = getBrowserInfo();
+
+ // Add mobile warning if applicable
+ if (browser.isMobile) {
+ issues.push({
+ feature: 'Desktop Browser Required',
+ message: 'This app is optimized for desktop browsers.',
+ solution: 'Please use a desktop computer for the best experience.',
+ critical: true
+ });
+ }
+
+ // Check browser version
+ const browserName = Object.keys(MINIMUM_VERSIONS).find(name => browser[name]);
+ if (browserName && browser.version < MINIMUM_VERSIONS[browserName]) {
+ issues.push({
+ feature: 'Browser Version',
+ message: `Your ${browserName} version (${browser.version}) is outdated.`,
+ solution: `Please update to version ${MINIMUM_VERSIONS[browserName]} or later.`,
+ critical: true
+ });
+ }
+
+ // Check for SharedArrayBuffer support
+ if (typeof SharedArrayBuffer === 'undefined') {
+ issues.push({
+ feature: 'SharedArrayBuffer',
+ message: 'SharedArrayBuffer is not supported in your browser.',
+ solution: `This feature is required for video processing. ${getRecommendedBrowser(browser)}`,
+ critical: true
+ });
+ }
+
+ // Check for secure context (HTTPS)
+ if (!window.isSecureContext) {
+ issues.push({
+ feature: 'Secure Context',
+ message: 'This app requires a secure context (HTTPS) to function properly.',
+ solution: 'Please access this app using HTTPS or localhost.',
+ critical: true
+ });
+ }
+
+ // Check for WebAssembly support
+ if (typeof WebAssembly === 'undefined') {
+ issues.push({
+ feature: 'WebAssembly',
+ message: 'WebAssembly is not supported in your browser.',
+ solution: `This feature is required for video processing. ${getRecommendedBrowser(browser)}`,
+ critical: true
+ });
+ }
+
+ // Check for Cross-Origin Isolation
+ if (!crossOriginIsolated) {
+ issues.push({
+ feature: 'Cross-Origin Isolation',
+ message: 'Cross-Origin Isolation is not enabled.',
+ solution: 'This site needs to be served with COOP and COEP headers for optimal performance.',
+ critical: false
+ });
+ }
+
+ // Check for required Web APIs
+ const requiredAPIs = [
+ { name: 'Blob', check: () => typeof Blob !== 'undefined' },
+ { name: 'Canvas', check: () => typeof HTMLCanvasElement !== 'undefined' },
+ { name: 'File API', check: () => typeof File !== 'undefined' && typeof FileReader !== 'undefined' },
+ ];
+
+ requiredAPIs.forEach(({ name, check }) => {
+ if (!check()) {
+ issues.push({
+ feature: name,
+ message: `${name} is not supported in your browser.`,
+ solution: getRecommendedBrowser(browser),
+ critical: true
+ });
+ }
+ });
+
+ return {
+ isCompatible: !issues.some(issue => issue.critical),
+ issues,
+ browser: {
+ name: Object.entries(browser)
+ .filter(([key, value]) => value && key !== 'version' && key !== 'isMobile')
+ .map(([name]) => name)[0],
+ version: browser.version,
+ isMobile: browser.isMobile
+ }
+ };
+};
diff --git a/live-wallpaper-creator/src/utils/errors.js b/live-wallpaper-creator/src/utils/errors.js
new file mode 100644
index 0000000..a887cda
--- /dev/null
+++ b/live-wallpaper-creator/src/utils/errors.js
@@ -0,0 +1,43 @@
+export class FFmpegError extends Error {
+ constructor(message, details = {}) {
+ super(message);
+ this.name = 'FFmpegError';
+ this.details = details;
+ }
+}
+
+export class BrowserCompatibilityError extends Error {
+ constructor(message, features = []) {
+ super(message);
+ this.name = 'BrowserCompatibilityError';
+ this.features = features;
+ }
+}
+
+export class ExportError extends Error {
+ constructor(message, type, details = {}) {
+ super(message);
+ this.name = 'ExportError';
+ this.type = type;
+ this.details = details;
+ }
+}
+
+export const createFFmpegError = (error) => {
+ if (error.message.includes('SharedArrayBuffer')) {
+ return new BrowserCompatibilityError(
+ 'SharedArrayBuffer is not available. Please use a modern browser with HTTPS.',
+ ['SharedArrayBuffer']
+ );
+ }
+ if (error.message.includes('WebAssembly')) {
+ return new BrowserCompatibilityError(
+ 'WebAssembly is not supported in your browser.',
+ ['WebAssembly']
+ );
+ }
+ return new FFmpegError(error.message, {
+ originalError: error,
+ timestamp: new Date().toISOString()
+ });
+};
diff --git a/live-wallpaper-creator/src/utils/export.js b/live-wallpaper-creator/src/utils/export.js
new file mode 100644
index 0000000..07191a2
--- /dev/null
+++ b/live-wallpaper-creator/src/utils/export.js
@@ -0,0 +1,148 @@
+import { FFmpeg } from '@ffmpeg/ffmpeg';
+import { toBlobURL } from '@ffmpeg/util';
+import { FFmpegError, ExportError, createFFmpegError } from './errors';
+
+let ffmpeg = null;
+
+const initFFmpeg = async () => {
+ if (!ffmpeg) {
+ ffmpeg = new FFmpeg();
+ try {
+ const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
+ const coreURL = await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript');
+ const wasmURL = await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm');
+ const workerURL = await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, 'text/javascript');
+
+ console.log('Loading FFmpeg with URLs:', { coreURL, wasmURL, workerURL });
+
+ await ffmpeg.load({
+ coreURL,
+ wasmURL,
+ workerURL,
+ logger: ({ message }) => console.log('FFmpeg:', message)
+ });
+
+ // Test FFmpeg initialization
+ await ffmpeg.writeFile('test.txt', new Uint8Array([1]));
+ await ffmpeg.deleteFile('test.txt');
+ console.log('FFmpeg initialized successfully');
+ } catch (error) {
+ console.error('FFmpeg initialization failed:', error);
+ throw createFFmpegError(error);
+ }
+ }
+ return ffmpeg;
+};
+
+export const exportAsGif = async (canvas, fps = 30, onProgress = () => {}) => {
+ const frames = [];
+ const frameCount = (fps * 3); // 3 seconds
+
+ try {
+ // Capture frames
+ for (let i = 0; i < frameCount; i++) {
+ frames.push(canvas.toDataURL('image/png'));
+ onProgress(i / (frameCount * 2)); // First half of progress for frame capture
+ await new Promise(resolve => setTimeout(resolve, 1000 / fps));
+ }
+
+ const ffmpeg = await initFFmpeg();
+
+ // Convert frames to GIF
+ for (let i = 0; i < frames.length; i++) {
+ const base64Data = frames[i].replace(/^data:image\/\w+;base64,/, '');
+ const buffer = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
+ await ffmpeg.writeFile(`frame_${i}.png`, buffer);
+ onProgress(0.5 + (i / (frames.length * 2))); // Second half of progress for conversion
+ }
+
+ await ffmpeg.exec([
+ '-framerate', `${fps}`,
+ '-pattern_type', 'glob',
+ '-i', 'frame_*.png',
+ '-vf', 'scale=trunc(iw/2)*2:trunc(ih/2)*2',
+ 'output.gif'
+ ]);
+
+ // Read the output file
+ const data = await ffmpeg.readFile('output.gif');
+
+ // Clean up files
+ for (let i = 0; i < frames.length; i++) {
+ await ffmpeg.deleteFile(`frame_${i}.png`);
+ }
+ await ffmpeg.deleteFile('output.gif');
+
+ onProgress(1); // Complete
+ return new Blob([data.buffer], { type: 'image/gif' });
+ } catch (error) {
+ console.error('GIF export failed:', error);
+ throw new ExportError(
+ error.message,
+ 'gif',
+ {
+ originalError: error,
+ frameCount,
+ fps,
+ timestamp: new Date().toISOString()
+ }
+ );
+ }
+};
+
+export const exportAsMP4 = async (canvas, fps = 30, onProgress = () => {}) => {
+ const frames = [];
+ const frameCount = (fps * 3); // 3 seconds
+
+ try {
+ // Capture frames
+ for (let i = 0; i < frameCount; i++) {
+ frames.push(canvas.toDataURL('image/png'));
+ onProgress(i / (frameCount * 2)); // First half of progress for frame capture
+ await new Promise(resolve => setTimeout(resolve, 1000 / fps));
+ }
+
+ const ffmpeg = await initFFmpeg();
+
+ // Convert frames to video
+ for (let i = 0; i < frames.length; i++) {
+ const base64Data = frames[i].replace(/^data:image\/\w+;base64,/, '');
+ const buffer = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
+ await ffmpeg.writeFile(`frame_${i}.png`, buffer);
+ onProgress(0.5 + (i / (frames.length * 2))); // Second half of progress for conversion
+ }
+
+ await ffmpeg.exec([
+ '-framerate', `${fps}`,
+ '-pattern_type', 'glob',
+ '-i', 'frame_*.png',
+ '-c:v', 'libx264',
+ '-pix_fmt', 'yuv420p',
+ 'output.mp4'
+ ]);
+
+ // Read the output file
+ const data = await ffmpeg.readFile('output.mp4');
+
+ // Clean up files
+ for (let i = 0; i < frames.length; i++) {
+ await ffmpeg.deleteFile(`frame_${i}.png`);
+ }
+ await ffmpeg.deleteFile('output.mp4');
+
+ onProgress(1); // Complete
+ return new Blob([data.buffer], { type: 'video/mp4' });
+ } catch (error) {
+ console.error('MP4 export failed:', error);
+ throw new ExportError(
+ error.message,
+ 'mp4',
+ {
+ originalError: error,
+ frameCount,
+ fps,
+ timestamp: new Date().toISOString()
+ }
+ );
+ }
+};
diff --git a/live-wallpaper-creator/src/utils/ffmpeg-init.js b/live-wallpaper-creator/src/utils/ffmpeg-init.js
new file mode 100644
index 0000000..76b7ef7
--- /dev/null
+++ b/live-wallpaper-creator/src/utils/ffmpeg-init.js
@@ -0,0 +1,43 @@
+import { FFmpeg } from '@ffmpeg/ffmpeg';
+import { toBlobURL } from '@ffmpeg/util';
+
+let ffmpeg = null;
+
+export const initFFmpeg = async () => {
+ if (!ffmpeg) {
+ ffmpeg = new FFmpeg();
+ try {
+ const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
+ const coreURL = await toBlobURL(
+ `${baseURL}/ffmpeg-core.js`,
+ 'text/javascript'
+ );
+ const wasmURL = await toBlobURL(
+ `${baseURL}/ffmpeg-core.wasm`,
+ 'application/wasm'
+ );
+ const workerURL = await toBlobURL(
+ `${baseURL}/ffmpeg-core.worker.js`,
+ 'text/javascript'
+ );
+
+ console.log('Loading FFmpeg with URLs:', { coreURL, wasmURL, workerURL });
+
+ await ffmpeg.load({
+ coreURL,
+ wasmURL,
+ workerURL,
+ logger: ({ message }) => console.log('FFmpeg:', message)
+ });
+
+ // Test FFmpeg initialization
+ await ffmpeg.writeFile('test.txt', new Uint8Array([1]));
+ await ffmpeg.deleteFile('test.txt');
+ console.log('FFmpeg initialized successfully');
+ } catch (error) {
+ console.error('FFmpeg initialization failed:', error);
+ throw new Error(`Failed to initialize FFmpeg: ${error.message}`);
+ }
+ }
+ return ffmpeg;
+};
diff --git a/live-wallpaper-creator/vite.config.js b/live-wallpaper-creator/vite.config.js
new file mode 100644
index 0000000..66bf01e
--- /dev/null
+++ b/live-wallpaper-creator/vite.config.js
@@ -0,0 +1,33 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ base: '/hive/pr-161/live-wallpaper-creator/',
+ server: {
+ headers: {
+ 'Cross-Origin-Opener-Policy': 'same-origin',
+ 'Cross-Origin-Embedder-Policy': 'require-corp',
+ },
+ },
+ preview: {
+ headers: {
+ 'Cross-Origin-Opener-Policy': 'same-origin',
+ 'Cross-Origin-Embedder-Policy': 'require-corp',
+ },
+ },
+ optimizeDeps: {
+ exclude: ['@ffmpeg/ffmpeg', '@ffmpeg/util']
+ },
+ build: {
+ target: 'esnext',
+ rollupOptions: {
+ output: {
+ manualChunks: {
+ ffmpeg: ['@ffmpeg/ffmpeg', '@ffmpeg/util']
+ }
+ }
+ }
+ }
+})