Commit 7e08c7c4 authored by Juergen Enge's avatar Juergen Enge
Browse files

resize with iiif implemented

parent c751c86e
Pipeline #13784 passed with stages
in 3 minutes and 43 seconds
......@@ -122,6 +122,10 @@ func main() {
log.Errorf("error initializing server: %v", err)
return
}
// add some helpful actions
srv.AddAction(memostream.NewImageActionIIIF(srv))
go func() {
if err := srv.ListenAndServe(config.CertPEM, config.KeyPEM); err != nil {
log.Errorf("server died: %v", err)
......
......@@ -46,7 +46,7 @@ folder = "C:/daten/go/src/gitlab.switch.ch/memoriav/memobase-2020/services/strea
connMaxTimeout = "4h"
# query has to return the fields uri, access and protocol. One parameter
#query = "SELECT uri, access, proto AS protocol, `status` FROM mediaserver.entities WHERE sig = ?"
query = "SELECT uri, access, proto AS protocol, `status`, mimetype FROM test.entities_metadata WHERE sig = ?"
query = "SELECT `uri`, `access`, `proto` AS protocol, `status`, `type`, `mimetype`, `width`, `height`, `duration` FROM test.entities_metadata WHERE sig = ?"
schema = "test"
[signatures]
......
package memostream
import "net/http"
// interface for a generic Action on a media object
type Action interface {
// media types which are allowed for this Action
GetType() []string
// execute the Action
Do(w http.ResponseWriter, req *http.Request, m *MediaEntry, action string, params ...string) (bool, error)
}
package memostream
import (
"fmt"
"github.com/goph/emperror"
"net/http"
"regexp"
"strconv"
"strings"
)
// image Action using IIIF server for image manipulation
type ImageActionIIIF struct {
ms *memoServer
}
func NewImageActionIIIF(ms *memoServer) *ImageActionIIIF {
iai := &ImageActionIIIF{ms: ms}
return iai
}
func (iai *ImageActionIIIF) GetType() []string { return []string{"image"} }
var regexpParamImageActionIIIF = []*regexp.Regexp{
regexp.MustCompile("^(size)([0-9]+)x([0-9]+)$"),
regexp.MustCompile("^(format)(jpeg|png)$"),
regexp.MustCompile("^(stretch)$"),
regexp.MustCompile("^(crop)$"),
}
func (iai *ImageActionIIIF) Do(w http.ResponseWriter, req *http.Request, me *MediaEntry, action string, params ...string) (bool, error) {
if action != "resize" {
return false, nil
}
ps := map[string][]string{}
// create array of parameters
for _, p := range params {
for _, rexp := range regexpParamImageActionIIIF {
res := rexp.FindStringSubmatch(strings.ToLower(p))
if res != nil {
ps[res[1]] = res[2:]
break
}
}
}
// calculate width and height from resize parameter
var width, height int64
var err error
resizeParams, ok := ps["size"]
if !ok {
// without resize we cannot do anything
return false, nil
}
width, err = strconv.ParseInt(resizeParams[0], 10, 64)
if err != nil {
return false, emperror.Wrapf(err, "invalid width %v in resize", resizeParams[0])
}
height, err = strconv.ParseInt(resizeParams[1], 10, 64)
if err != nil {
return false, emperror.Wrapf(err, "invalid height %v in resize", resizeParams[1])
}
var format string
if formatParams, ok := ps["format"]; ok {
format = strings.ToLower(formatParams[0])
}
var ext string
switch format {
case "png":
ext = "png"
default:
ext = "jpg"
}
sizePrefix := "!"
if _, ok := ps["stretch"]; ok {
sizePrefix = ""
}
resultName := fmt.Sprintf("%s_%vx%v.%s", me.Signature, width, height, ext)
w.Header().Set("Content-Disposition", fmt.Sprintf("filename=\"%s\"", resultName))
// build param string
var paramstring string
if _, ok := ps["crop"]; ok {
aspectOrig := float64(me.Width) / float64(me.Height)
aspectNew := float64(width) / float64(height)
var nX, nY, nW, nH int64
if aspectOrig > aspectNew {
nY = 0
nH = me.Height
nW = int64(float64(nH) * aspectNew)
nX = (me.Width - nW) / 2
} else {
nX = 0
nW = me.Width
nH = int64(float64(nW) / aspectNew)
nY = (me.Height - nH) / 2
}
paramstring = fmt.Sprintf("%v,%v,%v,%v/%s%v,%v/0/default.%s", nX, nY, nW, nH, sizePrefix, width, height, ext)
} else {
paramstring = fmt.Sprintf("full/%s%v,%v/0/default.%s", sizePrefix, width, height, ext)
}
sigfile := iai.ms.fspool.Truename(me.URI)
sigfile = strings.TrimPrefix(sigfile, iai.ms.iiifBase)
sigfile = strings.Replace(sigfile, "/", "%24", -1)
if err := iai.ms.proxyIIIF(req, w, me.Signature, sigfile, paramstring, me.Access == Media_Public); err != nil {
return false, fmt.Errorf("cannot proxy request: %v", err)
}
return true, nil
}
......@@ -15,9 +15,6 @@ import (
"fmt"
"github.com/goph/emperror"
"net/url"
"path/filepath"
"runtime"
"strings"
)
// protocol and access types need to be enums
......@@ -77,14 +74,17 @@ var (
// Represents the data needed to stream media object
type MediaEntry struct {
Signature string
URI url.URL
Protocol MediaProtocol
Access MediaAccess
Status MediaStatus
Signature string
URI url.URL
Protocol MediaProtocol
Access MediaAccess
Status MediaStatus
Type string
Mimetype string
Width, Height, Duration int64
}
func NewMediaEntry(signature, uri, access, protocol, status string) (*MediaEntry, error) {
func NewMediaEntry(signature, uri, access, protocol, status, t, mimetype string, width, height, length int64) (*MediaEntry, error) {
p, ok := MediaProtocolString[protocol]
// invalid data in database
if !ok {
......@@ -117,21 +117,14 @@ func NewMediaEntry(signature, uri, access, protocol, status string) (*MediaEntry
Protocol: p,
Access: a,
Status: s,
Type: t,
Mimetype: mimetype,
Width: width,
Height: height,
Duration: length,
}, nil
}
// mapUri the filepath in a clean way
func (me *MediaEntry) xgetFilePath() (string, error) {
if me.URI.Scheme != "file" {
return "", errors.New(fmt.Sprintf("invalid url scheme: %s", me.URI.Scheme))
}
filename := filepath.Clean(me.URI.Path)
if runtime.GOOS == "windows" {
filename = strings.TrimLeft(filename, string(filepath.Separator))
}
return filename, nil
}
func (me *MediaEntry) getProtocolString() string {
return MediaProtocolNum[me.Protocol]
}
......
......@@ -54,6 +54,7 @@ type memoServer struct {
accesslog io.Writer
errorTemplate *template.Template
fspool *FilesystemPool
actions map[string][]Action
}
func NewServer(
......@@ -124,9 +125,20 @@ func NewServer(
errorTemplate: errorTpl,
staticPrefix: staticPrefix,
staticDir: staticDir,
actions: map[string][]Action{},
}, nil
}
// add new action
func (ms *memoServer) AddAction(a Action) {
for _, typ := range a.GetType() {
if _, ok := ms.actions[typ]; !ok {
ms.actions[typ] = []Action{}
}
ms.actions[typ] = append(ms.actions[typ], a)
}
}
func (ms *memoServer) DoPanicf(writer http.ResponseWriter, status int, message string, a ...interface{}) (err error) {
msg := fmt.Sprintf(message, a...)
ms.DoPanic(writer, status, msg)
......
......@@ -14,7 +14,6 @@ import (
"github.com/goph/emperror"
"github.com/gorilla/mux"
"io"
"log"
"net/http"
"net/url"
"path/filepath"
......@@ -33,7 +32,7 @@ func (ms *memoServer) proxyIIIF(req *http.Request, writer http.ResponseWriter, s
urlstring := SingleJoiningSlash(ms.iiifUrl, file)
client := &http.Client{}
log.Println("Proxy: ", urlstring)
ms.log.Infof("Proxy: %v", urlstring)
req2, err := http.NewRequest("GET", urlstring, nil)
if err != nil {
return emperror.Wrapf(err, "Error creating new request")
......
......@@ -66,6 +66,19 @@ func (ms *memoServer) mainHandler(w http.ResponseWriter, req *http.Request) {
}
}
if actions, ok := ms.actions[me.Type]; ok {
for _, a := range actions {
ok, err := a.Do(w, req, me, action, strings.Split(params, "/")...)
if err != nil {
ms.DoPanicf(w, http.StatusInternalServerError, "Cannot execute action %s on signature %s: %v", action, me.Signature, err)
return
}
if ok {
return
}
}
}
// todo: create better code here
if action == "audio" && params == "view" {
type vData struct {
......
......@@ -12,6 +12,7 @@ package memostream
import (
"database/sql"
"fmt"
"github.com/goph/emperror"
)
......@@ -51,13 +52,28 @@ func (rdm *ResolverDBMySQL) Close() {
func (rdm *ResolverDBMySQL) Resolve(signature string) (*MediaEntry, error) {
// use prepared statement for speed
row := rdm.preparedStmt.QueryRow(signature)
var access, protocol, uri, status, mimetype string
var access, protocol, uri, status string
var typ, mimetype sql.NullString
var width, height, duration sql.NullInt64
// mapUri data
switch err := row.Scan(&uri, &access, &protocol, &status, &mimetype); err {
switch err := row.Scan(&uri, &access, &protocol, &status, &typ, &mimetype, &width, &height, &duration); err {
case sql.ErrNoRows: // dataset not found
return nil, emperror.Wrapf(err, "cannot find signature %s", signature)
case nil: // all fine, dataset found
return NewMediaEntry(signature, uri, access, protocol, status)
if !typ.Valid {
return nil, fmt.Errorf("no metadata for signature %s", signature)
}
return NewMediaEntry(
signature,
uri,
access,
protocol,
status,
typ.String,
mimetype.String,
width.Int64,
height.Int64,
duration.Int64)
default: // something strange happenz
return nil, emperror.Wrapf(err, "error querying %s - %s", rdm.query, signature)
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment