diff --git a/client/main.go b/client/main.go index 0da5781..78cb780 100644 --- a/client/main.go +++ b/client/main.go @@ -8,6 +8,7 @@ import ( "path" "path/filepath" "strings" + "sync" "time" pb "thumbnailclient/proto" @@ -32,13 +33,20 @@ func main() { {pb.FileType_PDF, "testdata/pdf-sample.pdf"}, {pb.FileType_VIDEO, "testdata/video-sample.webm"}} + a := sync.WaitGroup{} + for _, f := range filePath { - newFunction(f.Path, f.Type, client) + a.Add(1) + go func() { + createPreview(f.Path, f.Type, client) + a.Done() + }() } + a.Wait() } -func newFunction(filePath string, ftype pb.FileType, client pb.ThumbnailServiceClient) { +func createPreview(filePath string, ftype pb.FileType, client pb.ThumbnailServiceClient) { fileContent, err := os.ReadFile(filePath) if err != nil { log.Fatalf("Error reading file: %v", err) diff --git a/server/go.mod b/server/go.mod index 90b6f5a..71667c3 100644 --- a/server/go.mod +++ b/server/go.mod @@ -3,6 +3,7 @@ module github.com/JuLi0n21/thumbnail_service go 1.24.1 require ( + github.com/google/uuid v1.6.0 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 google.golang.org/grpc v1.71.0 google.golang.org/protobuf v1.36.6 diff --git a/server/go.sum b/server/go.sum index 1402567..1d3172a 100644 --- a/server/go.sum +++ b/server/go.sum @@ -22,25 +22,15 @@ go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= diff --git a/server/main.go b/server/main.go index e545a14..23d5732 100644 --- a/server/main.go +++ b/server/main.go @@ -8,22 +8,22 @@ import ( "net" "os" "os/exec" + "path/filepath" "strings" + "time" _ "image/gif" "image/jpeg" - _ "image/jpeg" "image/png" - _ "image/png" pb "github.com/JuLi0n21/thumbnail_service/proto" + "github.com/google/uuid" "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) + cmd := exec.Command("ffmpeg", "-y", "-i", inputPath, "-vf", "thumbnail", "-frames:v", "1", outputPath) var stderr strings.Builder cmd.Stderr = &stderr @@ -39,24 +39,21 @@ func generateVideoThumbnail(inputPath, outputPath string, maxWidth, maxHeight in 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 + outputFileWithPage := fmt.Sprintf("%s-01.jpg", outputPath) - // 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) } @@ -65,43 +62,39 @@ func generatePdfThumbnail(inputPath, outputPath string, maxWidth, maxHeight int) } 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) @@ -130,79 +123,72 @@ type server struct { } func (s *server) GenerateThumbnail(ctx context.Context, req *pb.ThumbnailRequest) (*pb.ThumbnailResponse, error) { - // Create a temporary file to store the uploaded content + start := time.Now() + fmt.Println(start.Format("2006-01-02 15:04:05.000"), "Thumbnail request ", req.FileType, "H: ", req.MaxHeight, "W: ", req.MaxWidth) + 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 + defer os.Remove(tempFile.Name()) - // 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 + thumbnailName := fmt.Sprintf("thumbnail-%s.jpg", uuid.New().String()) + outputPath := filepath.Join("thumbnails", thumbnailName) + + if err := os.MkdirAll("thumbnails", 0755); err != nil { + return nil, fmt.Errorf("failed to create thumbnails directory: %v", err) + } - // 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 + if err != nil { + return nil, err + } + + defer func() { + if _, err := os.Stat(outputPath); err == nil { + os.Remove(outputPath) + } + }() + 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 + end := time.Since(time.Now()) + fmt.Println(time.Now().Format("2006-01-02 15:04:05.000"), "Finshed in: ", end, req.FileType, "H: ", req.MaxHeight, "W: ", req.MaxWidth) return &pb.ThumbnailResponse{ Message: "Thumbnail generated successfully", - ThumbnailContent: thumbnailContent, // Send the thumbnail as bytes + ThumbnailContent: thumbnailContent, }, 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) diff --git a/server/thumbnails/.gitkeep b/server/thumbnails/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/server/thumbnails/image-thumbnail.jpg b/server/thumbnails/image-thumbnail.jpg deleted file mode 100644 index b6b8740..0000000 Binary files a/server/thumbnails/image-thumbnail.jpg and /dev/null differ diff --git a/server/thumbnails/pdf-thumbnail.jpg b/server/thumbnails/pdf-thumbnail.jpg deleted file mode 100644 index 3ec27e7..0000000 Binary files a/server/thumbnails/pdf-thumbnail.jpg and /dev/null differ diff --git a/server/thumbnails/video-thumbnail.jpg b/server/thumbnails/video-thumbnail.jpg deleted file mode 100644 index c97a1c7..0000000 Binary files a/server/thumbnails/video-thumbnail.jpg and /dev/null differ