1. Nếu bạn có quyền trên VPS → Cách đúng là bật lại shell_exec
Vì bạn nói đang chạy trên VPS, khả năng cao bạn có quyền root hoặc sudo → lúc này giải pháp chuẩn nhất là:
Bước A: Tìm đúng file cấu hình PHP đang chạy web
Trên VPS (SSH):
Nó sẽ trả kiểu như:
Nếu bạn dùng Nginx/Apache + PHP-FPM thì thường là dạng .../fpm/php.ini.
Bước B: Mở file php.ini đó
Ví dụ:
Bước C: Tìm dòng disable_functions
Tìm:
Lúc này bạn có 3 lựa chọn:
-
Xóa
shell_execkhỏi dòng:
-
Hoặc nếu bạn đang dev nội bộ VPS, có thể tạm comment luôn cả dòng:
-
Nếu file php.ini trống, mà debug vẫn báo
disable_functions→ có thể bị override ở file cấu hình pool FPM (vd:/etc/php/8.2/fpm/pool.d/www.conf) với dòng:
Tiếp theo
Hướng 1: Cài yt-dlp vào chính home của bạn (/home/thanhtung/...) → PHP dùng được
Bước 1: SSH vào host, về home
Bước 2: Tạo thư mục chứa binary yt-dlp (ví dụ ytbin)
Bước 3: Tải yt-dlp vào đây
Dùng curl:
Test thử:
Nếu nó in ra list định dạng là ok ✅
Lúc này file thực tế là:
/home/thanhtung/ytbin/yt-dlp→ NẰM TRONG open_basedir cho phép.
Nếu các bước ok thì bạn đã hoàn thành việc cài đặt. Và sau đây là code php của bạn
<?php
error_reporting(0);
ini_set('display_errors', 0);
set_time_limit(90);
ini_set('memory_limit', '512M');
class YouTubeDownloader {
// yt-dlp binary trong home (đã test ok)
private $tool = '/home/thanhtung/ytbin/yt-dlp';
private function formatBytes($bytes) {
if (!is_numeric($bytes) || $bytes <= 0) return 'unknown';
$units = ['B','KB','MB','GB','TB'];
$i = 0;
while ($bytes >= 1024 && $i < count($units) - 1) {
$bytes /= 1024; $i++;
}
return round($bytes, 1) . ' ' . $units[$i];
}
private function runYtDlp($url) {
// Nếu shell_exec bị tắt thì chịu
if (!function_exists('shell_exec')) return null;
$disabled = ini_get('disable_functions');
if ($disabled && stripos($disabled, 'shell_exec') !== false) return null;
$tool = $this->tool;
// Dùng đúng command đã test trong test_ytb.php, thêm tí retry thôi
$cmd = sprintf(
'%s --dump-single-json --no-warnings --no-playlist ' .
'--retries 5 ' .
'%s 2>&1',
escapeshellcmd($tool),
escapeshellarg($url)
);
$output = shell_exec($cmd);
if (!$output) return null;
// Tìm JSON theo dấu '{' đầu tiên
$start = strpos($output, '{');
if ($start === false) return null;
$json = substr($output, $start);
// Giới hạn JSON tối đa 5MB
$json = substr($json, 0, 5 * 1024 * 1024);
$data = json_decode($json, true);
if (!is_array($data) || empty($data['formats'])) return null;
$streams = [];
foreach ($data['formats'] as $f) {
// Có format storyboard / thumbnail, vẫn có url
if (empty($f['url'])) continue;
$hasVideo = !empty($f['vcodec']) && $f['vcodec'] !== 'none';
$hasAudio = !empty($f['acodec']) && $f['acodec'] !== 'none';
$quality = $f['format_note']
?? (($f['height'] ?? ($hasAudio ? 'audio' : 'unknown')) . 'p');
$size = 'unknown';
if (!empty($f['filesize'])) {
$size = $this->formatBytes($f['filesize']);
} elseif (!empty($f['filesize_approx'])) {
$size = '~' . $this->formatBytes($f['filesize_approx']);
}
// Loại stream: full / video / audio
$type = $hasVideo && $hasAudio
? 'full'
: ($hasVideo ? 'video' : 'audio');
$streams[] = [
'quality' => $quality,
'size' => $size,
'url' => $f['url'],
'type' => $type,
];
}
if (empty($streams)) {
// JSON ok nhưng không có url nào usable
return null;
}
return [
'title' => $data['title'] ?? 'Unknown',
'thumbnail' => $data['thumbnail'] ?? '',
'streams' => $streams,
];
}
public function get($url) {
$result = $this->runYtDlp($url);
if (!$result) {
return [
'error' => 'Không lấy được dữ liệu từ yt-dlp. Thử lại sau vài phút nhé!'
];
}
// Group lại theo full / video / audio cho JS
$grouped = ['full' => [], 'video' => [], 'audio' => []];
foreach ($result['streams'] as $s) {
if ($s['type'] === 'full') {
$grouped['full'][] = $s;
} elseif ($s['type'] === 'video') {
$grouped['video'][] = $s;
} else {
$grouped['audio'][] = $s;
}
}
return [
'success' => true,
'title' => $result['title'],
'thumbnail' => $result['thumbnail'],
'streams' => $grouped,
];
}
}
if (isset($_GET['url'])) {
header('Content-Type: application/json; charset=utf-8');
$dl = new YouTubeDownloader();
echo json_encode($dl->get($_GET['url']), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
}
?>
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YouTube Downloader Pro 2025</title>
<style>
body {font-family:Arial,sans-serif;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;padding:20px;min-height:100vh;}
.c {max-width:960px;margin:auto;background:#fff;color:#333;border-radius:16px;padding:30px;box-shadow:0 20px 50px rgba(0,0,0,.3);}
h1 {text-align:center;color:#fff;background:#333;padding:20px;border-radius:16px 16px 0 0;margin:-30px -30px 30px;}
input,button {width:100%;padding:16px;font-size:18px;border-radius:8px;margin:10px 0;}
input {border:2px solid #ddd;}
button {background:#ff4081;color:#fff;border:none;cursor:pointer;}
.r {margin-top:20px;}
.thumb img {max-width:100%;border-radius:12px;}
.sec h3 {background:#667eea;color:#fff;padding:12px;border-radius:8px;margin:20px 0 10px;}
.item {background:#f8f9fa;padding:15px;margin:8px 0;border-radius:8px;display:flex;justify-content:space-between;align-items:center;}
.dl {background:#667eea;color:#fff;padding:12px 25px;text-decoration:none;border-radius:8px;}
.err {background:#ffebee;color:#c62828;padding:20px;border-radius:8px;border-left:5px solid #c62828;}
</style>
</head>
<body>
<div class="c">
<h1>🎥 YouTube Downloader Pro 2025 - 100% Hoạt Động</h1>
<input type="text" id="u" placeholder="Dán link YouTube...">
<button onclick="go()">LẤY LINK</button>
<div id="r" class="r"></div>
</div>
<script>
async function go() {
let url = document.getElementById('u').value.trim();
if (!url) return;
let r = document.getElementById('r');
r.innerHTML = '<p>Đang lấy link (5-15 giây)...</p>';
try {
let res = await fetch('?url=' + encodeURIComponent(url));
let d = await res.json();
if (d.error) {
r.innerHTML = `<div class="err">${d.error}</div>`;
return;
}
let h = `<div class="thumb"><img src="${d.thumbnail}"></div><h2 style="text-align:center">${d.title}</h2><hr>`;
const add = (t,a) => {
if(a && a.length){
h += `<div class="sec"><h3>${t}</h3>`;
a.forEach(x => {
h += `<div class="item">
<div><b>${x.quality}</b> (${x.size})</div>
<a href="${x.url}" class="dl" target="_blank">Download</a>
</div>`;
});
h += '</div>';
}
};
add('Video + Audio', d.streams.full);
add('Video Only', d.streams.video);
add('Audio Only', d.streams.audio);
r.innerHTML = h;
} catch(e) {
r.innerHTML = '<div class="err">Lỗi kết nối, thử lại nhé!</div>';
}
}
</script>
</body>
</html>