Compare commits

...

2 Commits

Author SHA1 Message Date
Lars Behrends
34bb4a27be Add logo and banner to README
Embed project logo and banner in the README and add the corresponding image assets. Adds img/logo.png (displayed above the title) and img/banner.png (displayed below the title) to improve repository branding and visual presentation.
2026-04-20 22:55:48 +02:00
Lars Behrends
e5cdd6b383 Rename Kyoo to Omnyx & add page settings
Rename project branding from "Kyoo" to "Omnyx" across README, index.html, metadata.json, typedoc and various UI components. Add support for page-level settings: pageTitle, favicon (Base64 upload/preview), and customColors (color scheme) — introduced CustomColors type, persisted via API types and converters, and wired into updateSettings/fetchSettings flows. UI: SettingsView adds page settings UI (upload, preview, color pickers) and handlers; App applies pageTitle, favicon and sets CSS variables for customColors; Sidebar and Header now display the configured page title. Also update importer modules and docs to use the new project name in logs/comments.
2026-04-20 22:51:33 +02:00
17 changed files with 285 additions and 43 deletions

View File

@@ -1,6 +1,10 @@
# Kyoo - Media Discovery Platform
![Omnyx Logo](img/logo.png)
A modern web application for browsing, managing, and discovering media across multiple categories. Kyoo provides a unified interface for your media library with support for importing from external sources like Playnite, StashAPP, and XBVR.
# Omnyx - Media Discovery Platform
![Omnyx Banner](img/banner.png)
A modern web application for browsing, managing, and discovering media across multiple categories. Omnyx provides a unified interface for your media library with support for importing from external sources like Playnite, StashAPP, and XBVR.
## Features

BIN
img/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

BIN
img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Google AI Studio App</title>
<title>Omnyx - Media Discovery</title>
</head>
<body>
<div id="root"></div>

View File

@@ -1,5 +1,5 @@
{
"name": "Kyoo - Media Discovery",
"name": "Omnyx - Media Discovery",
"description": "A polished media discovery and tracking application inspired by modern anime platforms.",
"requestFramePermissions": []
}

View File

@@ -77,6 +77,22 @@ function AppContent() {
setEnabledCategories(loadedSettings.enabledCategories);
// Sync theme with theme context
setTheme(loadedSettings.theme);
// Set custom page title
if (loadedSettings.pageTitle) {
document.title = loadedSettings.pageTitle;
}
// Set custom favicon
if (loadedSettings.favicon) {
let faviconLink = document.querySelector("link[rel~='icon']") as HTMLLinkElement;
if (!faviconLink) {
faviconLink = document.createElement('link');
faviconLink.rel = 'icon';
document.head.appendChild(faviconLink);
}
faviconLink.href = loadedSettings.favicon;
}
}
} catch (error) {
console.error('Failed to load settings from API:', error);
@@ -86,6 +102,22 @@ function AppContent() {
loadSettingsFromApi();
}, [setTheme]);
// Apply custom colors when settings change
useEffect(() => {
if (settings?.customColors) {
const root = document.documentElement;
const colors = settings.customColors;
if (colors.primary) root.style.setProperty('--color-primary', colors.primary);
if (colors.secondary) root.style.setProperty('--color-secondary', colors.secondary);
if (colors.background) root.style.setProperty('--color-background', colors.background);
if (colors.surface) root.style.setProperty('--color-surface', colors.surface);
if (colors.text) root.style.setProperty('--color-text', colors.text);
if (colors.muted) root.style.setProperty('--color-muted', colors.muted);
if (colors.border) root.style.setProperty('--color-border', colors.border);
}
}, [settings?.customColors]);
const reloadSettings = async () => {
try {
const loadedSettings = await fetchSettings();
@@ -94,6 +126,22 @@ function AppContent() {
setEnabledCategories(loadedSettings.enabledCategories);
// Sync theme with theme context
setTheme(loadedSettings.theme);
// Set custom page title
if (loadedSettings.pageTitle) {
document.title = loadedSettings.pageTitle;
}
// Set custom favicon
if (loadedSettings.favicon) {
let faviconLink = document.querySelector("link[rel~='icon']") as HTMLLinkElement;
if (!faviconLink) {
faviconLink = document.createElement('link');
faviconLink.rel = 'icon';
document.head.appendChild(faviconLink);
}
faviconLink.href = loadedSettings.favicon;
}
}
} catch (error) {
console.error('Failed to reload settings from API:', error);
@@ -315,6 +363,7 @@ function AppContent() {
<Sidebar
enabledCategories={enabledCategories}
onToggleCategory={toggleCategory}
pageTitle={settings?.pageTitle}
/>
<main className="flex-1 lg:ml-72 flex flex-col">
@@ -385,7 +434,7 @@ function AppContent() {
<div className="max-w-[1920px] mx-auto flex flex-col md:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-2 text-lg font-black text-muted-foreground">
<div className="w-5 h-5 bg-gradient-to-br from-[#6d28d9] to-[#8b5cf6] rounded-full" />
<span className="bg-clip-text text-transparent bg-gradient-to-r from-foreground to-foreground/70">kyoo</span>
<span className="bg-clip-text text-transparent bg-gradient-to-r from-foreground to-foreground/70">{settings?.pageTitle || 'omnyx'}</span>
</div>
<div className="flex items-center gap-6 text-sm font-bold text-muted-foreground">
<a href="#" className="hover:text-[#6d28d9] transition-colors duration-300">Terms</a>
@@ -393,7 +442,7 @@ function AppContent() {
<a href="#" className="hover:text-[#6d28d9] transition-colors duration-300">Contact</a>
</div>
<p className="text-xs font-medium text-muted-foreground">
© 2026 Kyoo Media Discovery. All rights reserved.
© 2026 Omnyx Media Discovery. All rights reserved.
</p>
</div>
</footer>

View File

@@ -95,7 +95,7 @@ export default function Header({
)} />
</div>
<span className="bg-clip-text text-transparent bg-gradient-to-r from-white to-white/80">
kyoo
omnyx
</span>
</Link>
<button

View File

@@ -1,8 +1,8 @@
import React, { useState, useEffect } from 'react';
import { MediaCategory, UserSettings } from '@/types';
import { MediaCategory, UserSettings, CustomColors } from '@/types';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import { Film, Music, Book, Tv, Gamepad2, ShieldAlert, LayoutGrid, List, Globe, Monitor, Sun, Moon, Save, ArrowLeft } from 'lucide-react';
import { Film, Music, Book, Tv, Gamepad2, ShieldAlert, LayoutGrid, List, Globe, Monitor, Sun, Moon, Save, ArrowLeft, Type, Image, Palette } from 'lucide-react';
import { Link } from 'react-router-dom';
import { fetchSettings, updateSettings } from '@/api';
import { useTheme } from '@/contexts/ThemeContext';
@@ -47,6 +47,12 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
const [isSaving, setIsSaving] = useState(false);
const [saveStatus, setSaveStatus] = useState<'idle' | 'success' | 'error'>('idle');
// Page Settings State
const [pageTitle, setPageTitle] = useState<string>('');
const [favicon, setFavicon] = useState<string>('');
const [customColors, setCustomColors] = useState<CustomColors>({});
const [faviconPreview, setFaviconPreview] = useState<string>('');
useEffect(() => {
loadSettings();
}, []);
@@ -56,6 +62,10 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
const loadedSettings = await fetchSettings();
if (loadedSettings) {
setSettings(loadedSettings);
setPageTitle(loadedSettings.pageTitle || '');
setFavicon(loadedSettings.favicon || '');
setCustomColors(loadedSettings.customColors || {});
setFaviconPreview(loadedSettings.favicon || '');
}
} catch (error) {
console.error('Failed to load settings:', error);
@@ -68,7 +78,13 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
setIsSaving(true);
setSaveStatus('idle');
try {
const savedSettings = await updateSettings(settings);
const updatedSettings: UserSettings = {
...settings,
pageTitle: pageTitle || undefined,
favicon: favicon || undefined,
customColors: Object.keys(customColors).length > 0 ? customColors : undefined,
};
const savedSettings = await updateSettings(updatedSettings);
if (savedSettings) {
setSettings(savedSettings);
setSaveStatus('success');
@@ -96,6 +112,31 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
}));
};
const handleFaviconUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
const base64 = reader.result as string;
setFavicon(base64);
setFaviconPreview(base64);
};
reader.readAsDataURL(file);
}
};
const handleRemoveFavicon = () => {
setFavicon('');
setFaviconPreview('');
};
const handleColorChange = (colorKey: keyof CustomColors, value: string) => {
setCustomColors(prev => ({
...prev,
[colorKey]: value || undefined,
}));
};
if (isLoading) {
return (
<div className="min-h-screen bg-background flex items-center justify-center">
@@ -342,6 +383,115 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
</div>
</div>
</section>
{/* Page Settings */}
<section>
<h2 className="text-2xl font-black text-foreground mb-6">Page Settings</h2>
<div className="bg-muted/50 backdrop-blur-sm rounded-2xl p-6 border border-border/50 space-y-6">
{/* Page Title */}
<div>
<div className="flex items-center gap-2 mb-2">
<Type size={18} className="text-[#6d28d9]" />
<Label className="text-sm font-black text-foreground">Custom Page Title</Label>
</div>
<input
type="text"
value={pageTitle}
onChange={(e) => setPageTitle(e.target.value)}
placeholder="Leave empty for default title"
className="w-full px-4 py-3 rounded-xl bg-background border border-border/50 text-foreground placeholder:text-muted-foreground/50 focus:border-[#6d28d9] focus:outline-none transition-all"
/>
<p className="text-xs font-medium text-muted-foreground mt-2">
Custom title for your page. Leave empty to use the default title.
</p>
</div>
{/* Favicon Upload */}
<div>
<div className="flex items-center gap-2 mb-2">
<Image size={18} className="text-[#6d28d9]" />
<Label className="text-sm font-black text-foreground">Favicon / Icon</Label>
</div>
<div className="flex items-center gap-4">
{faviconPreview && (
<div className="relative">
<img
src={faviconPreview}
alt="Favicon preview"
className="w-16 h-16 rounded-xl object-cover border border-border/50"
/>
<button
onClick={handleRemoveFavicon}
className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center text-xs hover:bg-red-600 transition-colors"
>
×
</button>
</div>
)}
<div className="flex-1">
<input
type="file"
accept="image/*"
onChange={handleFaviconUpload}
className="hidden"
id="favicon-upload"
/>
<label
htmlFor="favicon-upload"
className="inline-flex items-center gap-2 px-4 py-3 rounded-xl bg-background border border-border/50 text-foreground hover:bg-muted hover:border-[#6d28d9]/30 cursor-pointer transition-all"
>
<Image size={16} />
{favicon ? 'Change favicon' : 'Upload favicon'}
</label>
</div>
</div>
<p className="text-xs font-medium text-muted-foreground mt-2">
Upload a custom favicon or icon. The image will be converted to Base64 format.
</p>
</div>
{/* Custom Colors */}
<div>
<div className="flex items-center gap-2 mb-4">
<Palette size={18} className="text-[#6d28d9]" />
<Label className="text-sm font-black text-foreground">Custom Colors</Label>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{[
{ key: 'primary', label: 'Primary Color' },
{ key: 'secondary', label: 'Secondary Color' },
{ key: 'background', label: 'Background Color' },
{ key: 'surface', label: 'Surface Color' },
{ key: 'text', label: 'Text Color' },
{ key: 'muted', label: 'Muted Text Color' },
{ key: 'border', label: 'Border Color' },
].map(({ key, label }) => (
<div key={key} className="flex items-center gap-3 p-3 rounded-xl bg-background border border-border/50">
<input
type="color"
value={customColors[key as keyof CustomColors] || '#6d28d9'}
onChange={(e) => handleColorChange(key as keyof CustomColors, e.target.value)}
className="w-10 h-10 rounded-lg cursor-pointer border-0"
/>
<div className="flex-1">
<Label className="text-xs font-black text-foreground">{label}</Label>
<input
type="text"
value={customColors[key as keyof CustomColors] || ''}
onChange={(e) => handleColorChange(key as keyof CustomColors, e.target.value)}
placeholder="#6d28d9"
className="w-full mt-1 px-2 py-1 rounded-lg bg-muted border border-border/30 text-xs text-foreground placeholder:text-muted-foreground/50 focus:border-[#6d28d9] focus:outline-none transition-all"
/>
</div>
</div>
))}
</div>
<p className="text-xs font-medium text-muted-foreground mt-2">
Leave color fields empty to use the default theme colors.
</p>
</div>
</div>
</section>
</div>
</div>
</div>

View File

@@ -31,9 +31,10 @@ import { CATEGORY_PATHS } from '@/constants';
interface SidebarProps {
enabledCategories: MediaCategory[];
onToggleCategory: (category: MediaCategory) => void;
pageTitle?: string;
}
export default function Sidebar({ enabledCategories, onToggleCategory }: SidebarProps) {
export default function Sidebar({ enabledCategories, onToggleCategory, pageTitle }: SidebarProps) {
const [isMediaExpanded, setIsMediaExpanded] = useState(true);
const [isMobileOpen, setIsMobileOpen] = useState(false);
const { theme, setTheme } = useTheme();
@@ -120,7 +121,7 @@ export default function Sidebar({ enabledCategories, onToggleCategory }: Sidebar
<div className="w-10 h-10 bg-gradient-to-br from-[#6d28d9] to-[#8b5cf6] rounded-xl flex items-center justify-center shadow-lg shadow-[#6d28d9]/30">
<div className="w-5 h-5 rounded-full bg-white" />
</div>
<span className="text-xl font-black text-foreground">kyoo</span>
<span className="text-xl font-black text-foreground">{pageTitle || 'omnyx'}</span>
</div>
</div>

View File

@@ -170,6 +170,12 @@ export function convertApiToSettings(apiItem: ApiSettingsItem): UserSettings {
language: apiItem.language || 'en',
theme: (apiItem.theme as 'light' | 'dark' | 'system') || 'system',
jellyfinLibraryMappings: apiItem.jellyfin_library_mappings,
// Page Settings
pageTitle: apiItem.page_title,
favicon: apiItem.favicon,
customColors: apiItem.custom_colors ? JSON.parse(apiItem.custom_colors) : undefined,
createdAt: apiItem.created_at,
updatedAt: apiItem.updated_at,
};
@@ -186,5 +192,10 @@ export function convertSettingsToApi(settings: UserSettings): CreateSettingsInpu
language: settings.language,
theme: settings.theme,
jellyfin_library_mappings: settings.jellyfinLibraryMappings,
// Page Settings
page_title: settings.pageTitle,
favicon: settings.favicon,
custom_colors: settings.customColors ? JSON.stringify(settings.customColors) : undefined,
};
}

View File

@@ -193,6 +193,12 @@ export interface ApiSettingsItem {
language: string;
theme: string;
jellyfin_library_mappings?: string;
// Page Settings
page_title?: string;
favicon?: string;
custom_colors?: string; // JSON string of CustomColors
created_at?: string;
updated_at?: string;
}
@@ -207,6 +213,11 @@ export interface CreateSettingsInput {
language?: string;
theme?: string;
jellyfin_library_mappings?: string;
// Page Settings
page_title?: string;
favicon?: string;
custom_colors?: string;
}
export interface UpdateSettingsInput extends Partial<CreateSettingsInput> {}

View File

@@ -1,7 +1,7 @@
/**
* Jellyfin Importer Module
*
* This module provides functionality to import media from a Jellyfin media server into the Kyoo media database.
* This module provides functionality to import media from a Jellyfin media server into the Omnyx media database.
* It supports importing movies, TV series (including episodes), music albums, and cast members.
* The module handles library mapping to categorize content appropriately and supports both new imports
* and updates to existing entries.
@@ -25,7 +25,7 @@ export interface JellyfinConfig {
}
/**
* Mapping configuration for Jellyfin libraries to Kyoo categories
* Mapping configuration for Jellyfin libraries to Omnyx categories
*/
export interface LibraryMapping {
/** Name of the Jellyfin library */
@@ -838,10 +838,10 @@ function convertJellyfinPersonToCast(person: JellyfinPerson, config: JellyfinCon
}
/**
* Imports media from a Jellyfin instance into the Kyoo media database
* Imports media from a Jellyfin instance into the Omnyx media database
*
* This function performs the following steps:
* 1. Fetches existing media and cast from Kyoo to check for duplicates
* 1. Fetches existing media and cast from Omnyx to check for duplicates
* 2. Fetches Jellyfin libraries for category mapping (if library mappings are provided)
* 3. Imports movies (if enabled)
* 4. Imports TV series with episodes (if enabled)
@@ -895,7 +895,7 @@ export async function importFromJellyfin(
logCallback('Starting Jellyfin import...');
// Step 0: Fetch existing media and cast to check for duplicates
logCallback('Fetching existing media from Kyoo API...');
logCallback('Fetching existing media from Omnyx API...');
const existingMediaResponse = await fetch(`${BASE_URL}/api/media?limit=10000`);
const existingMediaData = await existingMediaResponse.json();
const existingMedia = new Map(
@@ -903,7 +903,7 @@ export async function importFromJellyfin(
);
logCallback(`Found ${existingMedia.size} existing media items in database`);
logCallback('Fetching existing cast from Kyoo API...');
logCallback('Fetching existing cast from Omnyx API...');
const existingCastResponse = await fetch(`${BASE_URL}/api/cast`);
const existingCastData = await existingCastResponse.json();
const existingCast = new Map(
@@ -1297,15 +1297,15 @@ export async function cleanupJellyfinMedia(
try {
logCallback('Starting Jellyfin cleanup...');
// Fetch all existing media from Kyoo API
logCallback('Fetching existing media from Kyoo API...');
// Fetch all existing media from Omnyx API
logCallback('Fetching existing media from Omnyx API...');
const existingMediaResponse = await fetch(`${BASE_URL}/api/media?limit=10000`);
const existingMediaData = await existingMediaResponse.json();
const jellyfinMedia = (existingMediaData.data?.items || []).filter((m: Media) => m.source === 'jellyfin');
logCallback(`Found ${jellyfinMedia.length} Jellyfin media items in database`);
// Fetch all existing cast from Kyoo API
logCallback('Fetching existing cast from Kyoo API...');
// Fetch all existing cast from Omnyx API
logCallback('Fetching existing cast from Omnyx API...');
const existingCastResponse = await fetch(`${BASE_URL}/api/cast`);
const existingCastData = await existingCastResponse.json();
const jellyfinCast = (existingCastData.data?.items || []).filter((c: Staff) => c.photo && c.photo.includes(normalizeUrl(config.url)));

View File

@@ -1,8 +1,8 @@
/**
* Playnite Importer Module
*
* This module provides functionality to import games from a Playnite library into the Kyoo media database.
* It fetches game data from the Playnite API, converts it to the Kyoo media format, and handles both
* This module provides functionality to import games from a Playnite library into the Omnyx media database.
* It fetches game data from the Playnite API, converts it to the Omnyx media format, and handles both
* new imports and updates to existing entries.
*
* @module playniteImporter
@@ -216,14 +216,14 @@ async function fetchGameIcon(baseUrl: string, headers: Record<string, string>, g
}
*/
/**
* Imports games from a Playnite library into the Kyoo media database
* Imports games from a Playnite library into the Omnyx media database
*
* This function performs the following steps:
* 1. Fetches existing media from Kyoo to check for duplicates
* 1. Fetches existing media from Omnyx to check for duplicates
* 2. Fetches all games from the Playnite API
* 3. Fetches detailed information for each game
* 4. Converts Playnite game data to Kyoo media format
* 5. Imports or updates each game in the Kyoo database
* 4. Converts Playnite game data to Omnyx media format
* 5. Imports or updates each game in the Omnyx database
*
* @param config - Configuration for connecting to Playnite
* @param logCallback - Callback function for logging progress messages
@@ -264,7 +264,7 @@ export async function importFromPlaynite(
logCallback('Starting Playnite import...');
// Step 0: Fetch existing media to check for duplicates and enable updates
logCallback('Fetching existing media from Kyoo API...');
logCallback('Fetching existing media from Omnyx API...');
const existingMediaResponse = await fetch(`${BASE_URL}/api/media?limit=1000`);
const existingMediaData = await existingMediaResponse.json();
const existingMedia = new Map(

View File

@@ -2,7 +2,7 @@
* StashAPP Importer Module
*
* This module provides functionality to import adult video content and performers from a StashAPP instance
* into the Kyoo media database. It fetches scene and performer data via GraphQL, converts it to the Kyoo
* into the Omnyx media database. It fetches scene and performer data via GraphQL, converts it to the Omnyx
* media format, and handles both new imports and updates to existing entries.
*
* @module stashappImporter
@@ -226,7 +226,7 @@ function isPathBlacklisted(filePath: string, blacklist: string[]): boolean {
* Updates or creates actor entries from StashAPP performers
*
* This function fetches all performers from StashAPP and updates or creates
* corresponding actor entries in the Kyoo database.
* corresponding actor entries in the Omnyx database.
*
* @param config - Configuration for connecting to StashAPP
* @param logCallback - Callback function for logging progress messages
@@ -251,8 +251,8 @@ export async function updateActorsFromStashAPP(
try {
logCallback('Starting StashAPP actor update...');
// Fetch existing cast from Kyoo API
logCallback('Fetching existing cast from Kyoo API...');
// Fetch existing cast from Omnyx API
logCallback('Fetching existing cast from Omnyx API...');
const existingCastResponse = await fetch(`${BASE_URL}/api/cast`);
const existingCastData = await existingCastResponse.json();
const existingActors = new Map<string, Staff>(
@@ -456,10 +456,10 @@ export async function updateActorsFromStashAPP(
}
/**
* Imports scenes and performers from a StashAPP instance into the Kyoo media database
* Imports scenes and performers from a StashAPP instance into the Omnyx media database
*
* This function performs the following steps:
* 1. Fetches existing media and cast from Kyoo to check for duplicates
* 1. Fetches existing media and cast from Omnyx to check for duplicates
* 2. Fetches all scenes from StashAPP via GraphQL
* 3. Extracts unique performers from all scenes
* 4. Imports or updates performers first
@@ -499,7 +499,7 @@ export async function importFromStashAPP(
logCallback('Starting StashAPP import...');
// Step 0: Fetch existing media and cast to check for duplicates
logCallback('Fetching existing media from Kyoo API...');
logCallback('Fetching existing media from Omnyx API...');
const existingMediaResponse = await fetch(`${BASE_URL}/api/media`);
const existingMediaData = await existingMediaResponse.json();
const existingTitles = new Set(
@@ -507,7 +507,7 @@ export async function importFromStashAPP(
);
logCallback(`Found ${existingTitles.size} existing videos in database`);
logCallback('Fetching existing cast from Kyoo API...');
logCallback('Fetching existing cast from Omnyx API...');
const existingCastResponse = await fetch(`${BASE_URL}/api/cast`, {});
const existingCastData = await existingCastResponse.json();
const existingActors = new Map<string, Staff>(

View File

@@ -1,7 +1,7 @@
/**
* XBVR Importer Module
*
* This module provides functionality to import VR adult video content from an XBVR instance into the Kyoo media database.
* This module provides functionality to import VR adult video content from an XBVR instance into the Omnyx media database.
* It fetches scene data from the DeoVR API endpoint, extracts actors and video details, and handles both new imports
* and updates to existing entries. The module specifically filters for content in the 'Recent' scene group.
*
@@ -124,10 +124,10 @@ export type LogCallback = (message: string) => void;
export type ProgressCallback = (progress: Partial<ImportProgress>) => void;
/**
* Imports VR adult videos and actors from an XBVR instance into the Kyoo media database
* Imports VR adult videos and actors from an XBVR instance into the Omnyx media database
*
* This function performs the following steps:
* 1. Fetches existing media and cast from Kyoo to check for duplicates
* 1. Fetches existing media and cast from Omnyx to check for duplicates
* 2. Fetches the scene list from the DeoVR API endpoint
* 3. Extracts videos from the 'Recent' scene group
* 4. Fetches detailed information for each video
@@ -170,7 +170,7 @@ export async function importFromXBVR(
logCallback('Starting DeoVR import...');
// Step 0: Fetch existing media and cast to check for duplicates
logCallback('Fetching existing media from Kyoo API...');
logCallback('Fetching existing media from Omnyx API...');
const existingMediaResponse = await fetch(`${BASE_URL}/api/media?limit=1000`);
const existingMediaData = await existingMediaResponse.json();
const existingTitles = new Set(
@@ -178,7 +178,7 @@ export async function importFromXBVR(
);
logCallback(`Found ${existingTitles.size} existing videos in database`);
logCallback('Fetching existing cast from Kyoo API...');
logCallback('Fetching existing cast from Omnyx API...');
const existingCastResponse = await fetch(`${BASE_URL}/api/cast?limit=1000`);
const existingCastData = await existingCastResponse.json();
const existingActors = new Map(

View File

@@ -119,10 +119,26 @@ export interface UserSettings {
language: string;
theme: 'light' | 'dark' | 'system';
jellyfinLibraryMappings?: string; // JSON string of LibraryMapping[]
// Page Settings
pageTitle?: string; // Custom page title
favicon?: string; // Base64 encoded favicon/image
customColors?: CustomColors; // Custom color scheme
createdAt?: string;
updatedAt?: string;
}
export interface CustomColors {
primary?: string; // Primary accent color (hex)
secondary?: string; // Secondary accent color (hex)
background?: string; // Background color (hex)
surface?: string; // Surface/card color (hex)
text?: string; // Text color (hex)
muted?: string; // Muted text color (hex)
border?: string; // Border color (hex)
}
// Source to Category mapping - ensures sources are only used with appropriate categories
export const SOURCE_CATEGORY_MAPPING: Record<string, MediaCategory[]> = {
'xbvr': ['Adult'],

View File

@@ -7,7 +7,7 @@
"./src/lib/xbvrImporter.ts"
],
"out": "docs",
"name": "Kyoo Importer Documentation",
"name": "Omnyx Importer Documentation",
"theme": "default",
"excludePrivate": true,
"excludeProtected": false,