@ysolve/ocity-heritage-visualizer
v1.1.2
Published
Componente React para visualizar patrimonio cultural con texto a voz (TTS) y avatares animados
Maintainers
Readme
O-CITY Heritage Visualizer
Componente React para visualizar patrimonio cultural con texto a voz y avatares animados
📖 Descripción
ocity-heritage-visualizer es un componente React que transforma la información de patrimonio cultural de la plataforma O-CITY en una experiencia audiovisual interactiva.
En lugar de mostrar texto plano e imágenes, genera una "simulación de video" que incluye:
- 🎤 Narración por voz usando ElevenLabs TTS
- 🌍 Traducción automática con Google Translate
- 📝 Subtítulos sincronizados
- 🎭 Avatar animado (PNGTuber) que acompaña la narración
- ⏯️ Controles de reproducción completos (play, pause, stop)
- 🔄 Navegación entre patrimonios con autoavance opcional
✨ Características
- ✅ Soporte multiidioma (Español, Inglés, Francés)
- ✅ Reproducción de audio con Text-to-Speech
- ✅ Animación de avatares sincronizada con el audio
- ✅ Sistema de caché de audio para mejorar el rendimiento
- ✅ Completamente tipado con TypeScript
- ✅ Responsive y accesible
📦 Instalación
npm install @ysolve/ocity-heritage-visualizerO con yarn:
yarn add @ysolve/ocity-heritage-visualizerO con pnpm:
pnpm add @ysolve/ocity-heritage-visualizer🚀 Uso Básico
import { StoryVisualizer } from '@ysolve/ocity-heritage-visualizer';
import '@ysolve/ocity-heritage-visualizer/dist/ocity-heritage-visualizer.css';
// O también puedes usar el alias 'styles':
// import '@ysolve/ocity-heritage-visualizer/styles';
// Importa tus avatares
import avatarImage1 from './assets/avatar1.webp';
import avatarImage2 from './assets/avatar2.webp';
function App() {
const heritageItems = [
{
id: '1',
name: 'Castillo de Buñol',
description: {
local: {
short: 'Castillo medieval del siglo XI',
extended:
'El castillo de Buñol es una fortaleza cristiana construida sobre un asentamiento islámico...',
},
},
imageUrl: 'https://example.com/castle.jpg',
},
// Más patrimonios...
];
return (
<StoryVisualizer
heritageItems={heritageItems}
targetLanguage="es"
descriptionLength="extended"
autoAdvance={false}
voiceIds={['21m00Tcm4TlvDq8ikWAM']} // IDs de voces de ElevenLabs
avatars={[avatarImage1, avatarImage2]}
/>
);
}
export default App;🔧 Props
StoryVisualizerProps
| Prop | Tipo | Requerido | Descripción |
| ------------------- | ----------------------- | --------- | ---------------------------------- |
| heritageItems | HeritageItem[] | ✅ | Array de objetos de patrimonio |
| targetLanguage | "es" \| "en" \| "fr" | ✅ | Idioma de narración |
| descriptionLength | "short" \| "extended" | ✅ | Longitud de la descripción |
| autoAdvance | boolean | ❌ | Autoavance al siguiente patrimonio |
| voiceIds | string[] | ✅ | IDs de voces de ElevenLabs |
| avatars | string[] | ✅ | URLs de imágenes de avatares |
| ttsPreset | string | ❌ | ID del preset TTS a usar |
Tipo HeritageItem
interface HeritageItem {
id: string;
name: string;
description: {
local: {
short: string;
extended: string;
};
};
imageUrl: string;
}🎨 Personalización de Estilos
Los estilos se incluyen en el archivo CSS de la librería. Puedes sobrescribirlos en tu aplicación:
/* En tu archivo CSS */
.viewer {
max-width: 1200px;
/* Tus estilos personalizados */
}🔑 Configuración de Variables de Entorno
El componente requiere ciertas variables de entorno para funcionar correctamente:
# Qwen3-TTS-Flash API via ocity_tts Server (Proveedor principal)
# URL del servidor ocity_tts que actúa como proxy a Alibaba DashScope
# Default: http://localhost:3000 (si no se especifica)
VITE_QWEN_TTS_API_URL=http://localhost:3000
# ElevenLabs API (Proveedor fallback)
VITE_ELEVENLABS_API_BASE=https://api.elevenlabs.io/v1
VITE_ELEVENLABS_API_KEY=tu_api_key_aqui
# Google Translate API
VITE_GOOGLE_TRANSLATE_BASE=https://translate.googleapis.com
# URLs de imágenes
VITE_IMAGE_BASE_URL=https://tu-cdn.com/images/
VITE_DEFAULT_IMAGE_URL=https://tu-cdn.com/default.jpg
# Modo debug (opcional)
VITE_DEBUG_MODE=falseNotas sobre variables de entorno
VITE_QWEN_TTS_API_URL: URL del servidor ocity_tts (proveedor principal). Default:http://localhost:3000. Sin esta URL disponible, se usará automáticamente ElevenLabs como proveedor único.VITE_ELEVENLABS_API_KEY: Requerida como proveedor fallback o principal si Qwen no está disponible.- Las demás variables son obligatorias para el correcto funcionamiento del componente.
Sistema de Fallback de TTS
Este componente implementa automaticamente un sistema de fallback entre dos proveedores de TTS:
Proveedor Principal: Qwen3-TTS-Flash (a través de ocity_tts)
- Si el servidor está disponible (
VITE_QWEN_TTS_API_URL), se intenta sintetizar primero con este proveedor - Voces soportadas: Peter, Bodega, Ebona
- El servidor ocity_tts maneja la autenticación con Alibaba (sin CORS)
- Si el servidor está disponible (
Proveedor Fallback: ElevenLabs
- Si la síntesis con Qwen falla, automáticamente se intenta con ElevenLabs
- Si ambos fallan, el usuario ve un error informativo
Mapeo de voces:
- Voz Mujer (ElevenLabs) ↔ Ebona (Qwen3)
- Voz Hombre Joven (ElevenLabs) ↔ Bodega (Qwen3)
Levantar ocity_tts
Para usar Qwen3-TTS-Flash, necesitas tener el servidor ocity_tts corriendo:
cd ../ocity_tts
npm install
ALIBABA_API_KEY=sk_your_key_here npm run dev
# Corre en http://localhost:3000Luego, en ocity_history_visualizer, asegúrate de que VITE_QWEN_TTS_API_URL=http://localhost:3000 esté configurado.
Sistema de Presets TTS
El componente soporta presets predefinidos de parámetros TTS que aplican estilos emocionales y de narración consistentes. Los presets pueden combinarse con parámetros individuales, donde los parámetros individuales sobrescriben los valores del preset.
Presets Disponibles
El servidor ocity_tts expone un endpoint para obtener todos los presets disponibles:
curl http://localhost:3000/api/v1/tts/presetsPresets incluidos:
| ID | Nombre | Descripción | Emoción | Velocidad | Pitch | Propósito |
| ----------------------- | --------------------- | --------------------- | ------------ | --------- | ----- | --------------------- |
| formal_narrative | Formal Narrative | Narración documental | calm | 1.0 | 1.0 | documentary narration |
| conversational | Conversational | Presentación amigable | cheerful | 1.1 | 1.05 | presentation |
| dramatic_storytelling | Dramatic Storytelling | Narración cautivadora | enthusiastic | 0.9 | 1.1 | storytelling |
| audiobook | Audiobook | Narración profesional | calm | 0.85 | 0.95 | audiobook |
| educational | Educational | Tono educativo claro | calm | 0.95 | 1.0 | educational content |
Usando Presets en el Componente
Pasa el ID del preset a través de la prop ttsPreset:
<StoryVisualizer
heritageItems={heritageItems}
targetLanguage="es"
descriptionLength="extended"
autoAdvance={false}
voiceIds={['21m00Tcm4TlvDq8ikWAM']}
avatars={[avatarImage]}
ttsPreset="dramatic_storytelling" // ← Usar preset específico
/>Selector de Presets en Aplicación
El componente de demostración (en demo/src/App.tsx) incluye un selector de presets que permite cambiar entre presets en tiempo de ejecución:
import { useEffect, useState } from 'react';
function App() {
const [presets, setPresets] = useState([]);
const [selectedPreset, setSelectedPreset] = useState<string | undefined>();
useEffect(() => {
// Obtener presets disponibles del servidor ocity_tts
fetch('http://localhost:3000/api/v1/tts/presets')
.then((res) => res.json())
.then((data) => {
setPresets(data.presets);
if (data.presets.length > 0) {
setSelectedPreset(data.presets[0].id);
}
});
}, []);
return (
<>
<select
value={selectedPreset || ''}
onChange={(e) => setSelectedPreset(e.target.value)}
>
{presets.map((p) => (
<option key={p.id} value={p.id}>
{p.label}
</option>
))}
</select>
<StoryVisualizer
heritageItems={heritageItems}
targetLanguage="es"
descriptionLength="extended"
autoAdvance={false}
voiceIds={['21m00Tcm4TlvDq8ikWAM']}
avatars={[avatarImage]}
ttsPreset={selectedPreset}
/>
</>
);
}Diferencias Audibles entre Presets
Cada preset produce diferencias audibles en la narración:
- Formal Narrative → Narración tranquila y medida (documentales)
- Conversational → Tono más rápido y alegre (presentaciones)
- Dramatic Storytelling → Más lento pero entusiasta (historias cautivadoras)
- Audiobook → Muy lento y profesional (audiolibros)
- Educational → Moderadamente lento y claro (contenido educativo)
Caché Inteligente
El sistema de caché diferencia entre presets diferentes, por lo que la misma narración con diferentes presets se cachea por separado:
Cache key: qwen-${voiceId}-${language}-${preset}-${text}Esto significa que cambiar presets solicita síntesis nuevas pero evita re-procesar si vuelves a usar el mismo preset.
Obtener IDs de Voces de ElevenLabs
- Inicia sesión en ElevenLabs
- Ve a "Voice Library" o "My Voices"
- Copia el "Voice ID" de la voz deseada
Ejemplo de voces disponibles:
const voices = [
{ id: '21m00Tcm4TlvDq8ikWAM', name: 'Rachel - Voz Femenina' },
{ id: 'Nh2zY9kknu6z4pZy6FhD', name: 'Adam - Voz Masculina' },
];📋 Requisitos
- React 18.0.0 o superior
- Node.js 16.0.0 o superior
🛠️ Tecnologías Utilizadas
- React 19 - Framework de UI
- TypeScript - Tipado estático
- Vite - Build tool y bundler
- ElevenLabs API - Síntesis de voz (TTS)
- Google Translate API - Traducción automática
- TensorFlow.js - Procesamiento de texto
- IndexedDB - Caché de audio
📚 Ejemplos Avanzados
Ejemplo con múltiples voces y avatares
import { StoryVisualizer, voices } from '@ysolve/ocity-heritage-visualizer';
import avatar1 from './avatars/narrator1.webp';
import avatar2 from './avatars/narrator2.webp';
function AdvancedExample() {
return (
<StoryVisualizer
heritageItems={heritageData}
targetLanguage="es"
descriptionLength="extended"
autoAdvance={true}
voiceIds={[
voices[0].id, // Alterna entre diferentes voces
voices[1].id,
]}
avatars={[avatar1, avatar2]}
/>
);
}Uso con datos dinámicos
import { StoryVisualizer } from '@ysolve/ocity-heritage-visualizer';
import { useEffect, useState } from 'react';
function DynamicExample() {
const [heritages, setHeritages] = useState([]);
useEffect(() => {
fetch('https://api.o-city.org/heritages')
.then((res) => res.json())
.then((data) => setHeritages(data));
}, []);
if (heritages.length === 0) {
return <div>Cargando...</div>;
}
return (
<StoryVisualizer
heritageItems={heritages}
targetLanguage="es"
descriptionLength="extended"
voiceIds={['21m00Tcm4TlvDq8ikWAM']}
avatars={['./avatar.webp']}
/>
);
}🔄 Desarrollo Local
Si deseas contribuir o desarrollar localmente:
- Clona el repositorio:
git clone https://github.com/tu-usuario/ocity_history_visualizer.git
cd ocity_history_visualizer- Instala las dependencias:
npm install- Inicia el servidor de desarrollo:
npm run dev- Construye la librería:
npm run build:lib📝 Scripts Disponibles
npm run dev- Inicia el servidor de desarrollonpm run build:lib- Construye la librería para producciónnpm run lint- Ejecuta el linternpm run preview- Previsualiza la build de producción
📄 Licencia
Este proyecto está bajo la licencia MIT. Ver el archivo LICENSE para más detalles.
🤝 Contribuciones
Las contribuciones son bienvenidas. Por favor:
- Haz un fork del proyecto
- Crea una rama para tu feature (
git checkout -b feature/AmazingFeature) - Commit tus cambios (
git commit -m 'Add: amazing feature') - Push a la rama (
git push origin feature/AmazingFeature) - Abre un Pull Request
📧 Contacto
Para preguntas o sugerencias, contacta con el equipo de O-CITY.
🗺️ Roadmap
- [x] Componente base con TTS
- [x] Traducción automática
- [x] Sistema de avatares
- [x] Publicación en npm
- [ ] Soporte para más idiomas
- [ ] Personalización avanzada de avatares
- [ ] Temas personalizables
- [ ] Integración con más proveedores de TTS
Hecho con ❤️ por el equipo de O-CITY
