github.com/cloudreve/Cloudreve/v3@v3.0.0-20240224133659-3edb00a6484c/pkg/thumb/libreoffice.go (about)

     1  package thumb
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	model "github.com/cloudreve/Cloudreve/v3/models"
     8  	"github.com/cloudreve/Cloudreve/v3/pkg/util"
     9  	"github.com/gofrs/uuid"
    10  	"io"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"strings"
    15  )
    16  
    17  func init() {
    18  	RegisterGenerator(&LibreOfficeGenerator{})
    19  }
    20  
    21  type LibreOfficeGenerator struct {
    22  	exts        []string
    23  	lastRawExts string
    24  }
    25  
    26  func (l *LibreOfficeGenerator) Generate(ctx context.Context, file io.Reader, src string, name string, options map[string]string) (*Result, error) {
    27  	sofficeOpts := model.GetSettingByNames("thumb_libreoffice_path", "thumb_libreoffice_exts", "thumb_encode_method", "temp_path")
    28  
    29  	if l.lastRawExts != sofficeOpts["thumb_libreoffice_exts"] {
    30  		l.exts = strings.Split(sofficeOpts["thumb_libreoffice_exts"], ",")
    31  	}
    32  
    33  	if !util.IsInExtensionList(l.exts, name) {
    34  		return nil, fmt.Errorf("unsupported document format: %w", ErrPassThrough)
    35  	}
    36  
    37  	tempOutputPath := filepath.Join(
    38  		util.RelativePath(sofficeOpts["temp_path"]),
    39  		"thumb",
    40  		fmt.Sprintf("soffice_%s", uuid.Must(uuid.NewV4()).String()),
    41  	)
    42  
    43  	tempInputPath := src
    44  	if tempInputPath == "" {
    45  		// If not local policy files, download to temp folder
    46  		tempInputPath = filepath.Join(
    47  			util.RelativePath(sofficeOpts["temp_path"]),
    48  			"thumb",
    49  			fmt.Sprintf("soffice_%s%s", uuid.Must(uuid.NewV4()).String(), filepath.Ext(name)),
    50  		)
    51  
    52  		// Due to limitations of ffmpeg, we need to write the input file to disk first
    53  		tempInputFile, err := util.CreatNestedFile(tempInputPath)
    54  		if err != nil {
    55  			return nil, fmt.Errorf("failed to create temp file: %w", err)
    56  		}
    57  
    58  		defer os.Remove(tempInputPath)
    59  		defer tempInputFile.Close()
    60  
    61  		if _, err = io.Copy(tempInputFile, file); err != nil {
    62  			return nil, fmt.Errorf("failed to write input file: %w", err)
    63  		}
    64  
    65  		tempInputFile.Close()
    66  	}
    67  
    68  	// Convert the document to an image
    69  	cmd := exec.CommandContext(ctx, sofficeOpts["thumb_libreoffice_path"], "--headless",
    70  		"-nologo", "--nofirststartwizard", "--invisible", "--norestore", "--convert-to",
    71  		sofficeOpts["thumb_encode_method"], "--outdir", tempOutputPath, tempInputPath)
    72  
    73  	// Redirect IO
    74  	var stdErr bytes.Buffer
    75  	cmd.Stdin = file
    76  	cmd.Stderr = &stdErr
    77  
    78  	if err := cmd.Run(); err != nil {
    79  		util.Log().Warning("Failed to invoke LibreOffice: %s", stdErr.String())
    80  		return nil, fmt.Errorf("failed to invoke LibreOffice: %w", err)
    81  	}
    82  
    83  	return &Result{
    84  		Path: filepath.Join(
    85  			tempOutputPath,
    86  			strings.TrimSuffix(filepath.Base(tempInputPath), filepath.Ext(tempInputPath))+"."+sofficeOpts["thumb_encode_method"],
    87  		),
    88  		Continue: true,
    89  		Cleanup:  []func(){func() { _ = os.RemoveAll(tempOutputPath) }},
    90  	}, nil
    91  }
    92  
    93  func (l *LibreOfficeGenerator) Priority() int {
    94  	return 50
    95  }
    96  
    97  func (l *LibreOfficeGenerator) EnableFlag() string {
    98  	return "thumb_libreoffice_enabled"
    99  }