Files
laravel-video-extractor/README.md
2026-07-16 22:03:14 +03:00

198 lines
7.8 KiB
Markdown

# 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 <gitea-login> <token>
```
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 `<div data-options="...">` player payload (HTML-entity-decoded then double `json_decode`) for `movie.poster`, `videos[]` (mp4 qualities), `hlsManifestUrl` and `likeCount`.
2. (fallback) Open Graph / `ya:ovs` `<meta>` 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