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 год, но он заброшен. К тому же, этот немного отличается:
Курсы расположены в папках в точности так же, как и на сайте. На сайте один и тот же курс может быть в нескольких топиках, но в раздаче каждый из них будет находиться только в одном из соответствующих папок.
В каждом курсе находится файл "info-[id].md", где указана дата обновления курса, его описание и автор.
Если уроки разделены по главам, то они находятся в соответствующих папках.
Около каждого урока находится файл "Название урока.md", где написано дата публикации и описание урока.
Около каждого урока, для которого доступны субтитры, находится файл "Название урока.vtt", где находятся субтитры на английском языке.
Формат видео : MP4
Видео : avc, 2560x1440, 16:9, ~30.000 к/с, ~2000-4000 кб/с
Аудио : aac lc, 48.0 кгц, 192 кб/с, 2 канала
Download
Сайт не распространяет и не хранит электронные версии произведений, а лишь предоставляет доступ к создаваемому
пользователями каталогу ссылок на
торрент-файлы ,
которые содержат только списки хеш-сумм
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 писал(а):
89081067 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
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 ! ?