imagorvideoextended/processor.go
Failure 17d4d46d09
Some checks failed
docker / Docker (push) Has been cancelled
test / Test (push) Has been cancelled
youtube handling + special site handling framework
2024-08-06 08:50:35 -07:00

198 lines
4.6 KiB
Go

package imagorvideoextended
import (
"context"
"crypto/sha256"
"github.com/antchfx/htmlquery"
"github.com/cshum/imagor"
"github.com/cshum/imagor/imagorpath"
"github.com/gabriel-vasile/mimetype"
"go.uber.org/zap"
"io"
"net/url"
"os"
"regexp"
"strings"
)
// Processor for imagorvideo that implements imagor.Processor interface
type Processor struct {
Logger *zap.Logger
Debug bool
FallbackImage string
}
// NewProcessor creates Processor
func NewProcessor(options ...Option) *Processor {
p := &Processor{
Logger: zap.NewNop(),
}
for _, option := range options {
option(p)
}
return p
}
// Startup implements imagor.Processor interface
func (p *Processor) Startup(_ context.Context) error {
return nil
}
// Shutdown implements imagor.Processor interface
func (p *Processor) Shutdown(_ context.Context) error {
return nil
}
var youtubeIdRegex = regexp.MustCompile("[^\"&?\\/\\s\\.=]{11}")
func specialUrl(path string) (hostname string, specialHandler string, specialData map[string]string, err error) {
specialData = make(map[string]string)
components, err := url.Parse(path)
if err != nil {
return
}
hostname = components.Host
if trimmed, ok := strings.CutPrefix(hostname, "www."); ok {
hostname = trimmed
}
switch hostname {
case "youtu.be":
fallthrough
case "youtube.com":
fallthrough
case "m.youtube.com":
id := youtubeIdRegex.FindString(path)
println(id)
if id != "" {
specialHandler = "youtube"
specialData["id"] = id
}
}
return hostname, specialHandler, specialData, nil
}
func subThumbnail(url string) string {
key := os.Getenv("IMAGOR_SECRET")
params := imagorpath.Params{
Image: url,
FitIn: true,
Width: 400,
Height: 400,
}
path := imagorpath.Generate(params, imagorpath.NewHMACSigner(sha256.New, 0, key))
return path
}
// Process implements imagor.Processor interface
func (p *Processor) Process(ctx context.Context, in *imagor.Blob, params imagorpath.Params, load imagor.LoadFunc) (out *imagor.Blob, err error) {
defer func() {
if err == nil || out != nil {
return
}
if _, ok := err.(imagor.ErrForward); ok {
return
}
err = imagor.NewError(err.Error(), 406)
// fallback image on error
out = imagor.NewBlobFromBytes(transPixel)
if p.FallbackImage != "" {
if o, e := load(p.FallbackImage); e == nil {
out = o
}
}
}()
var mime = mimetype.Detect(in.Sniff())
if typ := mime.String(); !strings.HasPrefix(typ, "text/html") {
// forward identical for non video nor audio
err = imagor.ErrForward{Params: params}
out = in
return
}
rs, size, err := in.NewReadSeeker()
if err != nil {
return
}
defer func() {
_ = rs.Close()
}()
if size <= 0 && rs != nil {
// size must be known
if size, err = rs.Seek(0, io.SeekEnd); err != nil {
return
}
if _, err = rs.Seek(0, io.SeekStart); err != nil {
return
}
}
all, err := in.ReadAll()
if err != nil {
return nil, err
}
hostname, specialHandler, specialData, _ := specialUrl(params.Image)
doc, err := htmlquery.Parse(strings.NewReader(string(all[:])))
meta := Metadata{
Format: strings.TrimPrefix(mime.Extension(), "."),
Title: "",
Description: "",
Image: "",
Hostname: hostname,
SpecialHandler: specialHandler,
SpecialData: specialData,
}
metaTags := htmlquery.Find(doc, "//meta[@property]")
for _, metaTag := range metaTags {
var property = htmlquery.SelectAttr(metaTag, "property")
var val = htmlquery.SelectAttr(metaTag, "content")
switch property {
case "og:image":
fallthrough
case "twitter:image:src":
meta.Image = subThumbnail(val)
break
case "og:title":
fallthrough
case "twitter:title":
meta.Title = val
break
case "twitter:description":
fallthrough
case "og:description":
meta.Description = val
}
}
if meta.Title == "" {
title := htmlquery.FindOne(doc, "//title")
if title != nil {
meta.Title = htmlquery.InnerText(title)
} else {
meta.Title = in.FilePath()
}
}
out = imagor.NewBlobFromJsonMarshal(meta)
return
}
// Metadata imagorvideo metadata
type Metadata struct {
Format string `json:"format"`
Title string `json:"title"`
Description string `json:"description"`
Image string `json:"image"`
Hostname string `json:"hostname"`
SpecialHandler string `json:"special_handler"`
SpecialData map[string]string `json:"special_data"`
}
var transPixel = []byte("\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B")