Zum Hauptinhalt springen

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

OptionTypDefaultBeschreibung
--profile-id=intEinzelne Social-Profile-ID für One-Off Sync
--max-profiles=intConfig-WertMaximale Profile pro Lauf, überschreibt postbox.youtube_video_sync.max_profiles_per_run (Default: 0 = unbegrenzt)

Interne Schritte

  1. YouTubeVideoSync Records mit auto_sync_enabled = true laden
  2. Optional nach --profile-id filtern
  3. Bei --max-profiles > 0: Profile nach last_sync_at ASC sortieren und limitieren (älteste zuerst)
  4. Pro Profil einen SyncYouTubeVideoStats Job dispatchen
  5. Jobs landen auf der imports-youtube-video Queue
  6. Health-Marker in Cache setzen (health:youtube_video_sync_ran:{date}) mit Anzahl Jobs und Zeitstempel — wird von health:check-daily-scrapes und /up_system gelesen

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

OptionBeschreibung
--dry-runZeigt welches Profil gewählt würde, ohne zu dispatchen
--forceIgnoriert 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 SkipsBackoff-DauerNächster Check frühestens
115 MinutenNormal: 5min → Effektiv: 15min
230 MinutenNormal: 5min → Effektiv: 30min
3+60 MinutenNormal: 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ätKriteriumSortierung
1Profile ohne Related Channels (related_channels_searched_at IS NULL)Followers DESC
2Profile mit stale Related Channels (> 30 Tage alt)related_channels_searched_at ASC

Interne Schritte

  1. Backoff prüfen: Falls im Cooldown, sofort abbrechen (außer --force)
  2. Quota Stufe 1: Research-Quota via ResearchQuotaService::getStatus() prüfen (forceRefresh)
  3. Quota Stufe 2: Live Redis-Check via YouTubeQuotaGuard::canMakeCall('research')
  4. Nächstes Profil nach Priority-Logik finden
  5. Profile mit Status running oder pending ausschließen (Duplikat-Schutz)
  6. related_channels_status = 'pending' setzen
  7. FindRelatedYouTubeChannels Job auf youtube-related-channels Queue dispatchen
  8. 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

  1. YouTubeResearchQuery Records mit created_at < now()->subDays(30) selektieren
  2. Alle gefundenen Records löschen
  3. Research Queries entstehen durch FindRelatedYouTubeChannels Jobs

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

OptionTypDefaultBeschreibung
--date=stringScore für ein spezifisches Datum (Format: Y-m-d)
--from=stringStart-Datum für Range-Berechnung
--to=stringEnd-Datum für Range-Berechnung
--backfillflagAlle Daten mit vorhandenen Metriken abarbeiten
--profile-id=intNur Videos dieses Profils verarbeiten
--forceflagBestehende Scores überschreiben (Default: überspringen)
--dry-runflagNur 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

  1. Videos mit genügend Datenpunkten (MIN_DATA_POINTS aus VideoScoreCalculator) identifizieren
  2. Lookback-Window (METRICS_LOOKBACK_DAYS) für relevante Daily-Metriken
  3. Channel-Tier pro Profil cachen (verhindert wiederholte Follower-Lookups)
  4. Tier-normalisierter Score (0-100): Micro-Channels werden nicht unfair mit Mega-Channels verglichen
  5. 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

OptionTypDefaultBeschreibung
--hours=int1Schwellwert in Stunden ab dem ein Job als "stuck" gilt
--resetflagStatus 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

  1. Profile mit related_channels_status IN (pending, running, pending_imports) suchen
  2. Nur Profile deren Status-Timestamp älter als --hours ist
  3. Tabelle mit Profil-ID, Titel, Status und Stuck-Dauer anzeigen
  4. Bei --reset: Interaktive Bestätigung → Status auf null setzen, 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

OptionBeschreibung
--renewErneuert Subscriptions die innerhalb des konfigurierten Zeitfensters (Default: 48h) ablaufen. Profile mit 5+ aufeinanderfolgenden Renewal-Fehlern werden uebersprungen.
--syncErstellt fehlende Subscriptions fuer auto_sync_enabled-Profile ohne aktive Subscription.
--cleanupEntfernt Subscriptions fuer Profile ohne auto_sync_enabled. Sendet Unsubscribe an Hub und loescht DB-Record.
--allFuehrt --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

OptionTypDefaultBeschreibung
--batch-size=intConfig-Wert (100)Anzahl Channels pro Durchlauf
--profile-id=intEinzelne Social-Profile-ID pollen

Interne Schritte

  1. youtube_video_syncs mit auto_sync_enabled = true laden, JOIN auf social_profiles fuer channel_id
  2. Sortiert nach rss_last_poll_at ASC (aelteste zuerst)
  3. Batch-Polling via Http::pool() mit konfigurierbarer Concurrency (Default: 10)
  4. ETag/If-Modified-Since Header fuer jede Anfrage (304 = keine neuen Videos)
  5. Atom-XML parsen und Video-IDs extrahieren
  6. Batch-Check gegen DB: nur neue Video-IDs dispatchen als FetchNewYouTubeVideo-Jobs
  7. rss_etag, rss_last_modified, rss_last_poll_at pro Channel aktualisieren
  8. 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

OptionTypDefaultBeschreibung
--profile=intEinzelne Social-Profile-ID aggregieren
--dry-runflagNur Vorschau, keine Daten speichern

Berechnete Metriken pro Wochentag

MetrikBeschreibung
video_countAnzahl regulaere Videos (keine Shorts)
shorts_countAnzahl YouTube Shorts (admin-markiert oder auto-detected ≤60s)
vfr_medianMedian Views/Follower-Ratio
vfr_p25 / vfr_p7525./75. Perzentil fuer Konfidenzintervall
engagement_rate_medianMedian (Likes + Comments) / Followers
like_to_view_ratio_medianMedian Likes / Views
best_hour_utcBeste Uploadstunde (UTC) basierend auf VFR
hour_distributionJSON: Verteilung der Uploads pro Stunde (0-23)
prev_video_count / prev_vfr_medianVorherige Werte fuer Trend-Vergleich

Interne Schritte

  1. YouTube-Profile mit tracking_enabled, ohne blocked_at/sanitized_at, mit min. 3 Videos laden
  2. Verarbeitung in Chunks von 50 Profilen (Memory: 128 MB Limit)
  3. Pro Profil: Videos der letzten 365 Tage nach ISO-Wochentag (1=Mo, 7=So) gruppieren
  4. Shorts-Trennung: isEffectiveShort() (Admin-Markierung > Auto-Erkennung ≤60s)
  5. VFR, Engagement, L2V fuer regulaere Videos berechnen (Median)
  6. Stunden-Verteilung und beste Stunde (basierend auf VFR pro Stunde, min. 2 Videos)
  7. Trend-Daten: Vorherige Werte speichern bevor Upsert
  8. 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

OptionTypDefaultBeschreibung
--date=stringgesternAggregation für ein spezifisches Datum
--backfillflagAlle Daten mit vorhandenen Metriken abarbeiten (überspringt existierende)
--enforce-limitsflagLöscht youtube_video_daily_metrics die über dem per-Profile max_videos-Limit liegen
--dry-runflagNur 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:

QueueTimeoutTriesZweck
imports-youtube120s3Channel Updates (regulär)
imports-youtube-priority120s3Channel Updates (Leaders, PRO)
imports-youtube-video120s3Video-Statistiken Sync
imports-youtube-video-priority120s3Video-Statistiken (Priorität)
youtube-related-channels300s2Related 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