YouTube Commands
Spezifische Commands für YouTube-Datensammlung, Video-Statistiken und Related Channel Discovery.
youtube:sync-video-stats
Nightly Delta-Sync für YouTube Video-Statistiken. Nur Profile mit aktiviertem Auto-Sync
(YouTubeVideoSync.auto_sync_enabled = true) werden berücksichtigt.
youtube:sync-video-stats
{--profile-id= : Nur für eine bestimmte Social-Profile-ID}
{--max-profiles= : Maximale Anzahl Profile pro Durchlauf (überschreibt Config)}
Beispiele
# Alle Auto-Sync Profile synchronisieren
php artisan youtube:sync-video-stats
# Einzelnes Profil synchronisieren
php artisan youtube:sync-video-stats --profile-id=123
# Maximal 50 Profile pro Lauf (Quota-Schutz)
php artisan youtube:sync-video-stats --max-profiles=50
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--profile-id= | int | – | Einzelne Social-Profile-ID für One-Off Sync |
--max-profiles= | int | Config-Wert | Maximale Profile pro Lauf, überschreibt postbox.youtube_video_sync.max_profiles_per_run (Default: 0 = unbegrenzt) |
Interne Schritte
YouTubeVideoSyncRecords mitauto_sync_enabled = trueladen- Optional nach
--profile-idfiltern - Bei
--max-profiles> 0: Profile nachlast_sync_atASC sortieren und limitieren (älteste zuerst) - Pro Profil einen
SyncYouTubeVideoStatsJob dispatchen - Jobs landen auf der
imports-youtube-videoQueue - Health-Marker in Cache setzen (
health:youtube_video_sync_ran:{date}) mit Anzahl Jobs und Zeitstempel — wird vonhealth:check-daily-scrapesund/up_systemgelesen
Quota-Schutz via --max-profiles
Wenn --max-profiles gesetzt oder via Config konfiguriert ist, werden Profile nach last_sync_at (älteste zuerst) sortiert und limitiert.
So rotieren alle Profile über mehrere Tage, ohne das tägliche YouTube API-Quota zu überlasten.
Schedule
Täglich um 14:00 UTC mit .withoutOverlapping(). Cron-Heartbeat: youtube_video_sync.
Location: app/Console/Commands/SyncYouTubeVideoStats.php
youtube:auto-fill-related-channels
Dispatcht FindRelatedYouTubeChannels Jobs wenn Research-Quota verfügbar ist.
Füllt Related Channels für YouTube-Profile automatisch auf.
youtube:auto-fill-related-channels
{--dry-run : Vorschau ohne Job-Dispatch}
{--force : Quota-Check ignorieren}
Beispiele
# Standard-Lauf (prüft Quota, dispatcht 1 Job)
php artisan youtube:auto-fill-related-channels
# Vorschau
php artisan youtube:auto-fill-related-channels --dry-run
# Quota-Check umgehen
php artisan youtube:auto-fill-related-channels --force
Optionen
| Option | Beschreibung |
|---|---|
--dry-run | Zeigt welches Profil gewählt würde, ohne zu dispatchen |
--force | Ignoriert Quota-Prüfung und dispatcht den Job |
Quota-Prüfung (Zwei-Stufen)
Stufe 1 — Dashboard-Quota: Der Command dispatcht nur wenn > 25% der Research-Quota verfügbar ist.
Die Quota wird via ResearchQuotaService geprüft, der google_api_quota_usages ausliest (stündlich aktualisiert).
Stufe 2 — Live Redis-Check: Zusätzlich prüft YouTubeQuotaGuard::canMakeCall('research') in Echtzeit,
ob mindestens ein API-Key nicht exhausted ist. Die Dashboard-Daten können bis zu 1 Stunde veraltet sein,
der Redis-Cache wird bei jedem API-Call aktualisiert.
Exponential Backoff
Wenn ein Lauf wegen Quota übersprungen wird, aktiviert sich ein Backoff-Mechanismus:
| Consecutive Skips | Backoff-Dauer | Nächster Check frühestens |
|---|---|---|
| 1 | 15 Minuten | Normal: 5min → Effektiv: 15min |
| 2 | 30 Minuten | Normal: 5min → Effektiv: 30min |
| 3+ | 60 Minuten | Normal: 5min → Effektiv: 60min |
Nach einem erfolgreichen Dispatch wird der Backoff zurückgesetzt. Der Backoff-Counter verfällt nach 6 Stunden.
--force umgeht sowohl Quota-Prüfung als auch Backoff.
Priority-Logik
| Priorität | Kriterium | Sortierung |
|---|---|---|
| 1 | Profile ohne Related Channels (related_channels_searched_at IS NULL) | Followers DESC |
| 2 | Profile mit stale Related Channels (> 30 Tage alt) | related_channels_searched_at ASC |
Interne Schritte
- Backoff prüfen: Falls im Cooldown, sofort abbrechen (außer
--force) - Quota Stufe 1: Research-Quota via
ResearchQuotaService::getStatus()prüfen (forceRefresh) - Quota Stufe 2: Live Redis-Check via
YouTubeQuotaGuard::canMakeCall('research') - Nächstes Profil nach Priority-Logik finden
- Profile mit Status
runningoderpendingausschließen (Duplikat-Schutz) related_channels_status = 'pending'setzenFindRelatedYouTubeChannelsJob aufyoutube-related-channelsQueue dispatchen- Backoff zurücksetzen nach erfolgreichem Dispatch
Schedule
Alle 5 Minuten mit .withoutOverlapping().
Location: app/Console/Commands/AutoFillRelatedYouTubeChannels.php
youtube-research:prune
Löscht YouTube Research Queries die älter als 30 Tage sind.
Dies ist ein Schedule-Call (kein Artisan Command), definiert in routes/console.php.
youtube-research:prune
Beispiel
# Wird automatisch via Scheduler ausgeführt
# Manuelle Ausführung nicht direkt möglich (Schedule::call)
Interne Schritte
YouTubeResearchQueryRecords mitcreated_at < now()->subDays(30)selektieren- Alle gefundenen Records löschen
- Research Queries entstehen durch
FindRelatedYouTubeChannelsJobs
Retention
- 30 Tage Aufbewahrung für Research Queries
- Danach werden die Queries gelöscht, die gefundenen Related Channels bleiben erhalten
Schedule
Täglich um 01:30 UTC mit .withoutOverlapping().
Location: routes/console.php (Schedule::call)
youtube:calculate-video-scores
Berechnet tägliche Video Performance Scores (VPS) für YouTube-Videos. Tier-normalisierte Bewertung (0-100) basierend auf Subscriber-Zahlen des Channels.
youtube:calculate-video-scores
{--date= : Score für ein bestimmtes Datum berechnen (Y-m-d)}
{--from= : Start-Datum für Range-Berechnung (Y-m-d)}
{--to= : End-Datum für Range-Berechnung (Y-m-d)}
{--backfill : Scores für alle Daten mit vorhandenen Video-Metriken berechnen}
{--profile-id= : Nur Videos eines bestimmten Profils verarbeiten}
{--force : Bestehende Scores überschreiben}
{--dry-run : Vorschau ohne Speicherung}
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--date= | string | – | Score für ein spezifisches Datum (Format: Y-m-d) |
--from= | string | – | Start-Datum für Range-Berechnung |
--to= | string | – | End-Datum für Range-Berechnung |
--backfill | flag | – | Alle Daten mit vorhandenen Metriken abarbeiten |
--profile-id= | int | – | Nur Videos dieses Profils verarbeiten |
--force | flag | – | Bestehende Scores überschreiben (Default: überspringen) |
--dry-run | flag | – | Nur Vorschau, keine Daten speichern |
Beispiele
# Standard: Score für gestern berechnen
php artisan youtube:calculate-video-scores
# Score für bestimmtes Datum
php artisan youtube:calculate-video-scores --date=2026-02-10
# Range-Berechnung (z.B. nach Ausfall)
php artisan youtube:calculate-video-scores --from=2026-02-01 --to=2026-02-10
# Backfill für alle verfügbaren Daten
php artisan youtube:calculate-video-scores --backfill
# Nur Videos eines Profils, bestehende überschreiben
php artisan youtube:calculate-video-scores --profile-id=12345 --force
# Vorschau
php artisan youtube:calculate-video-scores --dry-run
Interne Logik
- Videos mit genügend Datenpunkten (
MIN_DATA_POINTSausVideoScoreCalculator) identifizieren - Lookback-Window (
METRICS_LOOKBACK_DAYS) für relevante Daily-Metriken - Channel-Tier pro Profil cachen (verhindert wiederholte Follower-Lookups)
- Tier-normalisierter Score (0-100): Micro-Channels werden nicht unfair mit Mega-Channels verglichen
- Bestehende Scores werden übersprungen (es sei denn
--force)
Schedule
Täglich um 21:00 UTC.
Location: app/Console/Commands/CalculateVideoScores.php
youtube:find-stuck-related
Findet YouTube-Profile mit hängengebliebenen Related-Channel-Suchen.
Debugging-Tool wenn Jobs im Status running oder pending_imports steckenbleiben.
youtube:find-stuck-related
{--hours=1 : Ab wie vielen Stunden gilt ein Job als "stuck"}
{--reset : Stuck-Profile zurücksetzen für Retry}
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--hours= | int | 1 | Schwellwert in Stunden ab dem ein Job als "stuck" gilt |
--reset | flag | – | Status auf null zurücksetzen (ermöglicht Retry) |
Beispiele
# Stuck-Jobs finden (Default: > 1 Stunde)
php artisan youtube:find-stuck-related
# Großzügigerer Zeitraum (> 6 Stunden)
php artisan youtube:find-stuck-related --hours=6
# Stuck-Jobs finden UND zurücksetzen
php artisan youtube:find-stuck-related --reset
Interne Logik
- Profile mit
related_channels_statusIN (pending,running,pending_imports) suchen - Nur Profile deren Status-Timestamp älter als
--hoursist - Tabelle mit Profil-ID, Titel, Status und Stuck-Dauer anzeigen
- Bei
--reset: Interaktive Bestätigung → Status aufnullsetzen, Fehlermeldung loggen
Schedule
Manuell (Admin-Debugging-Tool).
Location: app/Console/Commands/FindStuckRelatedChannels.php
youtube:manage-websub
Verwaltet WebSub (PubSubHubbub) Subscriptions fuer YouTube-Channel-Push-Notifications. Subscriptions ermoeglichen Echtzeit-Erkennung neuer Videos ohne API-Quota.
youtube:manage-websub
{--renew : Ablaufende Subscriptions erneuern}
{--sync : Fehlende Subscriptions fuer Auto-Sync-Profile erstellen}
{--cleanup : Subscriptions fuer nicht-mehr-aktive Profile entfernen}
{--all : Alle drei Aktionen ausfuehren}
Beispiele
# Alle Aktionen ausfuehren (Standard via Schedule)
php artisan youtube:manage-websub --all
# Nur ablaufende Subscriptions erneuern
php artisan youtube:manage-websub --renew
# Neue Subscriptions fuer Profile ohne Subscription erstellen
php artisan youtube:manage-websub --sync
Optionen
| Option | Beschreibung |
|---|---|
--renew | Erneuert Subscriptions die innerhalb des konfigurierten Zeitfensters (Default: 48h) ablaufen. Profile mit 5+ aufeinanderfolgenden Renewal-Fehlern werden uebersprungen. |
--sync | Erstellt fehlende Subscriptions fuer auto_sync_enabled-Profile ohne aktive Subscription. |
--cleanup | Entfernt Subscriptions fuer Profile ohne auto_sync_enabled. Sendet Unsubscribe an Hub und loescht DB-Record. |
--all | Fuehrt --renew, --sync und --cleanup nacheinander aus. |
Schedule
Taeglich um 06:00 UTC mit .withoutOverlapping(30). Cron-Heartbeat: websub_manage.
Location: app/Console/Commands/ManageWebSubSubscriptions.php
youtube:poll-rss-feeds
RSS-Fallback-Polling fuer YouTube-Channels mit Auto-Sync. Erkennt neue Videos wenn WebSub-Notifications ausbleiben. Nutzt ETag/If-Modified-Since-Caching.
youtube:poll-rss-feeds
{--batch-size= : Anzahl Channels pro Batch (ueberschreibt Config)}
{--profile-id= : Nur ein bestimmtes Profil pollen}
Beispiele
# Standard-Lauf (alle Auto-Sync-Profile, aelteste zuerst)
php artisan youtube:poll-rss-feeds
# Kleiner Batch fuer Testing
php artisan youtube:poll-rss-feeds --batch-size=10
# Einzelnes Profil pollen
php artisan youtube:poll-rss-feeds --profile-id=123
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--batch-size= | int | Config-Wert (100) | Anzahl Channels pro Durchlauf |
--profile-id= | int | – | Einzelne Social-Profile-ID pollen |
Interne Schritte
youtube_video_syncsmitauto_sync_enabled = trueladen, JOIN aufsocial_profilesfuerchannel_id- Sortiert nach
rss_last_poll_at ASC(aelteste zuerst) - Batch-Polling via
Http::pool()mit konfigurierbarer Concurrency (Default: 10) - ETag/If-Modified-Since Header fuer jede Anfrage (304 = keine neuen Videos)
- Atom-XML parsen und Video-IDs extrahieren
- Batch-Check gegen DB: nur neue Video-IDs dispatchen als
FetchNewYouTubeVideo-Jobs rss_etag,rss_last_modified,rss_last_poll_atpro Channel aktualisieren- Memory-Cleanup nach jedem Chunk:
unset()+gc_collect_cycles()— SimpleXML DOM-Trees und HTTP-Response-Objekte halten zirkulaere Referenzen die PHPs Refcount-GC nicht automatisch freigibt
Schedule
Alle 30 Minuten mit .withoutOverlapping(25). Cron-Heartbeat: youtube_rss_poll.
Location: app/Console/Commands/PollYouTubeRssFeeds.php
youtube:aggregate-publishing-stats
Aggregiert YouTube Publishing-Analytics pro Kanal und Wochentag (Plan 13). Berechnet Video-Anzahl, VFR-Median, Engagement-Rate, Like-to-View-Ratio, Stunden-Verteilung, beste Uploadstunde und Konfidenzintervalle.
youtube:aggregate-publishing-stats
{--profile= : Nur fuer eine bestimmte Social-Profile-ID}
{--dry-run : Vorschau ohne Speicherung}
Beispiele
# Alle YouTube-Profile aggregieren
php artisan youtube:aggregate-publishing-stats
# Einzelnes Profil
php artisan youtube:aggregate-publishing-stats --profile=123
# Vorschau
php artisan youtube:aggregate-publishing-stats --dry-run
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--profile= | int | – | Einzelne Social-Profile-ID aggregieren |
--dry-run | flag | – | Nur Vorschau, keine Daten speichern |
Berechnete Metriken pro Wochentag
| Metrik | Beschreibung |
|---|---|
video_count | Anzahl regulaere Videos (keine Shorts) |
shorts_count | Anzahl YouTube Shorts (admin-markiert oder auto-detected ≤60s) |
vfr_median | Median Views/Follower-Ratio |
vfr_p25 / vfr_p75 | 25./75. Perzentil fuer Konfidenzintervall |
engagement_rate_median | Median (Likes + Comments) / Followers |
like_to_view_ratio_median | Median Likes / Views |
best_hour_utc | Beste Uploadstunde (UTC) basierend auf VFR |
hour_distribution | JSON: Verteilung der Uploads pro Stunde (0-23) |
prev_video_count / prev_vfr_median | Vorherige Werte fuer Trend-Vergleich |
Interne Schritte
- YouTube-Profile mit
tracking_enabled, ohneblocked_at/sanitized_at, mit min. 3 Videos laden - Verarbeitung in Chunks von 50 Profilen (Memory: 128 MB Limit)
- Pro Profil: Videos der letzten 365 Tage nach ISO-Wochentag (1=Mo, 7=So) gruppieren
- Shorts-Trennung:
isEffectiveShort()(Admin-Markierung > Auto-Erkennung ≤60s) - VFR, Engagement, L2V fuer regulaere Videos berechnen (Median)
- Stunden-Verteilung und beste Stunde (basierend auf VFR pro Stunde, min. 2 Videos)
- Trend-Daten: Vorherige Werte speichern bevor Upsert
- Upsert via
updateOrCreate(eine Zeile pro Profil pro Wochentag)
Schedule
Woechentlich Montag 03:00 UTC mit .withoutOverlapping(). Cron-Heartbeat: youtube_publishing_stats.
Location: app/Console/Commands/AggregateYouTubePublishingStats.php
youtube:aggregate-video-stats-tracking
Aggregiert tägliche Video-Stats-Tracking-Abdeckung in youtube_video_stats_daily_aggregations.
Berechnet wie viele Videos pro Tag frische Metriken erhalten haben, aufgeschlüsselt nach
Gründen für fehlende Metriken (entfernt, Sync deaktiviert, über Limit, vor Cutoff).
youtube:aggregate-video-stats-tracking
{--date= : Aggregation für ein bestimmtes Datum (Y-m-d)}
{--backfill : Aggregation für alle Daten mit vorhandenen Metriken}
{--enforce-limits : Überschüssige Video-Metriken rückwirkend löschen}
{--dry-run : Vorschau ohne Speicherung/Löschung}
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--date= | string | gestern | Aggregation für ein spezifisches Datum |
--backfill | flag | – | Alle Daten mit vorhandenen Metriken abarbeiten (überspringt existierende) |
--enforce-limits | flag | – | Löscht youtube_video_daily_metrics die über dem per-Profile max_videos-Limit liegen |
--dry-run | flag | – | Nur Vorschau, keine Daten speichern oder löschen |
Beispiele
# Standard: Aggregation für gestern
php artisan youtube:aggregate-video-stats-tracking
# Bestimmtes Datum
php artisan youtube:aggregate-video-stats-tracking --date=2026-03-01
# Backfill für alle verfügbaren Daten
php artisan youtube:aggregate-video-stats-tracking --backfill
# Überschüssige Metriken entfernen (Vorschau)
php artisan youtube:aggregate-video-stats-tracking --enforce-limits --dry-run
# Überschüssige Metriken tatsächlich löschen
php artisan youtube:aggregate-video-stats-tracking --enforce-limits
Coverage-Berechnung
Die Coverage nutzt ROW_NUMBER() OVER (PARTITION BY social_profile_id ORDER BY published_at DESC) mit per-Profile-Limits:
- Standard-Profile: max. 50 Videos (
postbox.youtube_video_sync.max_videos) - PRO-Profile: max. 100 Videos (
postbox.youtube_video_detection.pro_max_videos)
Nur Videos innerhalb dieses Limits zählen zum Denominator. Dies spiegelt exakt die Logik des SyncYouTubeVideoStats-Jobs wider.
PRO/Standard Breakdown
Pro Tag werden aufgeschlüsselt:
- Anzahl PRO- und Standard-Profile mit Videos
- Anzahl eligible Videos je Profil-Typ
- Gesamtzahl Profile mit aktiviertem Auto-Sync
Shorts-Tracking
YouTube Shorts werden separat gezählt (shorts_total, shorts_with_metrics).
Erkennung via isEffectiveShort()-Logik: is_short-Feld hat Vorrang (Admin-Markierung), Fallback auf duration_seconds <= 60.
--enforce-limits (Prune)
Löscht youtube_video_daily_metrics für Videos die über dem per-Profile max_videos-Limit liegen.
Batched Deletes (1000 Video-IDs pro Batch) für Memory-Safety innerhalb des 128MB-Limits.
Schedule
Täglich um 22:00 UTC mit .withoutOverlapping(). Cron-Heartbeat: video_stats_tracking.
Location: app/Console/Commands/AggregateVideoStatsTracking.php
Übersicht: YouTube Queue Workers
Die YouTube-Jobs werden auf verschiedenen Queues verarbeitet:
| Queue | Timeout | Tries | Zweck |
|---|---|---|---|
imports-youtube | 120s | 3 | Channel Updates (regulär) |
imports-youtube-priority | 120s | 3 | Channel Updates (Leaders, PRO) |
imports-youtube-video | 120s | 3 | Video-Statistiken Sync |
imports-youtube-video-priority | 120s | 3 | Video-Statistiken (Priorität) |
youtube-related-channels | 300s | 2 | Related Channel Discovery |
Worker-Konfiguration (Forge)
# Channel Updates
php8.4 artisan queue:work database --sleep=5 --daemon --quiet --timeout=120 --tries=3 --queue=imports-youtube
# Priority Updates
php8.4 artisan queue:work database --sleep=5 --daemon --quiet --timeout=120 --tries=3 --queue=imports-youtube-priority
# Video Stats
php8.4 artisan queue:work database --sleep=5 --daemon --quiet --timeout=120 --tries=3 --queue=imports-youtube-video
# Related Channels (längerer Timeout wegen API-Calls)
php8.4 artisan queue:work database --sleep=5 --daemon --quiet --timeout=300 --tries=2 --queue=youtube-related-channels