[Laracasts] Full Dump, April 17, 2026 - Part 2

Страницы:  1
Ответить
 

tecvid

Стаж: 13 лет 5 месяцев

Сообщений: 32


tecvid · 17-Апр-26 10:40 (3 месяца 5 дней назад, ред. 25-Апр-26 10:09)

[Laracasts] Full Dump, April 17, 2026 - Part 2 [2026, ENG]
Год выпуска: 2026
Производитель: Laracasts
Сайт производителя: https://laracasts.com
Автор: Jeffrey Way
Продолжительность: 5-30 мин/урок
Тип раздаваемого материала: Видеоурок
Язык: Английский
Описание:
Полный дамп laracasts.com за 17 апреля 2026. Все уроки были скачаны напрямую из mux в разрешении 2K с сохранением оригинальных кодеков. Уроки, для которых не доступен 2K, были скачаны в следующем доступном разрешении (FHD или HD).
Поскольку объём с каждым разом становился бы всё больше, решил добавить новые уроки как вторую часть. Поэтому списка "Что нового" нет, так как всё содержимое данного торрента и есть всё новое с момента релиза первой части.
Ссылка на первую часть
Небольшая заметка насчёт субтитров...
Сайт laracasts сменил хостинг с vimeo на mux, поэтому логика субтитров немного изменилась. В одном промежутке времени очень много текста. Хотя в самом плеере показывается только нужная часть, в десктопном плеере субтитры занимают почти половину экрана (если не уменьшить). По какой логике в плеере mux показывается только нужная часть, я так и не понял. Попытался сориентироваться на длину слов/предложений, но ничего толкового из этого не вышло. Я даже "скормил" ссылку агенту claude code, он тоже поискал как у них это реализовано, и якобы нашёл и попытался применить, но и из этого тоже ничего толкового не вышло. Поэтому просто оставил как есть.
Данный дамп я делал для себя, поэтому сделал так, как удобно мне самому. Но решил поделиться им со всеми.
На трекере уже имеется полный дамп за 2022 год, но он заброшен. К тому же, этот немного отличается:
  1. Курсы расположены в папках в точности так же, как и на сайте. На сайте один и тот же курс может быть в нескольких топиках, но в раздаче каждый из них будет находиться только в одном из соответствующих папок.
  2. В каждом курсе находится файл "info-[id].md", где указана дата обновления курса, его описание и автор.
  3. Если уроки разделены по главам, то они находятся в соответствующих папках.
  4. Около каждого урока находится файл "Название урока.md", где написано дата публикации и описание урока.
  5. Около каждого урока, для которого доступны субтитры, находится файл "Название урока.vtt", где находятся субтитры на английском языке.
Формат видео: MP4
Видео: avc, 2560x1440, 16:9, ~30.000 к/с, ~2000-4000 кб/с
Аудио: aac lc, 48.0 кгц, 192 кб/с, 2 канала
Скриншоты
Download
Сайт не распространяет и не хранит электронные версии произведений, а лишь предоставляет доступ к создаваемому пользователями каталогу ссылок на торрент-файлы, которые содержат только списки хеш-сумм
Как скачивать? (для скачивания .torrent файлов необходима регистрация)
[Профиль]  [ЛС] 

awxcigin

Стаж: 1 год 11 месяцев

Сообщений: 12


awxcigin · 17-Апр-26 16:44 (спустя 6 часов)

thanks a lot, i wanted to ask something - how do you use mux etc to download these courses? any way i can learn how to do it? any guide will be helpful.
[Профиль]  [ЛС] 

tecvid

Стаж: 13 лет 5 месяцев

Сообщений: 32


tecvid · 17-Апр-26 22:45 (спустя 6 часов, ред. 17-Апр-26 22:45)

awxcigin
welcome, I use a custom script written in laravel (artisan command)
here is the code:
MuxDownloader.php
Код:

<?php
namespace App\Mux;
use App\Console\Commands\LC;
use Illuminate\Support\Number;
use Illuminate\Support\Str;
class MuxDownloader
{
    public function __construct(protected LC $command)
    {
        //
    }
    public function download(array $episode, string $filepath, int $episodeNumber = 0): void
    {
        $query = http_build_query([
            'token' => $episode['muxTokens']['playback'],
        ]);
        $masterUrl = "https://stream.mux.com/{$episode['muxPlaybackId']}.m3u8?$query";
        $resolved = $this->resolveVariant($masterUrl);
        $this->downloadVideo(
            $resolved['video'],
            $resolved['audio'],
            $this->command->disk->path($filepath),
            $episode['length'] ?? 0,
            $episodeNumber
        );
        // $this->downloadCaptions($episode['transcriptSegments'] ?? [], $filepath);
    }
    private function resolveVariant(string $masterUrl): array
    {
        $quality = (int) config('lc.quality');
        if (! $quality) {
            return [
                'video' => $masterUrl,
                'audio' => null,
            ];
        }
        $playlist = @file_get_contents($masterUrl);
        if (! $playlist) {
            return [
                'video' => $masterUrl,
                'audio' => null,
            ];
        }
        $baseUrl = dirname($masterUrl);
        $lines = explode("\n", trim($playlist));
        $best = null;
        $audioUrl = null;
        // Find audio stream
        foreach ($lines as $line) {
            if (preg_match('/^#EXT-X-MEDIA:.*TYPE=AUDIO.*URI="([^"]+)"/', $line, $m)) {
                $audioUrl = str_starts_with($m[1], 'http') ? $m[1] : "$baseUrl/$m[1]";
                break;
            }
        }
        // Find best video variant
        for ($i = 0; $i < count($lines); $i++) {
            if (preg_match('/^#EXT-X-STREAM-INF:.*RESOLUTION=(\d+)x(\d+)/', $lines[$i], $m)) {
                $height = (int) $m[2];
                if ($height <= $quality && (! $best || $height > $best['height'])) {
                    $best = [
                        'height' => $height,
                        'url' => $lines[$i + 1],
                    ];
                }
            }
        }
        if (! $best) {
            return [
                'video' => $masterUrl,
                'audio' => null,
            ];
        }
        $videoUrl = str_starts_with($best['url'], 'http') ? $best['url'] : "$baseUrl/{$best['url']}";
        $this->command->comment("Selected {$best['height']}p variant");
        return [
            'video' => $videoUrl,
            'audio' => $audioUrl,
        ];
    }
    private function downloadVideo(
        string $videoUrl,
        ?string $audioUrl,
        string $outputPath,
        int $duration,
        int $episodeNumber
    ): void {
        $maxAttempts = 3;
        for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
            if ($attempt > 1) {
                $this->command->warn("Retrying download (attempt $attempt/$maxAttempts)...");
            } else {
                $this->command->warn('Downloading video...');
            }
            $inputs = "-i \"$videoUrl\"";
            if ($audioUrl) {
                $inputs .= " -i \"$audioUrl\"";
            }
            $command = "export PATH=\"/opt/homebrew/bin:/usr/local/bin:\$PATH\" && ffmpeg $inputs -c copy -bsf:a aac_adtstoasc \"$outputPath\"";
            $process = proc_open(
                $command,
                [
                    0 => [
                        'file',
                        '/dev/null',
                        'r',
                    ],
                    1 => [
                        'pipe',
                        'w',
                    ],
                    2 => [
                        'pipe',
                        'w',
                    ],
                ],
                $pipes
            );
            if (is_resource($process)) {
                stream_set_blocking($pipes[2], false);
                fclose($pipes[1]);
                $buffer = '';
                while (proc_get_status($process)['running']) {
                    $buffer .= fread($pipes[2], 8192);
                    $this->parseProgress($buffer, $duration);
                    usleep(2e5);
                }
                $buffer .= stream_get_contents($pipes[2]);
                $this->parseProgress($buffer, $duration);
                fclose($pipes[2]);
                $code = proc_close($process);
                if ($code == 0) {
                    $this->command->success("\nVideo has been downloaded");
                    return;
                }
                $hadProgress = (bool) preg_match('/size=\s*\d+\w*\s+time=/', $buffer);
                $this->command->error(
                    ($hadProgress ? "\n" : '')."Failed to download video (attempt $attempt/$maxAttempts)"
                );
                $this->command->failedDownloads[] = [
                    'episode_label' => $episodeNumber,
                    'attempt' => $attempt,
                    'max_attempts' => $maxAttempts,
                ];
            }
        }
    }
    private function parseProgress(string $buffer, int $duration): void
    {
        if (preg_match_all('/size=\s*(\d+)\w*\s+time=(\d+):(\d+):(\d+\.\d+)/', $buffer, $matches, PREG_SET_ORDER)) {
            $last = end($matches);
            $size = (int) $last[1] * 1024;
            $time = $last[2] * 3600 + $last[3] * 60 + (float) $last[4];
            if ($duration > 0 && $time > 0) {
                $estimatedTotal = $size * $duration / $time;
                printf("Downloaded ~%s of ~%s (~%s)       \r",
                    Number::fileSize($size, 2),
                    Number::fileSize($estimatedTotal, 2),
                    Number::percentage($time * 100 / $duration, 1)
                );
            } else {
                printf("Downloaded %s       \r", Number::fileSize($size, 2));
            }
        }
    }
    private function downloadCaptions(array $segments, string $filepath): void
    {
        if (empty($segments)) {
            $this->command->comment("This episode doesn't have captions\n");
            return;
        }
        $this->command->comment('Downloading captions...');
        $vtt = "WEBVTT\n\n";
        foreach ($segments as $i => $segment) {
            $text = strip_tags($segment['text']);
            $start = $segment['startTime'];
            $end = data_get($segments, ($i + 1).'.startTime', $segment['endTime']);
            $vtt .= sprintf(
                "%s --> %s\n%s\n\n",
                $this->formatTimestamp($start),
                $this->formatTimestamp($end),
                $text
            );
        }
        $this->command->disk->put(
            Str::replaceLast('.mp4', '.vtt', $filepath),
            $vtt
        );
        $this->command->success("Captions has been downloaded\n");
    }
    private function formatTimestamp(float $seconds): string
    {
        return sprintf(
            '%02d:%02d:%06.3f',
            (int) ($seconds / 3600),
            (int) (($seconds % 3600) / 60),
            fmod($seconds, 60)
        );
    }
}
you can adapt it to your needs
[Профиль]  [ЛС] 

awxcigin

Стаж: 1 год 11 месяцев

Сообщений: 12


awxcigin · 18-Апр-26 07:02 (спустя 8 часов)

thanks a lot man.
tecvid писал(а):
89081067awxcigin
welcome, I use a custom script written in laravel (artisan command)
here is the code:
MuxDownloader.php
Код:

<?php
namespace App\Mux;
use App\Console\Commands\LC;
use Illuminate\Support\Number;
use Illuminate\Support\Str;
class MuxDownloader
{
    public function __construct(protected LC $command)
    {
        //
    }
    public function download(array $episode, string $filepath, int $episodeNumber = 0): void
    {
        $query = http_build_query([
            'token' => $episode['muxTokens']['playback'],
        ]);
        $masterUrl = "https://stream.mux.com/{$episode['muxPlaybackId']}.m3u8?$query";
        $resolved = $this->resolveVariant($masterUrl);
        $this->downloadVideo(
            $resolved['video'],
            $resolved['audio'],
            $this->command->disk->path($filepath),
            $episode['length'] ?? 0,
            $episodeNumber
        );
        // $this->downloadCaptions($episode['transcriptSegments'] ?? [], $filepath);
    }
    private function resolveVariant(string $masterUrl): array
    {
        $quality = (int) config('lc.quality');
        if (! $quality) {
            return [
                'video' => $masterUrl,
                'audio' => null,
            ];
        }
        $playlist = @file_get_contents($masterUrl);
        if (! $playlist) {
            return [
                'video' => $masterUrl,
                'audio' => null,
            ];
        }
        $baseUrl = dirname($masterUrl);
        $lines = explode("\n", trim($playlist));
        $best = null;
        $audioUrl = null;
        // Find audio stream
        foreach ($lines as $line) {
            if (preg_match('/^#EXT-X-MEDIA:.*TYPE=AUDIO.*URI="([^"]+)"/', $line, $m)) {
                $audioUrl = str_starts_with($m[1], 'http') ? $m[1] : "$baseUrl/$m[1]";
                break;
            }
        }
        // Find best video variant
        for ($i = 0; $i < count($lines); $i++) {
            if (preg_match('/^#EXT-X-STREAM-INF:.*RESOLUTION=(\d+)x(\d+)/', $lines[$i], $m)) {
                $height = (int) $m[2];
                if ($height <= $quality && (! $best || $height > $best['height'])) {
                    $best = [
                        'height' => $height,
                        'url' => $lines[$i + 1],
                    ];
                }
            }
        }
        if (! $best) {
            return [
                'video' => $masterUrl,
                'audio' => null,
            ];
        }
        $videoUrl = str_starts_with($best['url'], 'http') ? $best['url'] : "$baseUrl/{$best['url']}";
        $this->command->comment("Selected {$best['height']}p variant");
        return [
            'video' => $videoUrl,
            'audio' => $audioUrl,
        ];
    }
    private function downloadVideo(
        string $videoUrl,
        ?string $audioUrl,
        string $outputPath,
        int $duration,
        int $episodeNumber
    ): void {
        $maxAttempts = 3;
        for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
            if ($attempt > 1) {
                $this->command->warn("Retrying download (attempt $attempt/$maxAttempts)...");
            } else {
                $this->command->warn('Downloading video...');
            }
            $inputs = "-i \"$videoUrl\"";
            if ($audioUrl) {
                $inputs .= " -i \"$audioUrl\"";
            }
            $command = "export PATH=\"/opt/homebrew/bin:/usr/local/bin:\$PATH\" && ffmpeg $inputs -c copy -bsf:a aac_adtstoasc \"$outputPath\"";
            $process = proc_open(
                $command,
                [
                    0 => [
                        'file',
                        '/dev/null',
                        'r',
                    ],
                    1 => [
                        'pipe',
                        'w',
                    ],
                    2 => [
                        'pipe',
                        'w',
                    ],
                ],
                $pipes
            );
            if (is_resource($process)) {
                stream_set_blocking($pipes[2], false);
                fclose($pipes[1]);
                $buffer = '';
                while (proc_get_status($process)['running']) {
                    $buffer .= fread($pipes[2], 8192);
                    $this->parseProgress($buffer, $duration);
                    usleep(2e5);
                }
                $buffer .= stream_get_contents($pipes[2]);
                $this->parseProgress($buffer, $duration);
                fclose($pipes[2]);
                $code = proc_close($process);
                if ($code == 0) {
                    $this->command->success("\nVideo has been downloaded");
                    return;
                }
                $hadProgress = (bool) preg_match('/size=\s*\d+\w*\s+time=/', $buffer);
                $this->command->error(
                    ($hadProgress ? "\n" : '')."Failed to download video (attempt $attempt/$maxAttempts)"
                );
                $this->command->failedDownloads[] = [
                    'episode_label' => $episodeNumber,
                    'attempt' => $attempt,
                    'max_attempts' => $maxAttempts,
                ];
            }
        }
    }
    private function parseProgress(string $buffer, int $duration): void
    {
        if (preg_match_all('/size=\s*(\d+)\w*\s+time=(\d+):(\d+):(\d+\.\d+)/', $buffer, $matches, PREG_SET_ORDER)) {
            $last = end($matches);
            $size = (int) $last[1] * 1024;
            $time = $last[2] * 3600 + $last[3] * 60 + (float) $last[4];
            if ($duration > 0 && $time > 0) {
                $estimatedTotal = $size * $duration / $time;
                printf("Downloaded ~%s of ~%s (~%s)       \r",
                    Number::fileSize($size, 2),
                    Number::fileSize($estimatedTotal, 2),
                    Number::percentage($time * 100 / $duration, 1)
                );
            } else {
                printf("Downloaded %s       \r", Number::fileSize($size, 2));
            }
        }
    }
    private function downloadCaptions(array $segments, string $filepath): void
    {
        if (empty($segments)) {
            $this->command->comment("This episode doesn't have captions\n");
            return;
        }
        $this->command->comment('Downloading captions...');
        $vtt = "WEBVTT\n\n";
        foreach ($segments as $i => $segment) {
            $text = strip_tags($segment['text']);
            $start = $segment['startTime'];
            $end = data_get($segments, ($i + 1).'.startTime', $segment['endTime']);
            $vtt .= sprintf(
                "%s --> %s\n%s\n\n",
                $this->formatTimestamp($start),
                $this->formatTimestamp($end),
                $text
            );
        }
        $this->command->disk->put(
            Str::replaceLast('.mp4', '.vtt', $filepath),
            $vtt
        );
        $this->command->success("Captions has been downloaded\n");
    }
    private function formatTimestamp(float $seconds): string
    {
        return sprintf(
            '%02d:%02d:%06.3f',
            (int) ($seconds / 3600),
            (int) (($seconds % 3600) / 60),
            fmod($seconds, 60)
        );
    }
}
you can adapt it to your needs
[Профиль]  [ЛС] 

maik2l

Стаж: 13 лет 7 месяцев

Сообщений: 1


maik2l · 28-Апр-26 19:38 (спустя 10 дней)

Спасибо
[Профиль]  [ЛС] 

Ростислав_09

Стаж: 6 лет 2 месяца

Сообщений: 2


Ростислав_09 · 11-Июн-26 13:40 (спустя 1 месяц 12 дней)

Нужна раздача, пожалуйста.
[Профиль]  [ЛС] 

aminexwarrior

Стаж: 1 год 11 месяцев

Сообщений: 1


aminexwarrior · 26-Июн-26 09:49 (спустя 14 дней)

no seeders avaialable for now , do u plan to make it live again plz ! ?
[Профиль]  [ЛС] 
 
Ответить
Loading...
Error