Skip to content

Commit

Permalink
rebuild the application
Browse files Browse the repository at this point in the history
i rebuild it in go...
  • Loading branch information
MementoMori11723 committed Sep 13, 2024
1 parent eafe4e8 commit 74f7ef1
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 1,068 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
uploads/
gifs/
76 changes: 20 additions & 56 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,69 +1,33 @@
# Stage 1: Build Stage with python:3.10-alpine for a smaller footprint
FROM python:3.10-alpine AS builder
# Use the official Go base image for building the app
FROM golang:1.20-alpine AS builder

# Set environment variables to avoid Python output buffering
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Install build dependencies and required libraries
RUN apk add --no-cache \
build-base \
gcc \
libc-dev \
libjpeg-turbo-dev \
zlib-dev \
python3-dev \
musl-dev \
bash \
jpeg-dev \
freetype-dev \
pkgconfig \
py3-pip \
py3-setuptools \
py3-wheel \
openblas-dev

# Set the working directory in the container
# Set the working directory inside the container
WORKDIR /app

# Copy requirements file and install dependencies
COPY requirements.txt .

# Install Python dependencies without caching to reduce the final image size
RUN pip install --no-cache-dir --user -r requirements.txt
# Copy the Go app into the working directory
COPY app.go .

# Stage 2: Final Stage - Use a minimal Alpine image for runtime
FROM python:3.10-alpine
# Build the Go app with optimizations for a small binary
RUN go build -ldflags="-s -w" -o app app.go

# Install runtime dependencies only
RUN apk add --no-cache \
libjpeg-turbo \
zlib \
libstdc++ \
jpeg-dev \
bash \
openblas
# Create a new, smaller image for running the app
FROM alpine:latest

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Install required dependencies: ffmpeg and ca-certificates
RUN apk --no-cache add ca-certificates ffmpeg

# Set the working directory in the container
# Set working directory inside the container
WORKDIR /app

# Copy installed dependencies from the builder stage
COPY --from=builder /root/.local /root/.local

# Ensure the Python scripts in the user folder are in the PATH
ENV PATH=/root/.local/bin:$PATH

# Copy the application code
COPY . .
# Copy the binary from the builder stage
COPY --from=builder /app/app .

# Expose port for streamlit
EXPOSE 8501
# Create necessary directories
RUN mkdir -p ./uploads ./gifs

# Run the app
CMD ["streamlit", "run", "app.py"]
# Expose the port the app runs on
EXPOSE 8080

# Command to run the Go app
CMD ["./app"]

14 changes: 0 additions & 14 deletions Pipfile

This file was deleted.

843 changes: 0 additions & 843 deletions Pipfile.lock

This file was deleted.

59 changes: 2 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,63 +12,8 @@ And there you go, you have successfully converted a video to an image/gif.
## Requirments

1. opencv-python
2. streamlit
3. pillow

## Code

```python
from PIL import Image
import streamlit as st
import cv2
import os

def main():
st.set_page_config(page_title="Gifnetor",page_icon=":vhs:",layout="centered")
st.title("Video to Gif converter")
video = st.file_uploader("Upload Video",type=["mp4","mov","mkv"],accept_multiple_files=False)
path = os.path.join("uploads/",video.name) if video else None
if video:
with open(path,"wb") as upload :
upload.write(video.getbuffer())
st.success("Uploaded successfully")
button = st.button("Convert to Gif")
if button:
output = convert(path)
st.image(output)
st.download_button("Download Gif",data=open(output,"rb"),file_name="output.gif",mime="image/gif")
reset = st.button("Remove Files")
if reset:
if os.path.exists(path) and os.path.exists(os.path.join("uploads/","output.gif")):
os.remove(path)
os.remove(os.path.join("uploads/","output.gif"))
st.warning(f"Files were removed")
else:
st.info(f"No Files were removed")

def convert(filepath):
cap = cv2.VideoCapture(filepath)
frames = []
if not cap.isOpened():
st.error("Error occured while opening the file")
exit()
while True:
ret,frame = cap.read()
if not ret:break
pil_image = Image.fromarray(cv2.cvtColor(frame,cv2.COLOR_BGR2RGB))
pil_image = pil_image.resize((pil_image.width // 4, pil_image.height // 4))
frames.append(pil_image)
gif_path = os.path.join("uploads/","output.gif")
frames[0].save(gif_path,save_all=True,append_images=frames[1:],loop=0,optimize=True,quality=50,duration=0)
cap.release()
return gif_path

if __name__ == "__main__":
main()
```

to Run this locally `streamlit Run app.py`
1. Go
2. ffmpeg

## Output

Expand Down
166 changes: 166 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package main

import (
"encoding/base64"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)

const uploadPath = "./uploads/"
const gifOutputPath = "./gifs/"

func main() {
// Ensure directories exist
err := os.MkdirAll(uploadPath, os.ModePerm)
if err != nil {
log.Fatalf("Error creating upload directory: %v", err)
}
err = os.MkdirAll(gifOutputPath, os.ModePerm)
if err != nil {
log.Fatalf("Error creating gif directory: %v", err)
}

// Set up HTTP handlers
http.HandleFunc("/", uploadFormHandler)
http.HandleFunc("/upload", uploadFileHandler)

fmt.Println("Starting server on :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}

// uploadFormHandler renders a simple HTML form to upload a video
func uploadFormHandler(w http.ResponseWriter, r *http.Request) {
html := `
<html>
<head>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAACgCAYAAACLz2ctAABJ30lEQVR4AezSAQ2AMBDAwMrBv8HHBdvYNTkHbVXS1NMUy9CAATEgGBADggExIBgQA4IBMSAYEAOCATEgGBADggExIBgQA4IBMSAYEAOCATEgBBrRLMLuMMGYVmJesHFsWkAAAAASUVORK5CYII=">
<title>Video to GIF Converter</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-900 text-white flex items-center justify-center min-h-screen">
<div class="p-6 bg-gray-800 bg-opacity-60 rounded-lg shadow-lg text-center">
<h1 class="text-4xl font-bold mb-4">Upload Video File</h1>
<form enctype="multipart/form-data" action="/upload" method="post" class="space-y-4">
<input class="block w-full text-white bg-gray-700 rounded-md p-2" type="file" name="video" accept="video/*">
<br>
<input class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md cursor-pointer" type="submit" value="Convert to GIF">
</form>
</div>
</body>
</html>`
w.Write([]byte(html))
}

// uploadFileHandler handles the video file upload, converts to GIF, and returns the result as a base64 encoded image
func uploadFileHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
return
}

// Parse the multipart form
err := r.ParseMultipartForm(10 << 20) // Limit to 10MB files
if err != nil {
http.Error(w, "Could not parse form", http.StatusInternalServerError)
return
}

// Retrieve the uploaded file
file, header, err := r.FormFile("video")
if err != nil {
http.Error(w, "Could not get uploaded file", http.StatusBadRequest)
return
}
defer file.Close()

// Save the uploaded file to the server
videoFileName := filepath.Join(uploadPath, header.Filename)
outFile, err := os.Create(videoFileName)
if err != nil {
http.Error(w, "Could not save uploaded file", http.StatusInternalServerError)
return
}
defer outFile.Close()

_, err = io.Copy(outFile, file)
if err != nil {
http.Error(w, "Failed to save file", http.StatusInternalServerError)
return
}

// Convert the video to GIF
gifFileName := getOutputGIFPath(videoFileName)
err = convertVideoToGIF(videoFileName, gifFileName)
if err != nil {
http.Error(w, "Failed to convert video to GIF", http.StatusInternalServerError)
return
}

// Read the generated GIF and encode it as base64
gifData, err := os.ReadFile(gifFileName)
if err != nil {
http.Error(w, "Failed to read GIF file", http.StatusInternalServerError)
return
}
base64GIF := base64.StdEncoding.EncodeToString(gifData)

// Serve HTML with embedded base64 image and a back button
html := fmt.Sprintf(`
<html>
<head>
<title>GIF Result</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-900 text-white flex flex-col items-center justify-center min-h-screen">
<h1 class="text-4xl font-bold mb-4">GIF Result</h1>
<img src="data:image/gif;base64,%s" alt="Generated GIF" class="border-4 border-blue-600 rounded-md mb-4">
<a href="data:image/gif;base64,%s" download="output.gif" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md cursor-pointer mb-4">Download GIF</a>
<a href="/" class="px-4 py-2 bg-gray-600 hover:bg-gray-700 rounded-md cursor-pointer">Back to Upload</a>
</body>
</html>`, base64GIF, base64GIF)

w.Write([]byte(html))

// Schedule cleanup to delete both the video and GIF files after serving
time.AfterFunc(10*time.Second, func() {
os.Remove(videoFileName) // Remove video
os.Remove(gifFileName) // Remove GIF
})
}

// convertVideoToGIF converts a video file to a GIF using ffmpeg
func convertVideoToGIF(inputVideo, outputGIF string) error {
// Check if input file exists
if _, err := os.Stat(inputVideo); os.IsNotExist(err) {
return fmt.Errorf("input file does not exist")
}

// Construct the ffmpeg command
cmd := exec.Command("ffmpeg", "-i", inputVideo, "-vf", "fps=10,scale=320:-1:flags=lanczos", "-t", "10", outputGIF)

// Set the output destination
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

// Run the command
err := cmd.Run()
if err != nil {
return fmt.Errorf("failed to convert video to GIF: %v", err)
}

return nil
}

// getOutputGIFPath generates a valid output GIF file path
func getOutputGIFPath(inputVideo string) string {
ext := filepath.Ext(inputVideo)
outputGIF := strings.TrimSuffix(filepath.Base(inputVideo), ext) + ".gif"
return filepath.Join(gifOutputPath, outputGIF)
}
Loading

0 comments on commit 74f7ef1

Please sign in to comment.