feat: added thumbnail support for videos
This commit is contained in:
parent
040584c865
commit
6c22cb3b32
@ -26,20 +26,22 @@ import (
|
||||
// FileInfo describes a file.
|
||||
type FileInfo struct {
|
||||
*Listing
|
||||
Fs afero.Fs `json:"-"`
|
||||
Path string `json:"path"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Extension string `json:"extension"`
|
||||
ModTime time.Time `json:"modified"`
|
||||
Mode os.FileMode `json:"mode"`
|
||||
IsDir bool `json:"isDir"`
|
||||
IsSymlink bool `json:"isSymlink"`
|
||||
Type string `json:"type"`
|
||||
Subtitles []string `json:"subtitles,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Checksums map[string]string `json:"checksums,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
Fs afero.Fs `json:"-"`
|
||||
Dir string `json:"dir"`
|
||||
Path string `json:"path"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Extension string `json:"extension"`
|
||||
ModTime time.Time `json:"modified"`
|
||||
Mode os.FileMode `json:"mode"`
|
||||
IsDir bool `json:"isDir"`
|
||||
IsSymlink bool `json:"isSymlink"`
|
||||
IsThumbsEnabled bool `json:"isThumbsEnabled"`
|
||||
Type string `json:"type"`
|
||||
Subtitles []string `json:"subtitles,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Checksums map[string]string `json:"checksums,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
}
|
||||
|
||||
// FileOptions are the options when getting a file info.
|
||||
@ -54,6 +56,11 @@ type FileOptions struct {
|
||||
Content bool
|
||||
}
|
||||
|
||||
type FileThumbnail struct {
|
||||
Dir string
|
||||
Path string
|
||||
}
|
||||
|
||||
// NewFileInfo creates a File object from a path and a given user. This File
|
||||
// object will be automatically filled depending on if it is a directory
|
||||
// or a file. If it's a video file, it will also detect any subtitles.
|
||||
@ -84,6 +91,34 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
|
||||
return file, err
|
||||
}
|
||||
|
||||
func NewThumbnailInfo(opts FileOptions) (*FileInfo, error) {
|
||||
return NewFileInfo(FileOptions{
|
||||
Fs: opts.Fs,
|
||||
Path: NewFileThumbnail(opts).Path,
|
||||
Modify: opts.Modify,
|
||||
Expand: opts.Expand,
|
||||
ReadHeader: opts.ReadHeader,
|
||||
Checker: opts.Checker,
|
||||
})
|
||||
}
|
||||
|
||||
func NewFileThumbnail(opts FileOptions) FileThumbnail {
|
||||
dir, name := filepath.Split(opts.Path)
|
||||
|
||||
hash := md5.Sum([]byte(name))
|
||||
|
||||
thumbnailName := hex.EncodeToString(hash[:]) + ".jpg"
|
||||
|
||||
thumbnailPath := path.Join(dir, ".filebrowser", thumbnailName)
|
||||
|
||||
dir, _ = filepath.Split(thumbnailPath)
|
||||
|
||||
return FileThumbnail{
|
||||
Dir: dir,
|
||||
Path: thumbnailPath,
|
||||
}
|
||||
}
|
||||
|
||||
func stat(opts FileOptions) (*FileInfo, error) {
|
||||
var file *FileInfo
|
||||
|
||||
@ -92,8 +127,10 @@ func stat(opts FileOptions) (*FileInfo, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dir, _ := filepath.Split(opts.Path)
|
||||
file = &FileInfo{
|
||||
Fs: opts.Fs,
|
||||
Dir: dir,
|
||||
Path: opts.Path,
|
||||
Name: info.Name(),
|
||||
ModTime: info.ModTime(),
|
||||
@ -128,8 +165,10 @@ func stat(opts FileOptions) (*FileInfo, error) {
|
||||
return file, nil
|
||||
}
|
||||
|
||||
dir, _ := filepath.Split(opts.Path)
|
||||
file = &FileInfo{
|
||||
Fs: opts.Fs,
|
||||
Dir: dir,
|
||||
Path: opts.Path,
|
||||
Name: info.Name(),
|
||||
ModTime: info.ModTime(),
|
||||
@ -224,12 +263,14 @@ func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
|
||||
switch {
|
||||
case strings.HasPrefix(mimetype, "video"):
|
||||
i.Type = "video"
|
||||
i.detectThumbnail()
|
||||
i.detectSubtitles()
|
||||
return nil
|
||||
case strings.HasPrefix(mimetype, "audio"):
|
||||
i.Type = "audio"
|
||||
return nil
|
||||
case strings.HasPrefix(mimetype, "image"):
|
||||
i.IsThumbsEnabled = true
|
||||
i.Type = "image"
|
||||
return nil
|
||||
case strings.HasSuffix(mimetype, "pdf"):
|
||||
@ -301,6 +342,21 @@ func (i *FileInfo) detectSubtitles() {
|
||||
}
|
||||
}
|
||||
|
||||
func (i *FileInfo) detectThumbnail() {
|
||||
dir, name := filepath.Split(i.RealPath())
|
||||
|
||||
hash := md5.Sum([]byte(name))
|
||||
thumbnailName := hex.EncodeToString(hash[:])
|
||||
|
||||
path := path.Join(dir, ".filebrowser", thumbnailName+".jpg")
|
||||
|
||||
_, err := os.Stat(path)
|
||||
|
||||
if err == nil {
|
||||
i.IsThumbsEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
|
||||
afs := &afero.Afero{Fs: i.Fs}
|
||||
dir, err := afs.ReadDir(i.Path)
|
||||
|
||||
@ -6,10 +6,7 @@ import (
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// CopyDir copies a directory from source to dest and all
|
||||
// of its sub-directories. It doesn't stop if it finds an error
|
||||
// during the copy. Returns an error if any.
|
||||
func CopyDir(fs afero.Fs, source, dest string) error {
|
||||
func CreateDir(fs afero.Fs, source, dest string) error {
|
||||
// Get properties of source.
|
||||
srcinfo, err := fs.Stat(source)
|
||||
if err != nil {
|
||||
@ -22,6 +19,18 @@ func CopyDir(fs afero.Fs, source, dest string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyDir copies a directory from source to dest and all
|
||||
// of its sub-directories. It doesn't stop if it finds an error
|
||||
// during the copy. Returns an error if any.
|
||||
func CopyDir(fs afero.Fs, source, dest string) error {
|
||||
err := CreateDir(fs, source, dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dir, _ := fs.Open(source)
|
||||
obs, err := dir.Readdir(-1)
|
||||
if err != nil {
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
@dragover="dragOver"
|
||||
@drop="drop"
|
||||
@click="itemClick"
|
||||
:data-thumbs-enabled="isThumbsEnabled"
|
||||
:data-dir="isDir"
|
||||
:data-type="type"
|
||||
:aria-label="name"
|
||||
@ -15,7 +16,7 @@
|
||||
>
|
||||
<div>
|
||||
<img
|
||||
v-if="readOnly == undefined && type === 'image' && isThumbsEnabled"
|
||||
v-if="hasThumbnailUrl"
|
||||
v-lazy="thumbnailUrl"
|
||||
/>
|
||||
<i v-else class="material-icons"></i>
|
||||
@ -52,6 +53,7 @@ export default {
|
||||
props: [
|
||||
"name",
|
||||
"isDir",
|
||||
"isThumbsEnabled",
|
||||
"url",
|
||||
"type",
|
||||
"size",
|
||||
@ -90,8 +92,8 @@ export default {
|
||||
|
||||
return `${baseURL}/api/preview/thumb/${path}?k=${key}&inline=true`;
|
||||
},
|
||||
isThumbsEnabled() {
|
||||
return enableThumbs;
|
||||
hasThumbnailUrl() {
|
||||
return this.readOnly == undefined && this.isThumbsEnabled && enableThumbs;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
@ -221,6 +221,7 @@
|
||||
v-bind:index="item.index"
|
||||
v-bind:name="item.name"
|
||||
v-bind:isDir="item.isDir"
|
||||
v-bind:isThumbsEnabled="item.isThumbsEnabled"
|
||||
v-bind:url="item.url"
|
||||
v-bind:modified="item.modified"
|
||||
v-bind:type="item.type"
|
||||
|
||||
@ -59,6 +59,18 @@ func previewHandler(imgSvc ImgService, fileCache FileCache, enableThumbnails, re
|
||||
|
||||
setContentDisposition(w, r, file)
|
||||
|
||||
thumbnail, err := files.NewThumbnailInfo(files.FileOptions{
|
||||
Fs: d.user.Fs,
|
||||
Path: "/" + vars["path"],
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: true,
|
||||
ReadHeader: d.server.TypeDetectionByHeader,
|
||||
Checker: d,
|
||||
})
|
||||
if err == nil && thumbnail != nil {
|
||||
return rawFileHandler(w, r, thumbnail)
|
||||
}
|
||||
|
||||
switch file.Type {
|
||||
case "image":
|
||||
return handleImagePreview(w, r, imgSvc, fileCache, file, previewSize, enableThumbnails, resizePreview)
|
||||
|
||||
@ -77,6 +77,18 @@ func resourceDeleteHandler(fileCache FileCache) handleFunc {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
thumbnail, err := files.NewThumbnailInfo(files.FileOptions{
|
||||
Fs: d.user.Fs,
|
||||
Path: r.URL.Path,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: false,
|
||||
ReadHeader: d.server.TypeDetectionByHeader,
|
||||
Checker: d,
|
||||
})
|
||||
if err == nil && thumbnail != nil {
|
||||
d.user.Fs.RemoveAll(thumbnail.Path)
|
||||
}
|
||||
|
||||
err = d.RunHook(func() error {
|
||||
return d.user.Fs.RemoveAll(r.URL.Path)
|
||||
}, "delete", r.URL.Path, "", d.user)
|
||||
@ -299,6 +311,32 @@ func patchAction(ctx context.Context, action, src, dst string, d *data, fileCach
|
||||
return errors.ErrPermissionDenied
|
||||
}
|
||||
|
||||
srcThumbnail, err := files.NewThumbnailInfo(files.FileOptions{
|
||||
Fs: d.user.Fs,
|
||||
Path: src,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: false,
|
||||
ReadHeader: d.server.TypeDetectionByHeader,
|
||||
Checker: d,
|
||||
})
|
||||
if err == nil && srcThumbnail != nil {
|
||||
destThumbnail := files.NewFileThumbnail(files.FileOptions{
|
||||
Fs: d.user.Fs,
|
||||
Path: dst,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: false,
|
||||
ReadHeader: d.server.TypeDetectionByHeader,
|
||||
Checker: d,
|
||||
})
|
||||
|
||||
_, err := os.Stat(destThumbnail.Dir)
|
||||
if err != nil {
|
||||
fileutils.CreateDir(d.user.Fs, srcThumbnail.Dir, destThumbnail.Dir)
|
||||
}
|
||||
|
||||
fileutils.Copy(d.user.Fs, srcThumbnail.Path, destThumbnail.Path)
|
||||
}
|
||||
|
||||
return fileutils.Copy(d.user.Fs, src, dst)
|
||||
case "rename":
|
||||
if !d.user.Perm.Rename {
|
||||
@ -325,6 +363,32 @@ func patchAction(ctx context.Context, action, src, dst string, d *data, fileCach
|
||||
return err
|
||||
}
|
||||
|
||||
srcThumbnail, err := files.NewThumbnailInfo(files.FileOptions{
|
||||
Fs: d.user.Fs,
|
||||
Path: src,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: false,
|
||||
ReadHeader: d.server.TypeDetectionByHeader,
|
||||
Checker: d,
|
||||
})
|
||||
if err == nil && srcThumbnail != nil {
|
||||
destThumbnail := files.NewFileThumbnail(files.FileOptions{
|
||||
Fs: d.user.Fs,
|
||||
Path: dst,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: false,
|
||||
ReadHeader: d.server.TypeDetectionByHeader,
|
||||
Checker: d,
|
||||
})
|
||||
|
||||
_, err := os.Stat(destThumbnail.Dir)
|
||||
if err != nil {
|
||||
fileutils.CreateDir(d.user.Fs, srcThumbnail.Dir, destThumbnail.Dir)
|
||||
}
|
||||
|
||||
fileutils.MoveFile(d.user.Fs, srcThumbnail.Path, destThumbnail.Path)
|
||||
}
|
||||
|
||||
return fileutils.MoveFile(d.user.Fs, src, dst)
|
||||
default:
|
||||
return fmt.Errorf("unsupported action %s: %w", action, errors.ErrInvalidRequestParams)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user