#!/usr/bin/env php ['bitrate' => '32k', 'sample_rate' => 22050, 'channels' => 1], 'low' => ['bitrate' => '64k', 'sample_rate' => 22050, 'channels' => 1], 'mid' => ['bitrate' => '128k', 'sample_rate' => 44100, 'channels' => 2], ]; // ============================================================ // ARGÜMAN PARSE // ============================================================ $options = getopt('', ['limit:', 'offset:', 'dry-run', 'skip-high', 'only-mp3', 'song:', 'help']); if (isset($options['help'])) { echo file_get_contents(__FILE__); exit(0); } $limit = isset($options['limit']) ? (int)$options['limit'] : 0; $offset = isset($options['offset']) ? (int)$options['offset'] : 0; $dryRun = isset($options['dry-run']); $skipHigh = isset($options['skip-high']); $onlyMp3 = isset($options['only-mp3']); $singleSong = $options['song'] ?? null; // ============================================================ // LARAVEL BOOT // ============================================================ echo "🎵 Ses Eşitleme Re-encode Script v3\n"; echo str_repeat('=', 50) . "\n"; require __DIR__ . '/vendor/autoload.php'; $app = require_once __DIR__ . '/bootstrap/app.php'; $kernel = $app->make(Illuminate\Contracts\Console\Kernel::class); $kernel->bootstrap(); // Tenant başlat $tenant = App\Models\Tenant::find($TENANT_ID); if (!$tenant) { echo "❌ Tenant {$TENANT_ID} bulunamadı!\n"; exit(1); } tenancy()->initialize($tenant); echo "✅ Tenant: {$TENANT_ID}\n"; // ============================================================ // ŞARKILARI BUL // ============================================================ $query = DB::connection('tenant') ->table('muzibu_songs') ->whereNotNull('hls_path') ->where('hls_path', '!=', '') ->orderBy('song_id'); if ($singleSong) { $query->where('song_id', $singleSong); } if ($offset > 0) { $query->skip($offset); } if ($limit > 0) { $query->take($limit); } $songs = $query->get(); $total = $songs->count(); echo "📊 Toplam şarkı: {$total}\n"; if ($offset > 0) echo " Offset: {$offset}\n"; if ($limit > 0) echo " Limit: {$limit}\n"; echo " Mod: " . ($dryRun ? 'DRY-RUN' : ($onlyMp3 ? 'SADECE MP3' : ($skipHigh ? 'VARIANT+MP3 (high atla)' : 'TAM RE-ENCODE'))) . "\n"; echo str_repeat('=', 50) . "\n\n"; if ($dryRun) { echo "🔍 Dry-run: İşlem yapılmadı.\n"; exit(0); } // ============================================================ // YARDIMCI FONKSİYONLAR // ============================================================ /** * İki geçişli loudnorm filtre zinciri */ function buildTwoPassFilter(string $inputPath): string { global $FFMPEG, $LOUDNORM_TARGET, $LOUDNORM_TP, $LOUDNORM_LRA, $LIMITER; $loudnormBase = "loudnorm=I={$LOUDNORM_TARGET}:TP={$LOUDNORM_TP}:LRA={$LOUDNORM_LRA}"; // Pass 1: Ölçüm $cmd = "{$FFMPEG} -hide_banner -i " . escapeshellarg($inputPath) . " -af \"{$loudnormBase}:print_format=json\" -f null /dev/null 2>&1"; $output = shell_exec($cmd); // JSON parse if ($output && preg_match('/\{[^{}]*"input_i"\s*:\s*"[^"]*"[^{}]*\}/s', $output, $match)) { $json = json_decode($match[0], true); if ($json && isset($json['input_i'], $json['input_tp'], $json['input_lra'], $json['input_thresh'])) { return sprintf( '%s:measured_I=%s:measured_TP=%s:measured_LRA=%s:measured_thresh=%s:linear=false,%s', $loudnormBase, $json['input_i'], $json['input_tp'], $json['input_lra'], $json['input_thresh'], $LIMITER ); } } // Fallback: tek geçiş echo " ⚠️ Ölçüm başarısız, tek geçiş\n"; return "{$loudnormBase},{$LIMITER}"; } /** * MP3 kaynak dosyasını bul */ function findMp3(object $song): ?string { global $TENANT_ID; $basePaths = [ storage_path("app/public/muzibu/songs/{$song->file_path}"), storage_path("../tenant{$TENANT_ID}/app/public/muzibu/songs/{$song->file_path}"), ]; foreach ($basePaths as $p) { if (!empty($song->file_path) && file_exists($p)) return $p; } return null; } /** * HLS dizinini bul */ function findHlsDir(object $song): ?string { global $TENANT_ID; $relDir = str_contains($song->hls_path, 'playlist.m3u8') ? dirname($song->hls_path) : rtrim($song->hls_path, '/'); $paths = [ storage_path("app/public/{$relDir}"), storage_path("../tenant{$TENANT_ID}/app/public/{$relDir}"), storage_path("../tenant{$TENANT_ID}/app/public/muzibu/hls/{$song->song_id}"), ]; foreach ($paths as $p) { if (file_exists($p) && file_exists("{$p}/playlist.m3u8")) return $p; } return null; } /** * Bitrate tespit et */ function detectBitrate(string $filePath): int { $cmd = "ffprobe -v quiet -print_format json -show_format " . escapeshellarg($filePath); $output = shell_exec($cmd); $data = json_decode($output, true); $bps = $data['format']['bit_rate'] ?? 128000; $kbps = round((int)$bps / 1000); if ($kbps <= 128) return 128; if ($kbps <= 160) return 160; if ($kbps <= 192) return 192; if ($kbps <= 256) return 256; if ($kbps <= 320) return 320; return $kbps; } /** * High kalite re-encode (4sn segment) */ function reEncodeHigh(string $mp3Path, string $hlsDir, int $songId): bool { global $FFMPEG, $HLS_SEGMENT_TIME; $keyInfoPath = "{$hlsDir}/enc.keyinfo"; if (!file_exists($keyInfoPath)) { echo " ❌ enc.keyinfo yok\n"; return false; } // Eski segment'leri sil foreach (glob("{$hlsDir}/segment-*.ts") as $seg) unlink($seg); $bitrate = detectBitrate($mp3Path); $filters = buildTwoPassFilter($mp3Path); $playlistPath = "{$hlsDir}/playlist.m3u8"; $segPattern = "{$hlsDir}/segment-%03d.ts"; $cmd = sprintf( '%s -hide_banner -y -i %s -map 0:a -c:a aac -b:a %dk -profile:a aac_low -ar 48000 -ac 2 -af %s -hls_key_info_file %s -start_number 0 -hls_time %d -hls_list_size 0 -hls_playlist_type vod -hls_segment_filename %s -f hls %s 2>&1', $FFMPEG, escapeshellarg($mp3Path), $bitrate, escapeshellarg($filters), escapeshellarg($keyInfoPath), $HLS_SEGMENT_TIME, escapeshellarg($segPattern), escapeshellarg($playlistPath) ); exec($cmd, $output, $ret); return $ret === 0; } /** * Variant oluştur */ function generateVariant(string $mp3Path, string $hlsDir, string $name, array $config): bool { global $FFMPEG, $HLS_SEGMENT_TIME; $varDir = "{$hlsDir}/{$name}"; // Zaten varsa sil (yeni filtre ile yeniden oluştur) if (file_exists("{$varDir}/playlist.m3u8")) { foreach (glob("{$varDir}/segment-*.ts") as $seg) unlink($seg); @unlink("{$varDir}/playlist.m3u8"); } if (!file_exists($varDir)) mkdir($varDir, 0755, true); // Encryption: ana key'i kullan, farklı IV $keyInfoPath = null; $mainKeyInfo = "{$hlsDir}/enc.keyinfo"; if (file_exists($mainKeyInfo)) { $lines = explode("\n", trim(file_get_contents($mainKeyInfo))); $keyUri = $lines[0]; $keyFile = $lines[1]; $iv = bin2hex(random_bytes(16)); $sep = str_contains($keyUri, '?') ? '&' : '?'; $varKeyUri = "{$keyUri}{$sep}v={$name}"; $keyInfoPath = "{$varDir}/enc.keyinfo"; file_put_contents($keyInfoPath, implode("\n", [$varKeyUri, $keyFile, $iv])); } $filters = buildTwoPassFilter($mp3Path); $cmd = sprintf( '%s -hide_banner -y -i %s -c:a aac -b:a %s -profile:a aac_low -ar %d -ac %d -vn -af %s', $FFMPEG, escapeshellarg($mp3Path), $config['bitrate'], $config['sample_rate'], $config['channels'], escapeshellarg($filters) ); if ($keyInfoPath) { $cmd .= ' -hls_key_info_file ' . escapeshellarg($keyInfoPath); } $cmd .= sprintf( ' -hls_time %d -hls_list_size 0 -hls_playlist_type vod -hls_segment_filename %s %s 2>&1', $HLS_SEGMENT_TIME, escapeshellarg("{$varDir}/segment-%03d.ts"), escapeshellarg("{$varDir}/playlist.m3u8") ); exec($cmd, $output, $ret); return $ret === 0; } /** * Master playlist oluştur */ function generateMaster(string $hlsDir): void { global $VARIANTS; // High bitrate hesapla $highBitrate = 256000; $playlistFile = "{$hlsDir}/playlist.m3u8"; if (file_exists($playlistFile)) { $content = file_get_contents($playlistFile); $totalDur = 0; if (preg_match_all('/#EXTINF:([\d.]+),/', $content, $m)) { foreach ($m[1] as $d) $totalDur += (float)$d; } $totalSize = 0; foreach (glob("{$hlsDir}/segment-*.ts") as $seg) $totalSize += filesize($seg); if ($totalDur > 0 && $totalSize > 0) { $highBitrate = (int)round($totalSize * 8 / $totalDur); } } $master = "#EXTM3U\n"; $bandwidths = ['ultralow' => 32000, 'low' => 64000, 'mid' => 128000]; foreach ($VARIANTS as $name => $cfg) { if (file_exists("{$hlsDir}/{$name}/playlist.m3u8")) { $bw = $bandwidths[$name] ?? 64000; $master .= "#EXT-X-STREAM-INF:BANDWIDTH={$bw},CODECS=\"mp4a.40.2\",NAME=\"{$name}\"\n"; $master .= "{$name}/playlist.m3u8\n"; } } if (file_exists($playlistFile)) { $master .= "#EXT-X-STREAM-INF:BANDWIDTH={$highBitrate},CODECS=\"mp4a.40.2\",NAME=\"high\"\n"; $master .= "playlist.m3u8\n"; } file_put_contents("{$hlsDir}/master.m3u8", $master); } /** * MP3 üret (128k veya 64k) */ function generateMp3(string $mp3Path, int $songId, string $quality): bool { global $FFMPEG, $TENANT_ID; $dir = storage_path("../tenant{$TENANT_ID}/app/public/muzibu/songs/mp3_{$quality}"); if (!file_exists($dir)) mkdir($dir, 0755, true); $outPath = "{$dir}/{$songId}.mp3"; // Zaten varsa sil (yeni filtre ile yeniden oluştur) if (file_exists($outPath)) unlink($outPath); $filters = buildTwoPassFilter($mp3Path); $sr = $quality === '64' ? 22050 : 44100; $ch = $quality === '64' ? 1 : 2; $cmd = sprintf( '%s -hide_banner -y -i %s -b:a %sk -ar %d -ac %d -af %s -map_metadata -1 -vn %s 2>&1', $FFMPEG, escapeshellarg($mp3Path), $quality, $sr, $ch, escapeshellarg($filters), escapeshellarg($outPath) ); exec($cmd, $output, $ret); return $ret === 0; } // ============================================================ // ANA DÖNGÜ // ============================================================ $success = 0; $failed = 0; $startTime = microtime(true); foreach ($songs as $i => $song) { $num = $i + 1; $elapsed = round(microtime(true) - $startTime); $eta = $num > 1 ? round(($elapsed / $num) * ($total - $num) / 60) : '?'; echo "[{$num}/{$total}] Song #{$song->song_id} (ETA: {$eta}dk)\n"; $mp3Path = findMp3($song); if (!$mp3Path) { echo " ❌ MP3 bulunamadı, atlanıyor\n"; $failed++; continue; } $hlsDir = findHlsDir($song); if (!$hlsDir && !$onlyMp3) { echo " ❌ HLS dizini bulunamadı, atlanıyor\n"; $failed++; continue; } // 1. High re-encode if (!$onlyMp3 && !$skipHigh) { echo " 🔄 High re-encode... "; if (reEncodeHigh($mp3Path, $hlsDir, $song->song_id)) { echo "✅\n"; } else { echo "❌\n"; $failed++; continue; } } // 2. Variant'lar if (!$onlyMp3) { foreach ($VARIANTS as $name => $config) { echo " 🔄 {$name}... "; echo generateVariant($mp3Path, $hlsDir, $name, $config) ? "✅\n" : "❌\n"; } // 3. Master playlist generateMaster($hlsDir); echo " 📋 master.m3u8 ✅\n"; } // 4. MP3 128k echo " 🔄 MP3 128k... "; echo generateMp3($mp3Path, $song->song_id, '128') ? "✅\n" : "❌\n"; // 5. MP3 64k echo " 🔄 MP3 64k... "; echo generateMp3($mp3Path, $song->song_id, '64') ? "✅\n" : "❌\n"; $success++; echo "\n"; } $totalTime = round((microtime(true) - $startTime) / 60, 1); echo str_repeat('=', 50) . "\n"; echo "✅ Tamamlandı: {$success} başarılı, {$failed} başarısız ({$totalTime} dk)\n";