Painel de Controle
+Visão geral do seu sistema de vídeos.
+diff --git a/api.php b/api.php index bf067d3..8a2e600 100644 --- a/api.php +++ b/api.php @@ -54,7 +54,7 @@ switch ($action) { case 'get_streams': try { $pdo = db(); - $stmt = $pdo->query("SELECT id, url, filename, status, created_at FROM streams ORDER BY created_at DESC"); + $stmt = $pdo->query("SELECT id, url, filename, status, created_at, progress FROM streams ORDER BY created_at DESC"); $streams = $stmt->fetchAll(PDO::FETCH_ASSOC); $response['success'] = true; $response['streams'] = $streams; @@ -64,6 +64,26 @@ switch ($action) { } break; + case 'get_stream': + try { + $id = $_GET['id'] ?? null; + if (empty($id)) { + http_response_code(400); + $response['error'] = 'O ID do stream é obrigatório.'; + } else { + $pdo = db(); + $stmt = $pdo->prepare("SELECT id, url, filename, status, created_at, progress FROM streams WHERE id = :id"); + $stmt->execute([':id' => $id]); + $stream = $stmt->fetch(PDO::FETCH_ASSOC); + $response['success'] = true; + $response['stream'] = $stream; + } + } catch (PDOException $e) { + http_response_code(500); + $response['error'] = 'Erro no banco de dados: ' . $e->getMessage(); + } + break; + case 'convert_to_mp4': if ($_SERVER['REQUEST_METHOD'] === 'POST') { $data = json_decode(file_get_contents('php://input'), true); @@ -83,17 +103,37 @@ switch ($action) { http_response_code(404); $response['error'] = 'Stream não encontrado.'; } else { - $pdo->prepare("UPDATE streams SET status = 'converting' WHERE id = :id")->execute([':id' => $id]); + // Get video duration using ffprobe + $ffprobe_command = [ + 'ffprobe', + '-v', 'error', + '-show_entries', 'format=duration', + '-of', 'default=noprint_wrappers=1:nokey=1', + $stream['url'] + ]; + $duration_process = new \Symfony\Component\Process\Process($ffprobe_command); + $duration_process->run(); + $duration = (float)$duration_process->getOutput(); $output_filename = pathinfo($stream['filename'], PATHINFO_FILENAME) . '_' . time() . '.mp4'; $output_path = __DIR__ . '/videos/' . $output_filename; + $pdo->prepare("UPDATE streams SET status = 'converting', duration = :duration, output_filename = :output_filename WHERE id = :id")->execute([ + ':duration' => $duration, + ':output_filename' => $output_filename, + ':id' => $id + ]); + $progress_log_path = sys_get_temp_dir() . '/' . $output_filename . '.log'; + if (!is_dir(__DIR__ . '/videos')) { mkdir(__DIR__ . '/videos', 0775, true); } $command = [ 'ffmpeg', + '-y', + '-v', 'quiet', + '-progress', $progress_log_path, '-i', $stream['url'], '-c:v', 'libx264', '-preset', 'veryfast', @@ -105,23 +145,10 @@ switch ($action) { $process = new \Symfony\Component\Process\Process($command); $process->setTimeout(3600); - $process->run(); + $process->start(); - if (!$process->isSuccessful()) { - http_response_code(500); - $error_output = $process->getErrorOutput(); - $pdo->prepare("UPDATE streams SET status = 'error', error_message = :error WHERE id = :id")->execute([':error' => $error_output, ':id' => $id]); - $response['error'] = 'Falha na conversão do vídeo.'; - } else { - $stmt = $pdo->prepare("UPDATE streams SET status = 'completed', converted_path = :converted_path WHERE id = :id"); - $stmt->execute([ - ':converted_path' => $output_filename, - ':id' => $id - ]); - - $response['success'] = true; - $response['message'] = 'Vídeo convertido com sucesso!'; - } + $response['success'] = true; + $response['message'] = 'A conversão do vídeo foi iniciada.'; } } catch (PDOException $e) { http_response_code(500); @@ -137,6 +164,74 @@ switch ($action) { } break; + case 'get_conversion_progress': + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + $id = $_GET['id'] ?? null; + + if (empty($id)) { + http_response_code(400); + $response['error'] = 'O ID do stream é obrigatório.'; + } else { + try { + $pdo = db(); + $stmt = $pdo->prepare("SELECT duration, converted_path, output_filename FROM streams WHERE id = :id"); + $stmt->execute([':id' => $id]); + $stream = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$stream) { + http_response_code(404); + $response['error'] = 'Stream não encontrado.'; + } else { + $output_filename = $stream['output_filename']; + if(empty($output_filename)) { + $response['progress'] = 0; + $response['success'] = true; + } else { + $progress_log_path = sys_get_temp_dir() . '/' . $output_filename . '.log'; + + if (!file_exists($progress_log_path)) { + $response['progress'] = 0; + } else { + $log_content = file_get_contents($progress_log_path); + preg_match_all("/out_time_ms=(\d+)/", $log_content, $matches); + $last_match = end($matches[1]); + + if ($last_match) { + $processed_ms = (float)$last_match / 1000000; // a unidade está em microsegundos + $duration = (float)$stream['duration']; + $progress = $duration > 0 ? round(($processed_ms / $duration) * 100) : 0; + $progress = min(100, $progress); // Garante que o progresso não passe de 100 + + $pdo->prepare("UPDATE streams SET progress = :progress WHERE id = :id")->execute([':progress' => $progress, ':id' => $id]); + + if ($progress == 100) { + $pdo->prepare("UPDATE streams SET status = 'completed', converted_path = :converted_path WHERE id = :id")->execute([':converted_path' => $output_filename, ':id' => $id]); + unlink($progress_log_path); // Limpa o arquivo de log + } + + $response['progress'] = $progress; + } else { + $response['progress'] = 0; + } + } + $response['success'] = true; + } + + } + } catch (PDOException $e) { + http_response_code(500); + $response['error'] = 'Erro no banco de dados: ' . $e->getMessage(); + } catch (\Exception $e) { + http_response_code(500); + $response['error'] = 'Erro ao obter progresso: ' . $e->getMessage(); + } + } + } else { + http_response_code(405); + $response['error'] = 'Método não permitido para esta ação.'; + } + break; + case 'send_to_dropbox': if ($_SERVER['REQUEST_METHOD'] === 'POST') { $data = json_decode(file_get_contents('php://input'), true); @@ -168,17 +263,6 @@ switch ($action) { break; } - /* - COMO OBTER UM TOKEN DE ACESSO DO DROPBOX: - 1. Vá para https://www.dropbox.com/developers/apps e clique em "Create app". - 2. Escolha "Scoped Access". - 3. Selecione "Full Dropbox" ou "App folder". - 4. Dê um nome único à sua aplicação (ex: MeuConversorDeVideo). - 5. Na aba "Permissions", marque as permissões: `files.content.write`. - 6. Clique em "Submit". - 7. Na aba "Settings", procure pela seção "Generated access token" e clique em "Generate". - 8. Copie o token gerado e cole no campo "Token do Dropbox" na interface. - */ $app = new \Dropbox\App("", "", $token); $dropbox = new \Dropbox\Client($app); @@ -205,11 +289,6 @@ switch ($action) { } break; - default: - http_response_code(400); - $response['error'] = 'Ação não especificada ou inválida.'; - break; - case 'delete': if ($_SERVER['REQUEST_METHOD'] === 'POST') { $data = json_decode(file_get_contents('php://input'), true); @@ -257,6 +336,11 @@ switch ($action) { $response['error'] = 'Método não permitido para esta ação.'; } break; + + default: + http_response_code(400); + $response['error'] = 'Ação não especificada ou inválida.'; + break; } echo json_encode($response); diff --git a/cloud.php b/cloud.php new file mode 100644 index 0000000..cc5d01f --- /dev/null +++ b/cloud.php @@ -0,0 +1,137 @@ + + +
+ + +Converta seus vídeos M3U8 para MP4 de forma simples e rápida.
-Visão geral do seu sistema de vídeos.
+| Nome | -Status | -Criação | -Ações | -
|---|---|---|---|
| Nome | +Status | +Criação | +" . htmlspecialchars($row['filename']) . " | "; echo "" . $status . " | "; echo "" . htmlspecialchars($row['created_at']) . " | "; - echo ""; - - // Botão Play - $playUrl = ($status === 'completed' && !empty($row['converted_path'])) - ? 'videos/' . htmlspecialchars($row['converted_path'], ENT_QUOTES) - : htmlspecialchars($row['url'], ENT_QUOTES); - echo ''; - - // Botão Converter foi removido pois a conversão agora é automática - - // Botão Dropbox - if ($status === 'completed') { - echo ''; - } - - // Botão Deletar - echo ''; - - echo " | "; echo ""; } if ($stmt->rowCount() === 0) { - echo "
|---|---|---|---|
| Nenhum arquivo encontrado. Adicione um stream para começar. | |||
| Nenhum arquivo recente. | |||
| Erro ao carregar arquivos: " . $e->getMessage() . " | |||
| Erro ao carregar arquivos. | |||
Converta seus vídeos M3U8 para MP4 de forma simples e rápida.
+| Nome | +Status | +Criação | +Progresso | +Ações | +
|---|---|---|---|---|
| " . htmlspecialchars($row['filename']) . " | "; + echo "" . $status . " | "; + echo "" . htmlspecialchars($row['created_at']) . " | "; + echo ""; + if ($status === 'converting') { + echo ''; + } + echo " | "; + echo ""; + + $playUrl = ($status === 'completed' && !empty($row['converted_path'])) + ? 'videos/' . htmlspecialchars($row['converted_path'], ENT_QUOTES) + : htmlspecialchars($row['url'], ENT_QUOTES); + echo ''; + + if ($status === 'completed') { + echo ''; + } + + echo ''; + + echo " | "; + echo "
| Nenhum arquivo encontrado. Adicione um stream para começar. | ||||
| Erro ao carregar arquivos: " . $e->getMessage() . " | ||||