Files
laravel-video-extractor/README.md
2026-07-16 21:56:58 +03:00

7.8 KiB

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.

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/eatvio/laravel-video-extractor). It is not on Packagist, so the consuming project must register the repository with Composer and have read access to it.

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).

# In your Laravel project:
composer config repositories.laravel-video-extractor git \
  "ssh://git@gitea.office.local:222/eatvio/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:

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):
    composer config --global http-basic.gitea.office.local <gitea-login> <token>
    
  3. Register the repository and install:
    composer config repositories.laravel-video-extractor git \
      "http://gitea.office.local:3000/eatvio/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:

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, 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

$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)

$url = Video::thumbnail($url); // string|null — skips the formats request

Dependency injection

use Apoloz\VideoExtractor\VideoExtractor;

public function handle(VideoExtractor $extractor): void
{
    $info = $extractor->extract($url);
}

Configuration

Published to config/video-extractor.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's VK and Rutube extractors.

License

MIT