package main import ( "bytes" "context" "io" "log" "net/http" "os" "os/exec" "time" ) func compressRAMHandler(w http.ResponseWriter, r *http.Request) { start := time.Now() log.Printf("[+] Новый запрос от %s", r.RemoteAddr) if r.Method != http.MethodPost { http.Error(w, "Только POST метод", http.StatusMethodNotAllowed) return } // 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. Заголовки ответа w.Header().Set("Content-Type", "video/mp4") w.Header().Set("Transfer-Encoding", "chunked") ctx, cancel := context.WithTimeout(r.Context(), 120*time.Second) defer cancel() // 4. Запуск FFmpeg args := []string{ "-y", "-i", tmpFilePath, "-c:v", "libx264", "-preset", "veryfast", "-crf", "26", "-threads", "4", "-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 { log.Printf("[-] Ошибка stdout pipe: %v", err) http.Error(w, "Ошибка FFmpeg", http.StatusInternalServerError) return } if err := cmd.Start(); err != nil { log.Printf("[-] Ошибка старта FFmpeg: %v", err) http.Error(w, "Ошибка старта FFmpeg", http.StatusInternalServerError) return } log.Printf("[+] FFmpeg успешно запущен, начинаем стримить ответ...") // 5. Потоково отдаем сжатый ответ written, _ := io.Copy(w, stdoutPipe) if waitErr := cmd.Wait(); waitErr != nil { 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)) } } func main() { http.HandleFunc("/compress", compressRAMHandler) log.Println("RAM Converter запущен на :1554...") log.Fatal(http.ListenAndServe("127.0.0.1:1554", nil)) }