mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 08:44:04 +01:00
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
97
internal/ai/vision/api_client.go
Normal file
97
internal/ai/vision/api_client.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package vision
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/api/download"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/media"
|
||||
"github.com/photoprism/photoprism/pkg/media/http/header"
|
||||
"github.com/photoprism/photoprism/pkg/media/http/scheme"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
// NewApiRequest returns a new Vision API request with the specified file payload and scheme.
|
||||
func NewApiRequest(images Files, fileScheme string) (*ApiRequest, error) {
|
||||
imageUrls := make(Files, len(images))
|
||||
|
||||
if fileScheme == scheme.Https && !strings.HasPrefix(DownloadUrl, "https://") {
|
||||
log.Tracef("vision: file request scheme changed from https to data because https is not configured")
|
||||
fileScheme = scheme.Data
|
||||
}
|
||||
|
||||
for i := range images {
|
||||
switch fileScheme {
|
||||
case scheme.Https:
|
||||
if id, err := download.Register(images[i]); err != nil {
|
||||
return nil, fmt.Errorf("%s (create download url)", err)
|
||||
} else {
|
||||
imageUrls[i] = fmt.Sprintf("%s/%s", DownloadUrl, id)
|
||||
}
|
||||
case scheme.Data:
|
||||
if file, err := os.Open(images[i]); err != nil {
|
||||
return nil, fmt.Errorf("%s (create data url)", err)
|
||||
} else {
|
||||
imageUrls[i] = media.DataUrl(file)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid file scheme %s", clean.Log(fileScheme))
|
||||
}
|
||||
}
|
||||
|
||||
return &ApiRequest{
|
||||
Id: rnd.UUID(),
|
||||
Model: "",
|
||||
Images: imageUrls,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PerformApiRequest performs a Vision API request and returns the result.
|
||||
func PerformApiRequest(apiRequest *ApiRequest, uri, method, key string) (apiResponse *ApiResponse, err error) {
|
||||
if apiRequest == nil {
|
||||
return apiResponse, errors.New("api request is nil")
|
||||
}
|
||||
|
||||
data, jsonErr := apiRequest.MarshalJSON()
|
||||
|
||||
if jsonErr != nil {
|
||||
return apiResponse, jsonErr
|
||||
}
|
||||
|
||||
// Create HTTP client and authenticated service API request.
|
||||
client := http.Client{Timeout: ServiceTimeout}
|
||||
req, reqErr := http.NewRequest(method, uri, bytes.NewReader(data))
|
||||
|
||||
if key != "" {
|
||||
header.SetAuthorization(req, key)
|
||||
}
|
||||
|
||||
if reqErr != nil {
|
||||
return apiResponse, reqErr
|
||||
}
|
||||
|
||||
// Perform API request.
|
||||
clientResp, clientErr := client.Do(req)
|
||||
|
||||
if clientErr != nil {
|
||||
return apiResponse, clientErr
|
||||
}
|
||||
|
||||
apiResponse = &ApiResponse{}
|
||||
|
||||
// Unmarshal response and add labels, if returned.
|
||||
if apiJson, apiErr := io.ReadAll(clientResp.Body); apiErr != nil {
|
||||
return apiResponse, apiErr
|
||||
} else if apiErr = json.Unmarshal(apiJson, apiResponse); apiErr != nil {
|
||||
return apiResponse, apiErr
|
||||
}
|
||||
|
||||
return apiResponse, nil
|
||||
}
|
||||
@@ -9,13 +9,13 @@ import (
|
||||
"github.com/photoprism/photoprism/pkg/media/http/scheme"
|
||||
)
|
||||
|
||||
func TestNewClientRequest(t *testing.T) {
|
||||
func TestNewApiRequest(t *testing.T) {
|
||||
var assetsPath = fs.Abs("../../../assets")
|
||||
var examplesPath = assetsPath + "/examples"
|
||||
|
||||
t.Run("Data", func(t *testing.T) {
|
||||
thumbnails := Files{examplesPath + "/chameleon_lime.jpg"}
|
||||
result, err := NewClientRequest(thumbnails, scheme.Data)
|
||||
result, err := NewApiRequest(thumbnails, scheme.Data)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
@@ -28,10 +28,9 @@ func TestNewClientRequest(t *testing.T) {
|
||||
// t.Logf("json: %s", json)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Https", func(t *testing.T) {
|
||||
thumbnails := Files{examplesPath + "/chameleon_lime.jpg"}
|
||||
result, err := NewClientRequest(thumbnails, scheme.Https)
|
||||
result, err := NewApiRequest(thumbnails, scheme.Https)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
@@ -2,14 +2,7 @@ package vision
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/api/download"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/media"
|
||||
"github.com/photoprism/photoprism/pkg/media/http/scheme"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
@@ -22,41 +15,6 @@ type ApiRequest struct {
|
||||
Images Files `form:"images" yaml:"Images,omitempty" json:"images,omitempty"`
|
||||
}
|
||||
|
||||
// NewClientRequest returns a new Vision API request with the specified file payload and scheme.
|
||||
func NewClientRequest(images Files, fileScheme string) (*ApiRequest, error) {
|
||||
imageUrls := make(Files, len(images))
|
||||
|
||||
if fileScheme == scheme.Https && !strings.HasPrefix(DownloadUrl, "https://") {
|
||||
log.Tracef("vision: file request scheme changed from https to data because https is not configured")
|
||||
fileScheme = scheme.Data
|
||||
}
|
||||
|
||||
for i := range images {
|
||||
switch fileScheme {
|
||||
case scheme.Https:
|
||||
if id, err := download.Register(images[i]); err != nil {
|
||||
return nil, fmt.Errorf("%s (register download)", err)
|
||||
} else {
|
||||
imageUrls[i] = fmt.Sprintf("%s/%s", DownloadUrl, id)
|
||||
}
|
||||
case scheme.Data:
|
||||
if file, err := os.Open(images[i]); err != nil {
|
||||
return nil, fmt.Errorf("%s (create data url)", err)
|
||||
} else {
|
||||
imageUrls[i] = media.DataUrl(file)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid file scheme %s", clean.Log(fileScheme))
|
||||
}
|
||||
}
|
||||
|
||||
return &ApiRequest{
|
||||
Id: rnd.UUID(),
|
||||
Model: "",
|
||||
Images: imageUrls,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetId returns the request ID string and generates a random ID if none was set.
|
||||
func (r *ApiRequest) GetId() string {
|
||||
if r.Id == "" {
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package vision
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/media/http/header"
|
||||
)
|
||||
|
||||
// PerformApiRequest performs a Vision API request and returns the result.
|
||||
func PerformApiRequest(apiRequest *ApiRequest, uri, method, key string) (apiResponse *ApiResponse, err error) {
|
||||
if apiRequest == nil {
|
||||
return apiResponse, errors.New("api request is nil")
|
||||
}
|
||||
|
||||
data, jsonErr := apiRequest.MarshalJSON()
|
||||
|
||||
if jsonErr != nil {
|
||||
return apiResponse, jsonErr
|
||||
}
|
||||
|
||||
// Create HTTP client and authenticated service API request.
|
||||
client := http.Client{Timeout: ServiceTimeout}
|
||||
req, reqErr := http.NewRequest(method, uri, bytes.NewReader(data))
|
||||
|
||||
if key != "" {
|
||||
header.SetAuthorization(req, key)
|
||||
}
|
||||
|
||||
if reqErr != nil {
|
||||
return apiResponse, reqErr
|
||||
}
|
||||
|
||||
// Perform API request.
|
||||
clientResp, clientErr := client.Do(req)
|
||||
|
||||
if clientErr != nil {
|
||||
return apiResponse, clientErr
|
||||
}
|
||||
|
||||
apiResponse = &ApiResponse{}
|
||||
|
||||
// Unmarshal response and add labels, if returned.
|
||||
if apiJson, apiErr := io.ReadAll(clientResp.Body); apiErr != nil {
|
||||
return apiResponse, apiErr
|
||||
} else if apiErr = json.Unmarshal(apiJson, apiResponse); apiErr != nil {
|
||||
return apiResponse, apiErr
|
||||
}
|
||||
|
||||
return apiResponse, nil
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func Faces(fileName string, minSize int, cacheCrop bool, expected int) (result f
|
||||
}
|
||||
}
|
||||
|
||||
apiRequest, apiRequestErr := NewClientRequest(faceCrops, scheme.Data)
|
||||
apiRequest, apiRequestErr := NewApiRequest(faceCrops, scheme.Data)
|
||||
|
||||
if apiRequestErr != nil {
|
||||
return result, apiRequestErr
|
||||
|
||||
@@ -24,7 +24,7 @@ func Labels(images Files, src media.Src) (result classify.Labels, err error) {
|
||||
} else if model := Config.Model(ModelTypeLabels); model != nil {
|
||||
// Use remote service API if a server endpoint has been configured.
|
||||
if uri, method := model.Endpoint(); uri != "" && method != "" {
|
||||
apiRequest, apiRequestErr := NewClientRequest(images, scheme.Data)
|
||||
apiRequest, apiRequestErr := NewApiRequest(images, scheme.Data)
|
||||
|
||||
if apiRequestErr != nil {
|
||||
return result, apiRequestErr
|
||||
|
||||
@@ -25,7 +25,7 @@ func Nsfw(images Files, src media.Src) (result []nsfw.Result, err error) {
|
||||
} else if model := Config.Model(ModelTypeNsfw); model != nil {
|
||||
// Use remote service API if a server endpoint has been configured.
|
||||
if uri, method := model.Endpoint(); uri != "" && method != "" {
|
||||
apiRequest, apiRequestErr := NewClientRequest(images, scheme.Data)
|
||||
apiRequest, apiRequestErr := NewApiRequest(images, scheme.Data)
|
||||
|
||||
if apiRequestErr != nil {
|
||||
return result, apiRequestErr
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestPostVisionFaceEmbeddings(t *testing.T) {
|
||||
fs.Abs("./testdata/face_160x160.jpg"),
|
||||
}
|
||||
|
||||
req, err := vision.NewClientRequest(files, scheme.Data)
|
||||
req, err := vision.NewApiRequest(files, scheme.Data)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -65,7 +65,7 @@ func TestPostVisionFaceEmbeddings(t *testing.T) {
|
||||
fs.Abs("./testdata/london_160x160.jpg"),
|
||||
}
|
||||
|
||||
req, err := vision.NewClientRequest(files, scheme.Data)
|
||||
req, err := vision.NewApiRequest(files, scheme.Data)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -101,7 +101,7 @@ func TestPostVisionFaceEmbeddings(t *testing.T) {
|
||||
fs.Abs("./testdata/face_320x320.jpg"),
|
||||
}
|
||||
|
||||
req, err := vision.NewClientRequest(files, scheme.Data)
|
||||
req, err := vision.NewApiRequest(files, scheme.Data)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -142,7 +142,7 @@ func TestPostVisionFaceEmbeddings(t *testing.T) {
|
||||
|
||||
files := vision.Files{}
|
||||
|
||||
req, err := vision.NewClientRequest(files, scheme.Data)
|
||||
req, err := vision.NewApiRequest(files, scheme.Data)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestPostVisionLabels(t *testing.T) {
|
||||
fs.Abs("./testdata/cat_224x224.jpg"),
|
||||
}
|
||||
|
||||
req, err := vision.NewClientRequest(files, scheme.Data)
|
||||
req, err := vision.NewApiRequest(files, scheme.Data)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -59,7 +59,7 @@ func TestPostVisionLabels(t *testing.T) {
|
||||
fs.Abs("./testdata/green_224x224.jpg"),
|
||||
}
|
||||
|
||||
req, err := vision.NewClientRequest(files, scheme.Data)
|
||||
req, err := vision.NewApiRequest(files, scheme.Data)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -93,7 +93,7 @@ func TestPostVisionLabels(t *testing.T) {
|
||||
|
||||
files := vision.Files{}
|
||||
|
||||
req, err := vision.NewClientRequest(files, scheme.Data)
|
||||
req, err := vision.NewApiRequest(files, scheme.Data)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestPostVisionNsfw(t *testing.T) {
|
||||
fs.Abs("./testdata/nsfw_224x224.jpg"),
|
||||
}
|
||||
|
||||
req, err := vision.NewClientRequest(files, scheme.Data)
|
||||
req, err := vision.NewApiRequest(files, scheme.Data)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -74,7 +74,7 @@ func TestPostVisionNsfw(t *testing.T) {
|
||||
fs.Abs("./testdata/green_224x224.jpg"),
|
||||
}
|
||||
|
||||
req, err := vision.NewClientRequest(files, scheme.Data)
|
||||
req, err := vision.NewApiRequest(files, scheme.Data)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -108,7 +108,7 @@ func TestPostVisionNsfw(t *testing.T) {
|
||||
|
||||
files := vision.Files{}
|
||||
|
||||
req, err := vision.NewClientRequest(files, scheme.Data)
|
||||
req, err := vision.NewApiRequest(files, scheme.Data)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
Reference in New Issue
Block a user