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