2026-08-05 14:39:15 +03:00
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"bytes"
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"io"
|
|
|
|
|
|
"log"
|
|
|
|
|
|
"net/http"
|
2026-08-05 15:17:02 +03:00
|
|
|
|
"os"
|
2026-08-05 14:39:15 +03:00
|
|
|
|
"os/exec"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func compressRAMHandler(w http.ResponseWriter, r *http.Request) {
|
2026-08-05 15:17:02 +03:00
|
|
|
|
start := time.Now()
|
|
|
|
|
|
log.Printf("[+] Новый запрос от %s", r.RemoteAddr)
|
|
|
|
|
|
|
2026-08-05 14:39:15 +03:00
|
|
|
|
if r.Method != http.MethodPost {
|
|
|
|
|
|
http.Error(w, "Только POST метод", http.StatusMethodNotAllowed)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-05 15:17:02 +03:00
|
|
|
|
// 1. Создаем временный файл в RAM (/tmp работает на tmpfs)
|
|
|
|
|
|
tmpFile, err := os.CreateTemp("", "input-video-*.mp4")
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Printf("[-] Ошибка создания tmp-файла: %v", err)
|
|
|
|
|
|
http.Error(w, "Ошибка выpoint буфера RAM", http.StatusInternalServerError)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
tmpFilePath := tmpFile.Name()
|
|
|
|
|
|
defer os.Remove(tmpFilePath) // Удаляем файл после завершения работы
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Копируем тело запроса во временный файл
|
|
|
|
|
|
copiedBytes, err := io.Copy(tmpFile, r.Body)
|
|
|
|
|
|
_ = tmpFile.Close() // Обязательно закрываем файл перед чтением в FFmpeg!
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Printf("[-] Ошибка вычитывания Body: %v (получено %d байт)", err, copiedBytes)
|
|
|
|
|
|
http.Error(w, "Ошибка загрузки файла", http.StatusBadRequest)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Printf("[+] Файл полностью загружен в RAM: %d байт (%.2f MB) за %v",
|
|
|
|
|
|
copiedBytes, float64(copiedBytes)/(1024*1024), time.Since(start))
|
|
|
|
|
|
|
|
|
|
|
|
if copiedBytes == 0 {
|
|
|
|
|
|
log.Printf("[-] Ошибка: Прилетело 0 байт!")
|
|
|
|
|
|
http.Error(w, "Пустой файл", http.StatusBadRequest)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Заголовки ответа
|
2026-08-05 14:39:15 +03:00
|
|
|
|
w.Header().Set("Content-Type", "video/mp4")
|
|
|
|
|
|
w.Header().Set("Transfer-Encoding", "chunked")
|
|
|
|
|
|
|
2026-08-05 15:17:02 +03:00
|
|
|
|
ctx, cancel := context.WithTimeout(r.Context(), 120*time.Second)
|
2026-08-05 14:39:15 +03:00
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
2026-08-05 15:17:02 +03:00
|
|
|
|
// 4. Запуск FFmpeg
|
2026-08-05 14:39:15 +03:00
|
|
|
|
args := []string{
|
|
|
|
|
|
"-y",
|
2026-08-05 15:17:02 +03:00
|
|
|
|
"-i", tmpFilePath,
|
2026-08-05 14:39:15 +03:00
|
|
|
|
"-c:v", "libx264",
|
2026-08-05 15:26:25 +03:00
|
|
|
|
"-preset", "ultrafast",
|
|
|
|
|
|
"-crf", "29",
|
|
|
|
|
|
"-tune", "zerolatency",
|
|
|
|
|
|
"-threads", "5",
|
2026-08-05 14:39:15 +03:00
|
|
|
|
"-movflags", "frag_keyframe+empty_moov",
|
|
|
|
|
|
"-f", "mp4",
|
|
|
|
|
|
"pipe:1",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cmd := exec.CommandContext(ctx, "ffmpeg", args...)
|
|
|
|
|
|
|
|
|
|
|
|
var stderrBuf bytes.Buffer
|
|
|
|
|
|
cmd.Stderr = &stderrBuf
|
|
|
|
|
|
|
|
|
|
|
|
stdoutPipe, err := cmd.StdoutPipe()
|
|
|
|
|
|
if err != nil {
|
2026-08-05 15:17:02 +03:00
|
|
|
|
log.Printf("[-] Ошибка stdout pipe: %v", err)
|
|
|
|
|
|
http.Error(w, "Ошибка FFmpeg", http.StatusInternalServerError)
|
2026-08-05 14:39:15 +03:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := cmd.Start(); err != nil {
|
2026-08-05 15:17:02 +03:00
|
|
|
|
log.Printf("[-] Ошибка старта FFmpeg: %v", err)
|
|
|
|
|
|
http.Error(w, "Ошибка старта FFmpeg", http.StatusInternalServerError)
|
2026-08-05 14:39:15 +03:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-05 15:17:02 +03:00
|
|
|
|
log.Printf("[+] FFmpeg успешно запущен, начинаем стримить ответ...")
|
|
|
|
|
|
|
|
|
|
|
|
// 5. Потоково отдаем сжатый ответ
|
|
|
|
|
|
written, _ := io.Copy(w, stdoutPipe)
|
2026-08-05 14:39:15 +03:00
|
|
|
|
|
|
|
|
|
|
if waitErr := cmd.Wait(); waitErr != nil {
|
2026-08-05 15:17:02 +03:00
|
|
|
|
log.Printf("[-] FFmpeg завершился с ошибкой: %v\nЛог FFmpeg:\n%s", waitErr, stderrBuf.String())
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.Printf("[+] Обработка завершена успешно! Отправлено клиенту: %d байт (%.2f MB). Общее время: %v",
|
|
|
|
|
|
written, float64(written)/(1024*1024), time.Since(start))
|
2026-08-05 14:39:15 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
|
http.HandleFunc("/compress", compressRAMHandler)
|
|
|
|
|
|
|
|
|
|
|
|
log.Println("RAM Converter запущен на :1554...")
|
|
|
|
|
|
log.Fatal(http.ListenAndServe("127.0.0.1:1554", nil))
|
|
|
|
|
|
}
|