mirror of
https://github.com/JuLi0n21/thumbnailservice.git
synced 2026-04-20 00:10:07 +00:00
init
This commit is contained in:
210
server/main.go
Normal file
210
server/main.go
Normal file
@@ -0,0 +1,210 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"image"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
_ "image/gif"
|
||||
"image/jpeg"
|
||||
_ "image/jpeg"
|
||||
"image/png"
|
||||
_ "image/png"
|
||||
|
||||
pb "github.com/JuLi0n21/thumbnail_service/proto"
|
||||
"github.com/nfnt/resize"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Helper function to generate video thumbnails using ffmpeg
|
||||
func generateVideoThumbnail(inputPath, outputPath string, maxWidth, maxHeight int) error {
|
||||
cmd := exec.Command("ffmpeg", "-i", inputPath, "-vf", "thumbnail", "-frames:v", "1", outputPath)
|
||||
var stderr strings.Builder
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate video thumbnail using FFmpeg: %v. FFmpeg stderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
if maxWidth > 0 || maxHeight > 0 {
|
||||
return resizeImage(outputPath, outputPath, maxWidth, maxHeight)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper function to generate PDF thumbnails using poppler-utils (pdftoppm)
|
||||
func generatePdfThumbnail(inputPath, outputPath string, maxWidth, maxHeight int) error {
|
||||
// Command for Poppler-utils to generate a thumbnail from the first page of a PDF file
|
||||
cmd := exec.Command("pdftoppm", inputPath, outputPath, "-jpeg", "-f", "1", "-l", "1", "-scale-to", "200")
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate PDF thumbnail using Poppler-utils: %v", err)
|
||||
}
|
||||
|
||||
outputFileWithPage := fmt.Sprintf("%s-01.jpg", outputPath) // pdftoppm output file with page number suffix
|
||||
|
||||
// Rename the file
|
||||
err = os.Rename(outputFileWithPage, outputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to rename file: %v", err)
|
||||
}
|
||||
|
||||
// Resize the generated image if maxWidth or maxHeight is provided
|
||||
if maxWidth > 0 || maxHeight > 0 {
|
||||
return resizeImage(outputPath, outputPath, maxWidth, maxHeight)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resizeImage(inputPath, outputPath string, maxWidth, maxHeight int) error {
|
||||
// Open the input image file
|
||||
file, err := os.Open(inputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open image file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Decode the image
|
||||
img, imgType, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode image: %v", err)
|
||||
}
|
||||
|
||||
// Calculate the new dimensions, preserving the aspect ratio
|
||||
var newWidth, newHeight int
|
||||
if maxWidth > 0 && maxHeight > 0 {
|
||||
// Resize with both width and height limit
|
||||
newWidth = maxWidth
|
||||
newHeight = maxHeight
|
||||
} else if maxWidth > 0 {
|
||||
// Resize based on width
|
||||
newWidth = maxWidth
|
||||
newHeight = int(float64(img.Bounds().Dy()) * float64(maxWidth) / float64(img.Bounds().Dx()))
|
||||
} else if maxHeight > 0 {
|
||||
// Resize based on height
|
||||
newHeight = maxHeight
|
||||
newWidth = int(float64(img.Bounds().Dx()) * float64(maxHeight) / float64(img.Bounds().Dy()))
|
||||
} else {
|
||||
// No resizing needed
|
||||
newWidth = img.Bounds().Dx()
|
||||
newHeight = img.Bounds().Dy()
|
||||
}
|
||||
|
||||
// Resize the image using the calculated dimensions
|
||||
resizedImg := resize.Resize(uint(newWidth), uint(newHeight), img, resize.Lanczos3)
|
||||
|
||||
// Create the output file
|
||||
outFile, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output file: %v", err)
|
||||
}
|
||||
defer outFile.Close()
|
||||
switch imgType {
|
||||
case "jpeg":
|
||||
err = jpeg.Encode(outFile, resizedImg, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save resized image as jpeg: %v", err)
|
||||
}
|
||||
case "png":
|
||||
err = png.Encode(outFile, resizedImg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save resized image as png: %v", err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported image type: %v", imgType)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type server struct {
|
||||
pb.UnimplementedThumbnailServiceServer
|
||||
}
|
||||
|
||||
func (s *server) GenerateThumbnail(ctx context.Context, req *pb.ThumbnailRequest) (*pb.ThumbnailResponse, error) {
|
||||
// Create a temporary file to store the uploaded content
|
||||
tempFile, err := os.CreateTemp("", "upload-*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temporary file: %v", err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name()) // Cleanup temporary file
|
||||
|
||||
// Write the content from the request to the temporary file
|
||||
err = os.WriteFile(tempFile.Name(), req.FileContent, 0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to write content to file: %v", err)
|
||||
}
|
||||
|
||||
// Generate thumbnail and save it to disk based on file type
|
||||
var outputPath string
|
||||
|
||||
// Check file type using enum
|
||||
switch req.FileType {
|
||||
case pb.FileType_IMAGE:
|
||||
// Image file, use ImageMagick or other Go logic to create a thumbnail
|
||||
outputPath = "thumbnails/image-thumbnail.jpg"
|
||||
err = resizeImage(tempFile.Name(), outputPath, int(req.MaxWidth), int(req.MaxHeight))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case pb.FileType_VIDEO:
|
||||
// Video file, use FFmpeg to create a thumbnail
|
||||
outputPath = "thumbnails/video-thumbnail.jpg" // Video thumbnails are typically saved as JPG
|
||||
err = generateVideoThumbnail(tempFile.Name(), outputPath, int(req.MaxWidth), int(req.MaxHeight))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case pb.FileType_PDF:
|
||||
// PDF file, use Poppler-utils to create a thumbnail
|
||||
outputPath = "thumbnails/pdf-thumbnail.jpg" // PDF thumbnails are typically saved as JPG
|
||||
err = generatePdfThumbnail(tempFile.Name(), outputPath, int(req.MaxWidth), int(req.MaxHeight))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported file type: %v", req.FileType)
|
||||
}
|
||||
|
||||
// Read the generated thumbnail back into memory to send it as bytes
|
||||
thumbnailContent, err := os.ReadFile(outputPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read generated thumbnail: %v", err)
|
||||
}
|
||||
|
||||
// Return the response with the thumbnail bytes and output path
|
||||
return &pb.ThumbnailResponse{
|
||||
Message: "Thumbnail generated successfully",
|
||||
ThumbnailContent: thumbnailContent, // Send the thumbnail as bytes
|
||||
}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Set up a listener on port 50051
|
||||
listen, err := net.Listen("tcp", ":50051")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to listen: %v", err)
|
||||
}
|
||||
|
||||
// Create a gRPC server
|
||||
grpcServer := grpc.NewServer()
|
||||
|
||||
// Register the server
|
||||
pb.RegisterThumbnailServiceServer(grpcServer, &server{})
|
||||
|
||||
// Start serving requests
|
||||
log.Println("Server started on port 50051")
|
||||
if err := grpcServer.Serve(listen); err != nil {
|
||||
log.Fatalf("Failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user