You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
3.3 KiB
199 lines
3.3 KiB
package media
|
|
|
|
import (
|
|
"image"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"sort"
|
|
"time"
|
|
|
|
// for processing images
|
|
_ "image/gif"
|
|
_ "image/png"
|
|
|
|
"github.com/tajtiattila/metadata"
|
|
)
|
|
|
|
const (
|
|
TypeImage = "image"
|
|
TypeVideo = "video"
|
|
)
|
|
|
|
type MediaSource struct {
|
|
StorageDir string
|
|
ThumbDir string
|
|
|
|
set Set
|
|
}
|
|
|
|
type Media struct {
|
|
Type string
|
|
Name string
|
|
Size image.Rectangle
|
|
FullName string
|
|
|
|
ms *MediaSource
|
|
metadata *metadata.Metadata
|
|
}
|
|
|
|
func (m Media) ThumbPath(size image.Rectangle) string {
|
|
return "/" + thumbPath(size, m.Name)
|
|
}
|
|
|
|
func (m Media) ThumbFilename(size image.Rectangle) string {
|
|
size = m.NormalizeSize(size)
|
|
return thumbFilename(m.ms.ThumbDir, size, m.Name)
|
|
}
|
|
|
|
func (ms *MediaSource) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
m, err := ms.ByName(path.Base(r.URL.Path))
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
sizeRequested, err := SizeRequested(r.URL.Path, m.Size)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
size, err := ms.Thumb(*m, sizeRequested)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
http.ServeFile(w, r, m.ThumbFilename(size))
|
|
}
|
|
|
|
func (ms *MediaSource) ReceiveNewMedia(name string, r io.Reader) error {
|
|
ms.set = nil
|
|
|
|
dest := path.Join(ms.StorageDir, name)
|
|
f, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = io.Copy(f, r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return f.Close()
|
|
}
|
|
|
|
func (m *Media) Date() time.Time {
|
|
m.getMetadata()
|
|
|
|
if m.metadata != nil {
|
|
raw := m.metadata.Get(metadata.DateTimeOriginal)
|
|
if raw != "" {
|
|
d, err := time.Parse("2006-01-02T15:04:05", raw)
|
|
if err == nil {
|
|
return d
|
|
}
|
|
}
|
|
}
|
|
|
|
fi, err := os.Stat(m.FullName)
|
|
if err == nil {
|
|
return fi.ModTime()
|
|
}
|
|
|
|
return time.Time{}
|
|
}
|
|
|
|
func (m *Media) getMetadata() error {
|
|
if m.metadata != nil {
|
|
return nil
|
|
}
|
|
|
|
f, err := os.Open(m.FullName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
md, err := metadata.Parse(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m.metadata = md
|
|
return nil
|
|
}
|
|
|
|
func (ms *MediaSource) Size(name string) (image.Rectangle, error) {
|
|
switch filepath.Ext(name) {
|
|
case ".mp4":
|
|
return VideoSize(name)
|
|
}
|
|
|
|
return imageSize(name)
|
|
}
|
|
|
|
func (ms *MediaSource) ByName(name string) (*Media, error) {
|
|
ext := filepath.Ext(name)
|
|
typ := TypeImage
|
|
|
|
switch ext {
|
|
case ".mp4":
|
|
typ = TypeVideo
|
|
}
|
|
|
|
fullName := path.Join(ms.StorageDir, name)
|
|
size, _ := ms.Size(fullName)
|
|
|
|
return &Media{
|
|
Type: typ,
|
|
Name: name,
|
|
Size: size,
|
|
FullName: fullName,
|
|
ms: ms,
|
|
}, nil
|
|
}
|
|
|
|
func (ms *MediaSource) Walk() ([]*Media, error) {
|
|
if ms.set == nil {
|
|
var media []*Media
|
|
|
|
err := filepath.Walk(ms.StorageDir, func(name string, fi os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if fi.IsDir() {
|
|
return nil
|
|
}
|
|
m, err := ms.ByName(path.Base(name))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
media = append(media, m)
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return media, err
|
|
}
|
|
|
|
ms.set = media
|
|
}
|
|
|
|
return ms.set, nil
|
|
}
|
|
|
|
type Set []*Media
|
|
|
|
func (s Set) ByDate() Set {
|
|
sort.Slice(s, func(i, j int) bool {
|
|
return s[i].Date().After(s[j].Date())
|
|
})
|
|
return s
|
|
}
|
|
|
|
func removeExtension(name string) string {
|
|
ext := path.Ext(name)
|
|
return name[:len(name)-len(ext)]
|
|
}
|
|
|