'boolean', 'available_dubs' => 'array', ]; public function anime() { return $this->belongsTo(Anime::class); } public function season() { return $this->belongsTo(Season::class); } public function subtitles() { return $this->hasMany(Subtitle::class); } public function comments() { return $this->hasMany(Comment::class); } public function permissions() { return $this->morphMany(ContentPermission::class, 'content', 'content_type', 'content_id'); } public function getPermission(string $key): string { $override = ContentPermission::where('content_type', 'episode') ->where('content_id', $this->id) ->where('permission_key', $key) ->first(); if ($override) return $override->required_membership; // Anime-level permission $animeOverride = ContentPermission::where('content_type', 'anime') ->where('content_id', $this->anime_id) ->where('permission_key', $key) ->first(); if ($animeOverride) return $animeOverride->required_membership; $global = PermissionSetting::where('key', $key)->first(); return $global ? $global->required_membership : 'free'; } public function getDurationFormattedAttribute(): string { if (!$this->duration) return '-'; $minutes = intdiv($this->duration, 60); $seconds = $this->duration % 60; return sprintf('%d:%02d', $minutes, $seconds); } public function getThumbnailUrlAttribute(): ?string { return MediaUrl::fromStoragePath($this->thumbnail); } }