Added video support
This commit is contained in:
parent
f92384f0ce
commit
3e1fb6be6a
10
README.md
10
README.md
|
@ -1,14 +1,14 @@
|
||||||
## About
|
## About
|
||||||
|
|
||||||
Sometimes, you just need a way to randomly display images from your filesystem.
|
Sometimes, you just need a way to randomly display media from your filesystem.
|
||||||
|
|
||||||
Simply point this tool at one or more directories, and then open the specified port (default `8080`) in your browser.
|
Simply point this tool at one or more directories, and then open the specified port (default `8080`) in your browser.
|
||||||
|
|
||||||
A new image will be selected if you open `/` directly, or if you click on any displayed image.
|
A new file will be selected if you open `/` directly, or if you click on any displayed media.
|
||||||
|
|
||||||
Browser history is preserved, so you can always go back to any previously displayed image.
|
Browser history is preserved, so you can always go back to any previously displayed media.
|
||||||
|
|
||||||
Supported file types and extensions are `bmp`, `gif`, `jp[e]g`, `png`, and `webp`.
|
Supported file types and extensions are `bmp`, `gif`, `jp[e]g`, `png`, `webp`, `mp4`, `ogv`, and `webm`.
|
||||||
|
|
||||||
Feature requests, code criticism, bug reports, general chit-chat, and unrelated angst accepted at `roulette@seedno.de`.
|
Feature requests, code criticism, bug reports, general chit-chat, and unrelated angst accepted at `roulette@seedno.de`.
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ Note: These patterns require sequentially-numbered files matching the following
|
||||||
|
|
||||||
If a positive-value `refresh=<integer><unit>` query parameter is provided, the page will reload after that interval.
|
If a positive-value `refresh=<integer><unit>` query parameter is provided, the page will reload after that interval.
|
||||||
|
|
||||||
This can be used to generate a sort of slideshow of images.
|
This can be used to generate a sort of slideshow of files.
|
||||||
|
|
||||||
Supported units are `ns`, `us`/`µs`, `ms`, `s`, `m`, and `h`.
|
Supported units are `ns`, `us`/`µs`, `ms`, `s`, `m`, and `h`.
|
||||||
|
|
||||||
|
|
29
cmd/files.go
29
cmd/files.go
|
@ -43,7 +43,7 @@ type Concurrency struct {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNoImagesFound = fmt.Errorf("no supported image formats found which match all criteria")
|
ErrNoImagesFound = fmt.Errorf("no supported image formats found which match all criteria")
|
||||||
Extensions = [6]string{".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"}
|
Extensions = [9]string{".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".mp4", ".ogv", ".webm"}
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dimensions struct {
|
type Dimensions struct {
|
||||||
|
@ -168,12 +168,12 @@ func preparePath(path string) string {
|
||||||
|
|
||||||
func appendPath(directory, path string, files *Files, stats *ScanStats, shouldCache bool) error {
|
func appendPath(directory, path string, files *Files, stats *ScanStats, shouldCache bool) error {
|
||||||
if shouldCache {
|
if shouldCache {
|
||||||
image, err := isImage(path)
|
image, video, err := isSupportedFileType(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !image {
|
if !(image || video) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -382,20 +382,27 @@ func pathIsValid(filePath string, paths []string) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isImage(path string) (bool, error) {
|
func isSupportedFileType(path string) (bool, bool, error) {
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, os.ErrNotExist):
|
case errors.Is(err, os.ErrNotExist):
|
||||||
return false, nil
|
return false, false, nil
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return false, err
|
return false, false, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
head := make([]byte, 261)
|
head := make([]byte, 261)
|
||||||
file.Read(head)
|
file.Read(head)
|
||||||
|
|
||||||
return filetype.IsImage(head), nil
|
switch {
|
||||||
|
case filetype.IsImage(head):
|
||||||
|
return true, false, nil
|
||||||
|
case filetype.IsVideo(head):
|
||||||
|
return false, true, nil
|
||||||
|
default:
|
||||||
|
return false, false, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func pathHasSupportedFiles(path string) (bool, error) {
|
func pathHasSupportedFiles(path string) (bool, error) {
|
||||||
|
@ -410,12 +417,12 @@ func pathHasSupportedFiles(path string) (bool, error) {
|
||||||
case !recursive && info.IsDir() && p != path:
|
case !recursive && info.IsDir() && p != path:
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
case !info.IsDir():
|
case !info.IsDir():
|
||||||
image, err := isImage(p)
|
image, video, err := isSupportedFileType(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if image {
|
if image || video {
|
||||||
hasSupportedFiles <- true
|
hasSupportedFiles <- true
|
||||||
return filepath.SkipAll
|
return filepath.SkipAll
|
||||||
}
|
}
|
||||||
|
@ -609,12 +616,12 @@ func pickFile(args []string, filters *Filters, sort string, index *Index) (strin
|
||||||
filePath := fileList[r]
|
filePath := fileList[r]
|
||||||
|
|
||||||
if !fromCache {
|
if !fromCache {
|
||||||
isImage, err := isImage(filePath)
|
image, video, err := isSupportedFileType(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if isImage {
|
if image || video {
|
||||||
return filePath, nil
|
return filePath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version = "0.44.0"
|
var Version = "0.45.0"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(versionCmd)
|
rootCmd.AddCommand(versionCmd)
|
||||||
|
|
27
cmd/web.go
27
cmd/web.go
|
@ -751,22 +751,27 @@ func serveImage(paths []string, Regexes *Regexes, index *Index) http.HandlerFunc
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
image, err := isImage(filePath)
|
image, video, err := isSupportedFileType(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !image {
|
|
||||||
|
if !(image || video) {
|
||||||
notFound(w, r, filePath)
|
notFound(w, r, filePath)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dimensions, err := imageDimensions(filePath)
|
var dimensions *Dimensions
|
||||||
|
|
||||||
|
if image {
|
||||||
|
dimensions, err = imageDimensions(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fileName := filepath.Base(filePath)
|
fileName := filepath.Base(filePath)
|
||||||
|
|
||||||
|
@ -782,24 +787,38 @@ func serveImage(paths []string, Regexes *Regexes, index *Index) http.HandlerFunc
|
||||||
htmlBody.WriteString(`<!DOCTYPE html><html lang="en"><head>`)
|
htmlBody.WriteString(`<!DOCTYPE html><html lang="en"><head>`)
|
||||||
htmlBody.WriteString(`<style>html,body{margin:0;padding:0;height:100%;}`)
|
htmlBody.WriteString(`<style>html,body{margin:0;padding:0;height:100%;}`)
|
||||||
htmlBody.WriteString(`a{display:block;height:100%;width:100%;text-decoration:none;}`)
|
htmlBody.WriteString(`a{display:block;height:100%;width:100%;text-decoration:none;}`)
|
||||||
htmlBody.WriteString(`img{margin:auto;display:block;max-width:97%;max-height:97%;object-fit:scale-down;`)
|
htmlBody.WriteString(`img,video{margin:auto;display:block;max-width:97%;max-height:97%;object-fit:scale-down;`)
|
||||||
htmlBody.WriteString(`position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);}</style>`)
|
htmlBody.WriteString(`position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);}</style>`)
|
||||||
|
switch {
|
||||||
|
case image:
|
||||||
htmlBody.WriteString(fmt.Sprintf(`<title>%s (%dx%d)</title>`,
|
htmlBody.WriteString(fmt.Sprintf(`<title>%s (%dx%d)</title>`,
|
||||||
fileName,
|
fileName,
|
||||||
dimensions.width,
|
dimensions.width,
|
||||||
dimensions.height))
|
dimensions.height))
|
||||||
|
case video:
|
||||||
|
htmlBody.WriteString(fmt.Sprintf(`<title>%s</title>`,
|
||||||
|
fileName))
|
||||||
|
}
|
||||||
htmlBody.WriteString(`</head><body>`)
|
htmlBody.WriteString(`</head><body>`)
|
||||||
if refreshInterval != "0ms" {
|
if refreshInterval != "0ms" {
|
||||||
htmlBody.WriteString(fmt.Sprintf("<script>window.onload = function(){setInterval(function(){window.location.href = '/%s';}, %d);};</script>",
|
htmlBody.WriteString(fmt.Sprintf("<script>window.onload = function(){setInterval(function(){window.location.href = '/%s';}, %d);};</script>",
|
||||||
queryParams,
|
queryParams,
|
||||||
refreshTimer))
|
refreshTimer))
|
||||||
}
|
}
|
||||||
|
switch {
|
||||||
|
case image:
|
||||||
htmlBody.WriteString(fmt.Sprintf(`<a href="/%s"><img src="%s" width="%d" height="%d" alt="Roulette selected: %s"></a>`,
|
htmlBody.WriteString(fmt.Sprintf(`<a href="/%s"><img src="%s" width="%d" height="%d" alt="Roulette selected: %s"></a>`,
|
||||||
queryParams,
|
queryParams,
|
||||||
generateFilePath(filePath),
|
generateFilePath(filePath),
|
||||||
dimensions.width,
|
dimensions.width,
|
||||||
dimensions.height,
|
dimensions.height,
|
||||||
fileName))
|
fileName))
|
||||||
|
case video:
|
||||||
|
htmlBody.WriteString(fmt.Sprintf(`<a href="/%s"><video src="%s" alt="Roulette selected: %s" autoplay controls></a>`,
|
||||||
|
queryParams,
|
||||||
|
generateFilePath(filePath),
|
||||||
|
fileName))
|
||||||
|
}
|
||||||
htmlBody.WriteString(`</body></html>`)
|
htmlBody.WriteString(`</body></html>`)
|
||||||
|
|
||||||
_, err = io.WriteString(w, gohtml.Format(htmlBody.String()))
|
_, err = io.WriteString(w, gohtml.Format(htmlBody.String()))
|
||||||
|
|
Loading…
Reference in New Issue