github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/documents/compress.go (about)

     1  package documents
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"image/jpeg"
     7  	"mime"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/benoitkugler/goACVE/server/core/apiserver"
    14  )
    15  
    16  // compressionRate ajuste le taux de compression (le taux de qualite est 100 - taux de compression)
    17  // Peut renvoyer `0` pour ne pas compresser du tout.
    18  func compressionRate(size int) int {
    19  	const minSize, mediumSize, maxSize = 200000, 1000000, apiserver.DOCUMENT_MAX_SIZE
    20  	if size < minSize {
    21  		return 0
    22  	}
    23  
    24  	if size < mediumSize {
    25  		const minRate, maxRate = 20, 40
    26  		return affine(minRate, maxRate, minSize, mediumSize, size)
    27  	}
    28  	if size > maxSize {
    29  		size = maxSize
    30  	}
    31  	const minRate, maxRate = 50, 80 // document de plus de 1M
    32  	return affine(minRate, maxRate, minSize, mediumSize, size)
    33  }
    34  
    35  // renvoie un taux linéaire en `minRate` et `maxRate`
    36  // quand `size` est entre `minSize` et `maxSize`
    37  func affine(minRate, maxRate, minSize, maxSize, size int) int {
    38  	coeff := float64(size-minSize) / float64(maxSize-minSize)
    39  	return int(float64(maxRate-minRate)*coeff) + minRate
    40  }
    41  
    42  // renvoie une version compressée (éventuellement identique)
    43  // l'extension du fichier peut changer (pdf -> jpg)
    44  func compressDocument(filename string, content []byte) (string, []byte, error) {
    45  	ext := filepath.Ext(filename)
    46  	var err error
    47  	switch mime.TypeByExtension(ext) {
    48  	case "image/jpeg":
    49  		content, err = CompressJPEG(content)
    50  	case "application/pdf":
    51  		var contentJpg []byte
    52  		contentJpg, err = CompressPdf(content)
    53  		if len(contentJpg) < len(content) { // est-ce rentable ?
    54  			content = contentJpg
    55  			filename = strings.TrimSuffix(filename, ext) + ".JPG"
    56  		}
    57  	}
    58  	return filename, content, err
    59  }
    60  
    61  func CompressJPEG(content []byte) ([]byte, error) {
    62  	quality := 100 - compressionRate(len(content))
    63  	if quality == 100 { // pas de compression
    64  		return content, nil
    65  	}
    66  
    67  	img, err := jpeg.Decode(bytes.NewReader(content))
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	var buf bytes.Buffer
    73  	err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality})
    74  
    75  	return buf.Bytes(), err
    76  }
    77  
    78  // fileContent doit être un pdf
    79  func countPdfPages(fileContent []byte) (int, error) {
    80  	var out bytes.Buffer
    81  	cmdString := "pdftk - dump_data | awk '/NumberOfPages/{print $2}'"
    82  	cmd := exec.Command("bash", "-c", cmdString)
    83  	cmd.Stdin = bytes.NewReader(fileContent)
    84  	cmd.Stdout = &out
    85  	if err := cmd.Run(); err != nil {
    86  		return 0, fmt.Errorf("Impossible traiter le document PDF : %s", err)
    87  	}
    88  	nb, err := strconv.Atoi(strings.TrimSpace(out.String())) // le résulat contient un saut de ligne
    89  	if err != nil {
    90  		return 0, fmt.Errorf("Impossible traiter le document PDF : %s", err)
    91  	}
    92  	return nb, nil
    93  }
    94  
    95  // essaie de convertir en jpg, de compresser
    96  // le caller doit vérifier si la conversion est intéressante
    97  func CompressPdf(fileContent []byte) ([]byte, error) {
    98  	nb, err := countPdfPages(fileContent)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	if nb > 1 { // on ne peut pas convertir en jpg
   103  		return fileContent, nil
   104  	}
   105  
   106  	// use Ghoscript
   107  	var asJPEG bytes.Buffer
   108  	cmd := exec.Command("gs", "-dBATCH", "-dNOPAUSE", "-sDEVICE=jpeg", "-dFirstPage=1", "-dLastPage=1", "-r200", "-dUseCropBox", "-sstdout=%stderr", "-sOutputFile=-", "-")
   109  	cmd.Stdin = bytes.NewReader(fileContent)
   110  	cmd.Stdout = &asJPEG
   111  	if err := cmd.Run(); err != nil {
   112  		return nil, fmt.Errorf("Impossible de convertir le PDF : %s", err)
   113  	}
   114  
   115  	compressedJPG, err := CompressJPEG(asJPEG.Bytes())
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	return compressedJPG, nil
   120  }