mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -56,7 +56,7 @@ func (m *Model) File(imageUri string, confidenceThreshold int) (result Labels, e
|
||||
|
||||
var data []byte
|
||||
|
||||
if data, err = media.ReadUri(imageUri); err != nil {
|
||||
if data, err = media.ReadUrl(imageUri); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ func (m *Passcode) MarshalJSON() ([]byte, error) {
|
||||
UID: m.UID,
|
||||
Type: m.KeyType,
|
||||
Secret: m.Secret(),
|
||||
QRCode: media.Base64(m.Png(350)),
|
||||
QRCode: media.DataUrl(m.Png(350)),
|
||||
RecoveryCode: m.RecoveryCode,
|
||||
CreatedAt: m.CreatedAt,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
|
||||
@@ -1,30 +1,23 @@
|
||||
package media
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Base64 returns a data URL representing the binary buffer data.
|
||||
func Base64(buf *bytes.Buffer) string {
|
||||
encoded := base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||
|
||||
if encoded == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
var mimeType string
|
||||
|
||||
mime, err := mimetype.DetectReader(buf)
|
||||
|
||||
if err != nil {
|
||||
mimeType = "application/octet-stream"
|
||||
} else {
|
||||
mimeType = mime.String()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("data:%s;base64,%s", mimeType, encoded)
|
||||
// EncodeBase64 returns the base64 encoding of bin.
|
||||
func EncodeBase64(bin []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(bin)
|
||||
}
|
||||
|
||||
// ReadBase64 returns a new reader that decodes base64 and returns binary data.
|
||||
func ReadBase64(stream io.Reader) io.Reader {
|
||||
return base64.NewDecoder(base64.StdEncoding, stream)
|
||||
}
|
||||
|
||||
// DecodeBase64 returns the bytes represented by the base64 string s.
|
||||
// If the input is malformed, it returns the partially decoded data and
|
||||
// [CorruptInputError]. Newline characters (\r and \n) are ignored.
|
||||
func DecodeBase64(s string) ([]byte, error) {
|
||||
return base64.StdEncoding.DecodeString(s)
|
||||
}
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
package media
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const gopher = `iVBORw0KGgoAAAANSUhEUgAAAEsAAAA8CAAAAAALAhhPAAAFfUlEQVRYw62XeWwUVRzHf2+OPbo9d7tsWyiyaZti6eWGAhISoIGKECEKCAiJJkYTiUgTMYSIosYYBBIUIxoSPIINEBDi2VhwkQrVsj1ESgu9doHWdrul7ba73WNm3vOPtsseM9MdwvvrzTs+8/t95ze/33sI5BqiabU6m9En8oNjduLnAEDLUsQXFF8tQ5oxK3vmnNmDSMtrncks9Hhtt/qeWZapHb1ha3UqYSWVl2ZmpWgaXMXGohQAvmeop3bjTRtv6SgaK/Pb9/bFzUrYslbFAmHPp+3WhAYdr+7GN/YnpN46Opv55VDsJkoEpMrY/vO2BIYQ6LLvm0ThY3MzDzzeSJeeWNyTkgnIE5ePKsvKlcg/0T9QMzXalwXMlj54z4c0rh/mzEfr+FgWEz2w6uk8dkzFAgcARAgNp1ZYef8bH2AgvuStbc2/i6CiWGj98y2tw2l4FAXKkQBIf+exyRnteY83LfEwDQAYCoK+P6bxkZm/0966LxcAAILHB56kgD95PPxltuYcMtFTWw/FKkY/6Opf3GGd9ZF+Qp6mzJxzuRSractOmJrH1u8XTvWFHINNkLQLMR+XHXvfPPHw967raE1xxwtA36IMRfkAAG29/7mLuQcb2WOnsJReZGfpiHsSBX81cvMKywYZHhX5hFPtOqPGWZCXnhWGAu6lX91ElKXSalcLXu3UaOXVay57ZSe5f6Gpx7J2MXAsi7EqSp09b/MirKSyJfnfEEgeDjl8FgDAfvewP03zZ+AJ0m9aFRM8eEHBDRKjfcreDXnZdQuAxXpT2NRJ7xl3UkLBhuVGU16gZiGOgZmrSbRdqkILuL/yYoSXHHkl9KXgqNu3PB8oRg0geC5vFmLjad6mUyTKLmF3OtraWDIfACyXqmephaDABawfpi6tqqBZytfQMqOz6S09iWXhktrRaB8Xz4Yi/8gyABDm5NVe6qq/3VzPrcjELWrebVuyY2T7ar4zQyybUCtsQ5Es1FGaZVrRVQwAgHGW2ZCRZshI5bGQi7HesyE972pOSeMM0dSktlzxRdrlqb3Osa6CCS8IJoQQQgBAbTAa5l5epO34rJszibJI8rxLfGzcp1dRosutGeb2VDNgqYrwTiPNsLxXiPi3dz7LiS1WBRBDBOnqEjyy3aQb+/bLiJzz9dIkscVBBLxMfSEac7kO4Fpkngi0ruNBeSOal+u8jgOuqPz12nryMLCniEjtOOOmpt+KEIqsEdocJjYXwrh9OZqWJQyPCTo67LNS/TdxLAv6R5ZNK9npEjbYdT33gRo4o5oTqR34R+OmaSzDBWsAIPhuRcgyoteNi9gF0KzNYWVItPf2TLoXEg+7isNC7uJkgo1iQWOfRSP9NR11RtbZZ3OMG/VhL6jvx+J1m87+RCfJChAtEBQkSBX2PnSiihc/Twh3j0h7qdYQAoRVsRGmq7HU2QRbaxVGa1D6nIOqaIWRjyRZpHMQKWKpZM5feA+lzC4ZFultV8S6T0mzQGhQohi5I8iw+CsqBSxhFMuwyLgSwbghGb0AiIKkSDmGZVmJSiKihsiyOAUs70UkywooYP0bii9GdH4sfr1UNysd3fUyLLMQN+rsmo3grHl9VNJHbbwxoa47Vw5gupIqrZcjPh9R4Nye3nRDk199V+aetmvVtDRE8/+cbgAAgMIWGb3UA0MGLE9SCbWX670TDy1y98c3D27eppUjsZ6fql3jcd5rUe7+ZIlLNQny3Rd+E5Tct3WVhTM5RBCEdiEK0b6B+/ca2gYU393nFj/n1AygRQxPIUA043M42u85+z2SnssKrPl8Mx76NL3E6eXc3be7OD+H4WHbJkKI8AU8irbITQjZ+0hQcPEgId/Fn/pl9crKH02+5o2b9T/eMx7pKoskYgAAAABJRU5ErkJggg==`
|
||||
|
||||
// gopherPng creates an io.Reader by decoding the base64 encoded image data string in the gopher constant.
|
||||
func gopherPng() io.Reader { return base64.NewDecoder(base64.StdEncoding, strings.NewReader(gopher)) }
|
||||
|
||||
func TestBase64(t *testing.T) {
|
||||
t.Run("PNG", func(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
_, bufErr := buf.ReadFrom(gopherPng())
|
||||
assert.NoError(t, bufErr)
|
||||
assert.Equal(t, "data:image/png;base64,"+gopher, Base64(buf))
|
||||
t.Run("Gopher", func(t *testing.T) {
|
||||
data, err := DecodeBase64(gopher)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, gopher, EncodeBase64(data))
|
||||
})
|
||||
}
|
||||
|
||||
70
pkg/media/data_url.go
Normal file
70
pkg/media/data_url.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package media
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
)
|
||||
|
||||
// DataUrl returns a data URL representing the binary buffer data.
|
||||
func DataUrl(buf *bytes.Buffer) string {
|
||||
encoded := EncodeBase64(buf.Bytes())
|
||||
|
||||
if encoded == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
var mimeType string
|
||||
|
||||
mime, err := mimetype.DetectReader(buf)
|
||||
|
||||
if err != nil {
|
||||
mimeType = "application/octet-stream"
|
||||
} else {
|
||||
mimeType = mime.String()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("data:%s;base64,%s", mimeType, encoded)
|
||||
}
|
||||
|
||||
// ReadUrl reads binary data from a regular file path,
|
||||
// fetches its data from a remote http or https URL,
|
||||
// or decodes a base64 data URL as created by DataUrl.
|
||||
func ReadUrl(file string) (data []byte, err error) {
|
||||
u, err := url.Parse(file)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Also supports http, https, and data URLs instead of a file name for remote processing.
|
||||
if u.Scheme == "http" || u.Scheme == "https" {
|
||||
resp, httpErr := http.Get(file)
|
||||
|
||||
if httpErr != nil {
|
||||
return nil, httpErr
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if data, err = io.ReadAll(resp.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if u.Scheme == "data" {
|
||||
if _, binaryData, found := strings.Cut(u.Opaque, ";base64,"); !found || len(binaryData) == 0 {
|
||||
return nil, fmt.Errorf("invalid data URL")
|
||||
} else {
|
||||
return DecodeBase64(binaryData)
|
||||
}
|
||||
} else if data, err = os.ReadFile(file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, err
|
||||
}
|
||||
36
pkg/media/data_url_test.go
Normal file
36
pkg/media/data_url_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package media
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const gopher = `iVBORw0KGgoAAAANSUhEUgAAAEsAAAA8CAAAAAALAhhPAAAFfUlEQVRYw62XeWwUVRzHf2+OPbo9d7tsWyiyaZti6eWGAhISoIGKECEKCAiJJkYTiUgTMYSIosYYBBIUIxoSPIINEBDi2VhwkQrVsj1ESgu9doHWdrul7ba73WNm3vOPtsseM9MdwvvrzTs+8/t95ze/33sI5BqiabU6m9En8oNjduLnAEDLUsQXFF8tQ5oxK3vmnNmDSMtrncks9Hhtt/qeWZapHb1ha3UqYSWVl2ZmpWgaXMXGohQAvmeop3bjTRtv6SgaK/Pb9/bFzUrYslbFAmHPp+3WhAYdr+7GN/YnpN46Opv55VDsJkoEpMrY/vO2BIYQ6LLvm0ThY3MzDzzeSJeeWNyTkgnIE5ePKsvKlcg/0T9QMzXalwXMlj54z4c0rh/mzEfr+FgWEz2w6uk8dkzFAgcARAgNp1ZYef8bH2AgvuStbc2/i6CiWGj98y2tw2l4FAXKkQBIf+exyRnteY83LfEwDQAYCoK+P6bxkZm/0966LxcAAILHB56kgD95PPxltuYcMtFTWw/FKkY/6Opf3GGd9ZF+Qp6mzJxzuRSractOmJrH1u8XTvWFHINNkLQLMR+XHXvfPPHw967raE1xxwtA36IMRfkAAG29/7mLuQcb2WOnsJReZGfpiHsSBX81cvMKywYZHhX5hFPtOqPGWZCXnhWGAu6lX91ElKXSalcLXu3UaOXVay57ZSe5f6Gpx7J2MXAsi7EqSp09b/MirKSyJfnfEEgeDjl8FgDAfvewP03zZ+AJ0m9aFRM8eEHBDRKjfcreDXnZdQuAxXpT2NRJ7xl3UkLBhuVGU16gZiGOgZmrSbRdqkILuL/yYoSXHHkl9KXgqNu3PB8oRg0geC5vFmLjad6mUyTKLmF3OtraWDIfACyXqmephaDABawfpi6tqqBZytfQMqOz6S09iWXhktrRaB8Xz4Yi/8gyABDm5NVe6qq/3VzPrcjELWrebVuyY2T7ar4zQyybUCtsQ5Es1FGaZVrRVQwAgHGW2ZCRZshI5bGQi7HesyE972pOSeMM0dSktlzxRdrlqb3Osa6CCS8IJoQQQgBAbTAa5l5epO34rJszibJI8rxLfGzcp1dRosutGeb2VDNgqYrwTiPNsLxXiPi3dz7LiS1WBRBDBOnqEjyy3aQb+/bLiJzz9dIkscVBBLxMfSEac7kO4Fpkngi0ruNBeSOal+u8jgOuqPz12nryMLCniEjtOOOmpt+KEIqsEdocJjYXwrh9OZqWJQyPCTo67LNS/TdxLAv6R5ZNK9npEjbYdT33gRo4o5oTqR34R+OmaSzDBWsAIPhuRcgyoteNi9gF0KzNYWVItPf2TLoXEg+7isNC7uJkgo1iQWOfRSP9NR11RtbZZ3OMG/VhL6jvx+J1m87+RCfJChAtEBQkSBX2PnSiihc/Twh3j0h7qdYQAoRVsRGmq7HU2QRbaxVGa1D6nIOqaIWRjyRZpHMQKWKpZM5feA+lzC4ZFultV8S6T0mzQGhQohi5I8iw+CsqBSxhFMuwyLgSwbghGb0AiIKkSDmGZVmJSiKihsiyOAUs70UkywooYP0bii9GdH4sfr1UNysd3fUyLLMQN+rsmo3grHl9VNJHbbwxoa47Vw5gupIqrZcjPh9R4Nye3nRDk199V+aetmvVtDRE8/+cbgAAgMIWGb3UA0MGLE9SCbWX670TDy1y98c3D27eppUjsZ6fql3jcd5rUe7+ZIlLNQny3Rd+E5Tct3WVhTM5RBCEdiEK0b6B+/ca2gYU393nFj/n1AygRQxPIUA043M42u85+z2SnssKrPl8Mx76NL3E6eXc3be7OD+H4WHbJkKI8AU8irbITQjZ+0hQcPEgId/Fn/pl9crKH02+5o2b9T/eMx7pKoskYgAAAABJRU5ErkJggg==`
|
||||
|
||||
// gopherPng creates an io.Reader by decoding the base64 encoded image data string in the gopher constant.
|
||||
func gopherPng() io.Reader { return ReadBase64(strings.NewReader(gopher)) }
|
||||
|
||||
func TestDataUrl(t *testing.T) {
|
||||
t.Run("Gopher", func(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
_, bufErr := buf.ReadFrom(gopherPng())
|
||||
assert.NoError(t, bufErr)
|
||||
assert.Equal(t, "data:image/png;base64,"+gopher, DataUrl(buf))
|
||||
})
|
||||
}
|
||||
|
||||
func TestReadUrl(t *testing.T) {
|
||||
t.Run("Gopher", func(t *testing.T) {
|
||||
dataUrl := "data:image/png;base64," + gopher
|
||||
if data, err := ReadUrl(dataUrl); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
expected, _ := DecodeBase64(gopher)
|
||||
assert.Equal(t, expected, data)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package media
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ReadUri reads binary data from regular files, fetches data from remote http and
|
||||
// https URLs, or decodes base64 data URLs - depending on the type of URI you pass.
|
||||
func ReadUri(uri string) (data []byte, err error) {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Also supports http, https, and data URLs instead of a file name for remote processing.
|
||||
if u.Scheme == "http" || u.Scheme == "https" {
|
||||
resp, httpErr := http.Get(uri)
|
||||
|
||||
if httpErr != nil {
|
||||
return nil, httpErr
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if data, err = io.ReadAll(resp.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if u.Scheme == "data" {
|
||||
if _, binaryData, found := strings.Cut(u.Opaque, ";base64,"); !found || len(binaryData) == 0 {
|
||||
return nil, fmt.Errorf("invalid data URL")
|
||||
} else {
|
||||
return base64.StdEncoding.DecodeString(binaryData)
|
||||
}
|
||||
} else if data, err = os.ReadFile(uri); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, err
|
||||
}
|
||||
Reference in New Issue
Block a user