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")