198 lines
7.8 KiB
Markdown
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
|