/** * Content Metadata Utilities * Extract metadata tags (dubbed, subtitled, etc.) from content titles */ /** * Extract metadata tags from a content title * Returns an array of tags like ["dubbed", "subtitled", "arabic"] */ export function extractContentMetadata(title: string): string[] { const tags: string[] = []; const lowerTitle = title.toLowerCase(); // Arabic metadata terms if (/\bمدبلج\b/.test(title)) { tags.push('dubbed'); } if (/\bمترجم\b/.test(title)) { tags.push('subtitled'); } if (/\bعربي\b/.test(title)) { tags.push('arabic'); } // English metadata terms if (/\bdubbed\b/i.test(lowerTitle)) { if (!tags.includes('dubbed')) tags.push('dubbed'); } if (/\bsubtitled\b/i.test(lowerTitle) || /\bsub\b/i.test(lowerTitle)) { if (!tags.includes('subtitled')) tags.push('subtitled'); } if (/\barabic\b/i.test(lowerTitle)) { if (!tags.includes('arabic')) tags.push('arabic'); } return tags; } /** * Clean title by removing metadata tags for display/search * This is used to normalize titles for TMDb matching */ export function cleanTitleForMatching(title: string): string { // First, try to extract just the English/Latin portion of the title // This handles cases like "Red Sonja (2025) ريد سونيا" -> "Red Sonja (2025)" const cleanedTitle = extractLatinTitle(title); return cleanedTitle // Remove Arabic metadata .replace(/\bمدبلج\b/g, ' ') .replace(/\bمترجم\b/g, ' ') .replace(/\bعربي\b/g, ' ') // Remove English metadata .replace(/\bdubbed\b/gi, ' ') .replace(/\bsubtitled\b/gi, ' ') .replace(/\bsub\b/gi, ' ') .replace(/\barabic\b/gi, ' ') // Clean up spaces .replace(/\s+/g, ' ') .trim(); } /** * Detect the primary script/language of a title * Returns the detected script type for search optimization */ export function detectTitleScript(title: string): 'latin' | 'arabic' | 'cjk' | 'cyrillic' | 'mixed' | 'unknown' { const hasLatin = /[a-zA-Z]/.test(title); const hasArabic = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/.test(title); const hasCJK = /[\u4E00-\u9FFF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF]/.test(title); const hasCyrillic = /[\u0400-\u04FF]/.test(title); // Count scripts present const scripts = [hasLatin, hasArabic, hasCJK, hasCyrillic].filter(Boolean).length; if (scripts === 0) return 'unknown'; if (scripts > 1) return 'mixed'; if (hasLatin) return 'latin'; if (hasArabic) return 'arabic'; if (hasCJK) return 'cjk'; if (hasCyrillic) return 'cyrillic'; return 'unknown'; } /** * Get the TMDb language code for a detected script * Used for searching content in non-Latin languages */ export function getLanguageForScript(script: ReturnType): string | null { switch (script) { case 'arabic': return 'ar'; // Arabic case 'cjk': return 'zh'; // Chinese (could also be ja/ko, but zh is most common) case 'cyrillic': return 'ru'; // Russian (could also be uk/bg/etc) default: return null; // Use default language } } /** * Extract the Latin (English/Western) portion of a multilingual title * Handles cases like: * - "Red Sonja (2025) ريد سونيا" -> "Red Sonja (2025)" * - "Gladiator II مصارع 2" -> "Gladiator II" * - "قصة حب" -> "قصة حب" (pure Arabic, returns as-is for native search) * - "The Matrix" -> "The Matrix" (no change) */ export function extractLatinTitle(title: string): string { // Check if title has both Latin and non-Latin characters const hasLatin = /[a-zA-Z]/.test(title); const hasNonLatin = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF\u4E00-\u9FFF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF\u0400-\u04FF]/.test(title); // If title has both Latin and non-Latin characters, extract Latin portion if (hasLatin && hasNonLatin) { // Split by non-Latin script boundaries and extract Latin portions // Keep Latin letters, numbers, spaces, and common punctuation const latinParts = title .split(/[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF\u4E00-\u9FFF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF\u0400-\u04FF]+/) .map(part => part.trim()) .filter(part => part.length > 0 && /[a-zA-Z]/.test(part)); if (latinParts.length > 0) { // Join the Latin parts and clean up return latinParts.join(' ').replace(/\s+/g, ' ').trim(); } } // Return original title if: // - It's purely Latin // - It's purely non-Latin (TMDb will need to search in original language) // - Extraction failed return title; } /** * Extract year from title * "Big World (2024)" -> 2024 * "Big World 2023" -> 2023 * "2024 Big World" -> 2024 */ export function extractYearFromTitle(title: string): number | null { // Try parentheses format first: "Title (2024)" - most reliable const parenMatch = title.match(/\((\d{4})\)/); if (parenMatch) { const year = parseInt(parenMatch[1], 10); if (year >= 1900 && year <= new Date().getFullYear() + 1) { return year; } } // Try any 4-digit year pattern (19xx or 20xx) const yearMatch = title.match(/\b(19|20)\d{2}\b/); if (yearMatch) { const year = parseInt(yearMatch[0], 10); if (year >= 1900 && year <= new Date().getFullYear() + 1) { return year; } } return null; } /** * Remove year from title for cleaner TMDb search * "Big World (2024)" -> "Big World" * "2024 Big World" -> "Big World" * "Big World 2024" -> "Big World" */ export function stripYearFromTitle(title: string): string { return title // Remove year in parentheses: "Title (2024)" -> "Title" .replace(/\s*\(\d{4}\)\s*/g, ' ') // Remove year at start: "2024 Title" -> "Title" .replace(/^(19|20)\d{2}\s+/g, '') // Remove year at end: "Title 2024" -> "Title" .replace(/\s+(19|20)\d{2}$/g, '') // Remove standalone year in middle (careful not to remove from titles like "2001: A Space Odyssey") .replace(/\s+(19|20)\d{2}\s+/g, ' ') // Clean up extra spaces .replace(/\s+/g, ' ') .trim(); }