cms.c2sgmbh/src/lib/utils/youtube.ts
Martin Porwoll 3294fbb506 feat(YouTube): add YouTube Operations Hub with YtSeries collection
Complete YouTube content management system:
- YouTubeChannels: Channel management with branding and metrics
- YouTubeContent: Video pipeline with workflow, approvals, scheduling
- YtSeries: Dedicated series management per channel (NEW)
- YtBatches: Production batch tracking with targets and progress
- YtTasks: Task management with notifications
- YtNotifications: User notification system
- YtMonthlyGoals: Monthly production goals per channel
- YtScriptTemplates: Reusable script templates
- YtChecklistTemplates: Checklist templates for workflows

Features:
- Role-based access (YouTubeManager, YouTubeCreator, YouTubeViewer)
- Auto-task generation on status changes
- Series relationship with channel-based filtering
- API endpoints for dashboard, tasks, and task completion
- German/English localization support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 14:54:40 +00:00

167 lines
4.7 KiB
TypeScript

// src/lib/utils/youtube.ts
/**
* YouTube URL Parser and Utilities
*
* Hilfsfunktionen zum Parsen von YouTube-URLs und Generieren von Embed-URLs.
* Unterstützt verschiedene YouTube-URL-Formate:
* - youtube.com/watch?v=VIDEO_ID
* - youtu.be/VIDEO_ID
* - youtube.com/embed/VIDEO_ID
* - youtube.com/shorts/VIDEO_ID
*/
/**
* Extrahiert die Video-ID aus einer YouTube-URL.
*
* @param url - Die YouTube-URL
* @returns Die Video-ID oder null wenn nicht gefunden
*
* @example
* extractYouTubeId('https://www.youtube.com/watch?v=dQw4w9WgXcQ') // 'dQw4w9WgXcQ'
* extractYouTubeId('https://youtu.be/dQw4w9WgXcQ') // 'dQw4w9WgXcQ'
* extractYouTubeId('https://www.youtube.com/shorts/VIDEO_ID') // 'VIDEO_ID'
*/
export function extractYouTubeId(url: string): string | null {
if (!url) return null
const patterns = [
/youtube\.com\/watch\?v=([^&]+)/,
/youtu\.be\/([^?]+)/,
/youtube\.com\/embed\/([^?]+)/,
/youtube\.com\/shorts\/([^?]+)/,
/youtube-nocookie\.com\/embed\/([^?]+)/,
]
for (const pattern of patterns) {
const match = url.match(pattern)
if (match) return match[1]
}
return null
}
/**
* Generiert eine YouTube Embed-URL.
*
* @param videoId - Die YouTube Video-ID
* @param privacyMode - Wenn true, wird youtube-nocookie.com verwendet (DSGVO-konform)
* @param options - Zusätzliche Embed-Optionen
* @returns Die vollständige Embed-URL
*
* @example
* getYouTubeEmbedUrl('dQw4w9WgXcQ') // 'https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ'
* getYouTubeEmbedUrl('dQw4w9WgXcQ', false) // 'https://www.youtube.com/embed/dQw4w9WgXcQ'
*/
export function getYouTubeEmbedUrl(
videoId: string,
privacyMode = true,
options?: {
autoplay?: boolean
muted?: boolean
loop?: boolean
controls?: boolean
startTime?: number
}
): string {
const domain = privacyMode ? 'www.youtube-nocookie.com' : 'www.youtube.com'
const baseUrl = `https://${domain}/embed/${videoId}`
// Build query parameters
const params = new URLSearchParams()
if (options?.autoplay) params.set('autoplay', '1')
if (options?.muted) params.set('mute', '1')
if (options?.loop) {
params.set('loop', '1')
params.set('playlist', videoId) // Required for loop to work
}
if (options?.controls === false) params.set('controls', '0')
if (options?.startTime) params.set('start', String(options.startTime))
const queryString = params.toString()
return queryString ? `${baseUrl}?${queryString}` : baseUrl
}
/**
* Generiert eine YouTube Thumbnail-URL.
*
* @param videoId - Die YouTube Video-ID
* @param quality - Die gewünschte Qualität
* @returns Die Thumbnail-URL
*
* @example
* getYouTubeThumbnail('dQw4w9WgXcQ') // 'https://img.youtube.com/vi/dQw4w9WgXcQ/hqdefault.jpg'
* getYouTubeThumbnail('dQw4w9WgXcQ', 'maxres') // 'https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg'
*/
export function getYouTubeThumbnail(
videoId: string,
quality: 'default' | 'mq' | 'hq' | 'sd' | 'maxres' = 'hq'
): string {
const qualityMap: Record<string, string> = {
default: 'default',
mq: 'mqdefault',
hq: 'hqdefault',
sd: 'sddefault',
maxres: 'maxresdefault',
}
return `https://img.youtube.com/vi/${videoId}/${qualityMap[quality]}.jpg`
}
/**
* Prüft, ob eine URL eine gültige YouTube-URL ist.
*
* @param url - Die zu prüfende URL
* @returns true wenn die URL eine gültige YouTube-URL ist
*/
export function isValidYouTubeUrl(url: string): boolean {
if (!url) return false
const youtubePatterns = [
/youtube\.com\/watch\?v=/,
/youtu\.be\//,
/youtube\.com\/embed\//,
/youtube\.com\/shorts\//,
/youtube-nocookie\.com\/embed\//,
]
return youtubePatterns.some((pattern) => pattern.test(url))
}
/**
* Extrahiert die Playlist-ID aus einer YouTube-Playlist-URL.
*
* @param url - Die YouTube-Playlist-URL
* @returns Die Playlist-ID oder null wenn nicht gefunden
*
* @example
* extractYouTubePlaylistId('https://www.youtube.com/playlist?list=PLxxxxxx') // 'PLxxxxxx'
*/
export function extractYouTubePlaylistId(url: string): string | null {
if (!url) return null
const patterns = [
/youtube\.com\/playlist\?list=([^&]+)/,
/youtube\.com\/watch\?.*list=([^&]+)/,
]
for (const pattern of patterns) {
const match = url.match(pattern)
if (match) return match[1]
}
return null
}
/**
* Generiert eine YouTube Playlist Embed-URL.
*
* @param playlistId - Die YouTube Playlist-ID
* @param privacyMode - Wenn true, wird youtube-nocookie.com verwendet
* @returns Die vollständige Playlist Embed-URL
*/
export function getYouTubePlaylistEmbedUrl(playlistId: string, privacyMode = true): string {
const domain = privacyMode ? 'www.youtube-nocookie.com' : 'www.youtube.com'
return `https://${domain}/embed/videoseries?list=${playlistId}`
}