# laravel-video-extractor Extract **thumbnails**, **metadata** and **stream formats** from **VK** (`vkvideo.ru` / `vk.com`), **Rutube** and **OK.ru** (Odnoklassniki) videos. The platform is auto-detected from the URL — one facade, all services. ```php use Apoloz\VideoExtractor\Facades\Video; // Full extraction (metadata + thumbnails + formats) $info = Video::extract('https://rutube.ru/video/373ba417757981facf06c55ea5c46cb4/'); echo $info->title; // "Первые слова малыша..." echo $info->bestThumbnail()->url; // https://pic.rtbcdn.ru/video/...jpg echo $info->duration; // 3756 // Lightweight thumbnail-only path (single request) Video::thumbnail('https://vkvideo.ru/video-18403220_456239696'); // OK.ru — auto-detected, full metadata + mp4/HLS formats Video::extract('https://ok.ru/video/15019292494547')->bestThumbnail()->url; ``` ## Requirements - PHP **8.1+** - Laravel **10 / 11 / 12** ## Installation The package is hosted in a private Gitea repository (`gitea.office.local:3000/apoloz/laravel-video-extractor`). It is **not** on Packagist, so the consuming project must register the repository with Composer and have read access to it. ### Option A — via SSH (recommended) Requires an SSH key with read access to the repo on every machine running `composer install` (the key configured during development works on this host). ```bash # In your Laravel project: composer config repositories.laravel-video-extractor git \ "ssh://git@gitea.office.local:222/apoloz/laravel-video-extractor.git" composer require apoloz/laravel-video-extractor:^1.0 ``` For other servers / CI, add a **Gitea deploy key** (repo → Settings → Deploy Keys) with the machine's public key and pre-seed `known_hosts`: ```bash ssh-keyscan -p 222 gitea.office.local >> ~/.ssh/known_hosts ``` ### Option B — via HTTP with an access token Useful when SSH key distribution is impractical (CI, restricted networks). 1. In Gitea: **Settings → Applications → Generate New Token** (scope `read:repository`). 2. Configure Composer auth (on each machine): ```bash composer config --global http-basic.gitea.office.local ``` 3. Register the repository and install: ```bash composer config repositories.laravel-video-extractor git \ "http://gitea.office.local:3000/apoloz/laravel-video-extractor.git" composer require apoloz/laravel-video-extractor:^1.0 ``` For CI, the auth can be supplied via the `COMPOSER_AUTH` environment variable instead of `composer config`. ### After installation The service provider and `Video` facade are auto-discovered by Laravel — no manual registration needed. To publish the config: ```bash php artisan vendor:publish --tag=video-extractor-config ``` ### Versioning Stable releases are git tags (`v1.0.0`, ...). Use a caret constraint (`^1.0`) to pull tagged releases. The `@dev` / `dev-main` constraint pulls the latest `main` branch tip (no tag required) — convenient during development. ## Supported URLs | Platform | Examples | | --- | --- | | VK | `vkvideo.ru/video-{oid}_{id}`, `vk.com/video-{oid}_{id}`, `vkvideo.ru/clip-...`, `vk.com/video_ext.php?oid=...&id=...`, `m.vk.com/...`, `vksport.vkvideo.ru/...` | | Rutube | `rutube.ru/video/{id}`, `rutube.ru/live/video/{id}`, `rutube.ru/video/private/{id}/?p=token`, `rutube.ru/play/embed/{id}` | | OK.ru | `ok.ru/video/{id}`, `ok.ru/videoembed/{id}`, `ok.ru/live/{id}`, `m.ok.ru/video/{id}`, `odnoklassniki.ru/video/{id}`, `ok.ru/dk?st.mvId=...` | ## How it works The package mirrors the extraction logic of [`yt-dlp`](https://github.com/yt-dlp/yt-dlp), adapted for idiomatic Laravel: - **VK** uses two endpoints with automatic fallback: 1. `GET https://vk.com/video_ext.php?oid=&id=` (primary) — returns a multi-resolution `image[]` array of thumbnails plus full metadata, embedded as JSON in the HTML. 2. `POST https://vk.com/al_video.php` (fallback, yt-dlp's proven method) — yields a single thumbnail URL but surfaces explicit error codes (`3` = login required, `8` = unavailable). - VK serves responses in **Windows-1251**; the package transparently re-encodes them to UTF-8. - **Rutube** uses its clean public REST API (UTF-8, no auth, no challenges): 1. `GET https://rutube.ru/api/video/{id}/?format=json` — metadata + stable `thumbnail_url`. 2. `GET https://rutube.ru/api/play/options/{id}/?format=json` — master HLS playlist + access verdict. - **OK.ru** scrapes the public video page (UTF-8, anonymous access) with a two-tier strategy: 1. (primary) Parses the `
` player payload (HTML-entity-decoded then double `json_decode`) for `movie.poster`, `videos[]` (mp4 qualities), `hlsManifestUrl` and `likeCount`. 2. (fallback) Open Graph / `ya:ovs` `` tags — used when the player payload is absent (external/restricted/paid videos) to still recover the thumbnail and basic metadata. ## Usage ### Extract everything ```php $info = Video::extract($url); $info->title; $info->description; $info->duration; // seconds $info->uploader; // author / channel $info->timestamp; // unix $info->viewCount; $info->isLive; $info->ageLimit; // 0 or 18 $info->categories; // ["Education"] $info->thumbnails; // ThumbnailDto[] (multiple resolutions where available) $info->bestThumbnail(); // highest-resolution ThumbnailDto $info->thumbnailUrl(); // convenience: just the URL $info->formats; // FormatDto[] (mp4/hls/dash for VK, HLS for Rutube) ``` ### Just the thumbnail (fast path) ```php $url = Video::thumbnail($url); // string|null — skips the formats request ``` ### Dependency injection ```php use Apoloz\VideoExtractor\VideoExtractor; public function handle(VideoExtractor $extractor): void { $info = $extractor->extract($url); } ``` ## Configuration Published to `config/video-extractor.php`: ```php return [ 'http' => [ 'timeout' => env('VIDEO_EXTRACTOR_TIMEOUT', 30), 'user_agent' => env('VIDEO_EXTRACTOR_USER_AGENT'), ], 'cache' => [ 'enabled' => env('VIDEO_EXTRACTOR_CACHE_ENABLED', true), 'store' => env('VIDEO_EXTRACTOR_CACHE_STORE'), // null = default 'thumbnail_ttl' => env('VIDEO_EXTRACTOR_THUMBNAIL_TTL', 604800), // 7 days 'info_ttl' => env('VIDEO_EXTRACTOR_INFO_TTL', 600), // 10 min ], ]; ``` **Caching strategy:** thumbnail URLs are stable CDN resources and are cached for a long time (default 7 days). Full extraction results include format URLs that carry an `expires=`/`expire=` parameter, so they are cached briefly and the TTL is automatically capped by the soonest format expiry. ## Error handling | Exception | Cause | | --- | --- | | `PlatformNotSupportedException` | The URL host is neither VK nor Rutube | | `VideoNotFoundException` | Video deleted / removed / not found | | `GeoBlockedException` | Rutube `acl_access` denied the request | | `LoginRequiredException` | VK returned code `3` (private / age-restricted) | | `VideoExtractionException` | Base class for all package errors | All exceptions extend `Apoloz\VideoExtractor\Exceptions\VideoExtractionException`. ## Known limitations - **VK JS challenge (429 → `/challenge.html`):** VK occasionally requires solving a JavaScript challenge. yt-dlp solves this with a built-in JS interpreter; that is out of scope for this package, which raises a descriptive `RuntimeException` instead. Public videos normally work without it; retry later or use a VK session cookie for heavy use. - **Auth-required content** (private / age-restricted VK videos) is not supported without upstream session handling. - **Stream format URLs expire** within hours by design (VK `expires=`, Rutube `expire=`); always re-extract before playback. ## Credits Extraction logic inspired by and adapted from [yt-dlp](https://github.com/yt-dlp/yt-dlp)'s `VK` and `Rutube` extractors. ## License MIT