Fix: perform two-step xlsx->pdf->jpg conversion to preserve gridlines for thumbnails
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m4s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m4s
This commit is contained in:
parent
2e463a3104
commit
130aa1b806
1 changed files with 105 additions and 66 deletions
|
|
@ -154,81 +154,120 @@ func generateOfficeThumbnail(filePath, destPath string, cfg *config.Config) erro
|
||||||
ext = "docx"
|
ext = "docx"
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := map[string]interface{}{
|
callConvert := func(payload map[string]interface{}) (string, error) {
|
||||||
"async": false,
|
payloadBytes, err := json.Marshal(payload)
|
||||||
"filetype": ext,
|
if err != nil {
|
||||||
"key": fmt.Sprintf("thumb_%d", time.Now().UnixNano()),
|
return "", err
|
||||||
"outputtype": "jpg",
|
|
||||||
"title": "preview.jpg",
|
|
||||||
"url": fileUrl,
|
|
||||||
"thumbnail": map[string]interface{}{
|
|
||||||
"aspect": 1,
|
|
||||||
"first": true,
|
|
||||||
"width": 1024,
|
|
||||||
"height": 1024,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if ext == "xlsx" {
|
|
||||||
payload["spreadsheetLayout"] = map[string]interface{}{
|
|
||||||
"ignorePrintArea": true,
|
|
||||||
"printGridlines": true,
|
|
||||||
"printHeadings": true,
|
|
||||||
"fitToWidth": 1,
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
payloadBytes, err := json.Marshal(payload)
|
req, err := http.NewRequest("POST", "https://office.elijahkuntz.com/ConvertService.ashx", bytes.NewBuffer(payloadBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", "https://office.elijahkuntz.com/ConvertService.ashx", bytes.NewBuffer(payloadBytes))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Header.Set("Accept", "application/json")
|
|
||||||
|
|
||||||
// Sign the payload with the OnlyOffice JWT
|
|
||||||
if cfg.OnlyOfficeJWT != "" {
|
|
||||||
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(payload))
|
|
||||||
signedToken, err := jwtToken.SignedString([]byte(cfg.OnlyOfficeJWT))
|
|
||||||
if err == nil {
|
|
||||||
req.Header.Set("Authorization", "Bearer "+signedToken)
|
|
||||||
}
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
|
if cfg.OnlyOfficeJWT != "" {
|
||||||
|
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(payload))
|
||||||
|
signedToken, err := jwtToken.SignedString([]byte(cfg.OnlyOfficeJWT))
|
||||||
|
if err == nil {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+signedToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 30 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("conversion request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", fmt.Errorf("conversion request returned status: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result struct {
|
||||||
|
Error *int `json:"error,omitempty"`
|
||||||
|
FileUrl string `json:"fileUrl,omitempty"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to decode conversion response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Error != nil && *result.Error != 0 {
|
||||||
|
return "", fmt.Errorf("conversion service returned error code: %d", *result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.FileUrl == "" {
|
||||||
|
return "", fmt.Errorf("no fileUrl in conversion response")
|
||||||
|
}
|
||||||
|
return result.FileUrl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{Timeout: 30 * time.Second}
|
var finalImgUrl string
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("conversion request failed: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if ext == "xlsx" || ext == "xls" {
|
||||||
return fmt.Errorf("conversion request returned status: %d", resp.StatusCode)
|
pdfPayload := map[string]interface{}{
|
||||||
}
|
"async": false,
|
||||||
|
"filetype": ext,
|
||||||
|
"key": fmt.Sprintf("thumb_pdf_%d", time.Now().UnixNano()),
|
||||||
|
"outputtype": "pdf",
|
||||||
|
"title": "preview.pdf",
|
||||||
|
"url": fileUrl,
|
||||||
|
"spreadsheetLayout": map[string]interface{}{
|
||||||
|
"ignorePrintArea": true,
|
||||||
|
"printGridlines": true,
|
||||||
|
"printHeadings": true,
|
||||||
|
"fitToWidth": 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pdfUrl, err := callConvert(pdfPayload)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to convert xlsx to pdf: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
var result struct {
|
jpgPayload := map[string]interface{}{
|
||||||
Error *int `json:"error,omitempty"`
|
"async": false,
|
||||||
FileUrl string `json:"fileUrl,omitempty"`
|
"filetype": "pdf",
|
||||||
}
|
"key": fmt.Sprintf("thumb_jpg_%d", time.Now().UnixNano()),
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
"outputtype": "jpg",
|
||||||
return fmt.Errorf("failed to decode conversion response: %w", err)
|
"title": "preview.jpg",
|
||||||
}
|
"url": pdfUrl,
|
||||||
|
"thumbnail": map[string]interface{}{
|
||||||
// Wait, the API might return error 0 for success
|
"aspect": 1,
|
||||||
if result.Error != nil && *result.Error != 0 {
|
"first": true,
|
||||||
return fmt.Errorf("conversion service returned error code: %d", *result.Error)
|
"width": 1024,
|
||||||
}
|
"height": 1024,
|
||||||
|
},
|
||||||
if result.FileUrl == "" {
|
}
|
||||||
return fmt.Errorf("no fileUrl in conversion response")
|
finalImgUrl, err = callConvert(jpgPayload)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to convert pdf to jpg: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"async": false,
|
||||||
|
"filetype": ext,
|
||||||
|
"key": fmt.Sprintf("thumb_%d", time.Now().UnixNano()),
|
||||||
|
"outputtype": "jpg",
|
||||||
|
"title": "preview.jpg",
|
||||||
|
"url": fileUrl,
|
||||||
|
"thumbnail": map[string]interface{}{
|
||||||
|
"aspect": 1,
|
||||||
|
"first": true,
|
||||||
|
"width": 1024,
|
||||||
|
"height": 1024,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
finalImgUrl, err = callConvert(payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download the resulting image
|
// Download the resulting image
|
||||||
imgResp, err := http.Get(result.FileUrl)
|
imgResp, err := http.Get(finalImgUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to download converted image: %w", err)
|
return fmt.Errorf("failed to download converted image: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in a new issue