Compare commits
9 Commits
236b1354ac
...
5b67d5c5f0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b67d5c5f0 | ||
|
|
93aecf47c6 | ||
|
|
69e2789b9b | ||
|
|
3e6d2874d5 | ||
|
|
e377a4f5db | ||
|
|
2668cbed7d | ||
|
|
bea85bc736 | ||
|
|
b5c146bd1d | ||
|
|
c761ea38b8 |
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/cmd/prune/prune
|
||||
/prune
|
||||
/cmd/ui/ui
|
||||
/ui
|
||||
**/*.sw*
|
||||
**/testdata
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"ffmpeg.d/pkg/fs"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -244,23 +246,9 @@ func mv(wPath, rPath string) error {
|
||||
}
|
||||
|
||||
func lsd(d string) ([]string, error) {
|
||||
return ls(d, true)
|
||||
return fs.LsD(d)
|
||||
}
|
||||
|
||||
func lsf(d string) ([]string, error) {
|
||||
return ls(d, false)
|
||||
}
|
||||
|
||||
func ls(d string, dirs bool) ([]string, error) {
|
||||
entries, err := os.ReadDir(d)
|
||||
|
||||
results := make([]string, 0, len(entries))
|
||||
for i := range entries {
|
||||
if dirs == entries[i].IsDir() {
|
||||
results = append(results, path.Join(d, entries[i].Name()))
|
||||
}
|
||||
}
|
||||
sort.Strings(results)
|
||||
|
||||
return results, err
|
||||
return fs.LsF(d)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
BIN
cmd/prune/testdata/2024-09-17T22-55-38.webm
vendored
Normal file
|
Before Width: | Height: | Size: 9.1 MiB After Width: | Height: | Size: 9.1 MiB |
|
Before Width: | Height: | Size: 8.3 MiB After Width: | Height: | Size: 8.3 MiB |
21
cmd/ui/index.html.tmpl
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<header>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/dark.css">
|
||||
</header>
|
||||
<body>
|
||||
<div>
|
||||
{{ range .Series }}
|
||||
<div style="display: inline-block; max-width: 12em; max-height: 9em; margin-bottom: .7em;">
|
||||
<a href="/media/{{ .HREF }}">
|
||||
<img src="/media/{{ .Thumbnail }}" alt="{{ .Thumbnail }}"/>
|
||||
<span>{{ .Thumbnail }}</span>
|
||||
<br>
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</body>
|
||||
<footer>
|
||||
</footer>
|
||||
</html>
|
||||
123
cmd/ui/main.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
_ "embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"ffmpeg.d/pkg/fs"
|
||||
)
|
||||
|
||||
//go:embed *.tmpl
|
||||
var TMPL embed.FS
|
||||
|
||||
func main() {
|
||||
ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT)
|
||||
defer can()
|
||||
|
||||
if err := Run(ctx, os.Args[1:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Run(ctx context.Context, args []string) error {
|
||||
flags := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
||||
d := flags.String("d", "./testdata/", "directory containing directories of (x.jpg,x.mp4)")
|
||||
p := flags.Int("p", 38080, "port to listen on")
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.ParseFS(TMPL, "*.tmpl")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", *p),
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.URL.Path, "/media/") {
|
||||
http.StripPrefix("/media/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.FileServer(http.Dir(*d)).ServeHTTP(w, r)
|
||||
})).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
type Series struct {
|
||||
HREF string
|
||||
Thumbnail string
|
||||
}
|
||||
|
||||
ds, err := fs.LsD(*d)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
seriesByKey := map[string]Series{}
|
||||
for _, d := range ds {
|
||||
files, err := fs.LsF(d)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
for _, f := range files {
|
||||
key := strings.Split(path.Base(f), ".")[0]
|
||||
v := seriesByKey[key]
|
||||
switch path.Ext(f) {
|
||||
case ".jpg":
|
||||
v.Thumbnail = path.Join(path.Base(d), path.Base(f))
|
||||
case ".mp4":
|
||||
v.HREF = path.Join(path.Base(d), path.Base(f))
|
||||
}
|
||||
seriesByKey[key] = v
|
||||
}
|
||||
}
|
||||
series := []Series{}
|
||||
for _, v := range seriesByKey {
|
||||
series = append(series, v)
|
||||
}
|
||||
slices.SortFunc(series, func(a, b Series) int {
|
||||
return -1 * strings.Compare(path.Base(a.Thumbnail), path.Base(b.Thumbnail))
|
||||
})
|
||||
|
||||
if err := tmpl.Execute(w, map[string]any{
|
||||
"Series": series,
|
||||
}); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}),
|
||||
BaseContext: func(net.Listener) context.Context {
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
defer s.Shutdown(ctx)
|
||||
|
||||
errs := make(chan error)
|
||||
go func() {
|
||||
defer close(errs)
|
||||
|
||||
errs <- s.ListenAndServe()
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-errs:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
s.Shutdown(ctx)
|
||||
for range errs {
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
1
cmd/ui/testdata/cam/1.jpg
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
LAdN7KVw-Ig.jpg
|
||||
1
cmd/ui/testdata/cam/1.mp4
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
LAdN7KVw-Ig.mp4
|
||||
1
cmd/ui/testdata/cam/2.jpg
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
LAdN7KVw-Ig.jpg
|
||||
1
cmd/ui/testdata/cam/2.mp4
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
LAdN7KVw-Ig.mp4
|
||||
1
cmd/ui/testdata/cam/3.jpg
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
LAdN7KVw-Ig.jpg
|
||||
1
cmd/ui/testdata/cam/3.mp4
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
LAdN7KVw-Ig.mp4
|
||||
1
cmd/ui/testdata/cam/4.jpg
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
LAdN7KVw-Ig.jpg
|
||||
1
cmd/ui/testdata/cam/4.mp4
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
LAdN7KVw-Ig.mp4
|
||||
1
cmd/ui/testdata/cam/5.jpg
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
LAdN7KVw-Ig.jpg
|
||||
1
cmd/ui/testdata/cam/5.mp4
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
LAdN7KVw-Ig.mp4
|
||||
1
cmd/ui/testdata/cam/6.jpg
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
LAdN7KVw-Ig.jpg
|
||||
1
cmd/ui/testdata/cam/6.mp4
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
LAdN7KVw-Ig.mp4
|
||||
BIN
cmd/ui/testdata/cam/LAdN7KVw-Ig.jpg
vendored
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
cmd/ui/testdata/cam/LAdN7KVw-Ig.mp4
vendored
Normal file
29
pkg/fs/ls.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func LsD(d string) ([]string, error) {
|
||||
return ls(d, true)
|
||||
}
|
||||
|
||||
func LsF(d string) ([]string, error) {
|
||||
return ls(d, false)
|
||||
}
|
||||
|
||||
func ls(d string, dirs bool) ([]string, error) {
|
||||
entries, err := os.ReadDir(d)
|
||||
|
||||
results := make([]string, 0, len(entries))
|
||||
for i := range entries {
|
||||
if dirs == entries[i].IsDir() {
|
||||
results = append(results, path.Join(d, entries[i].Name()))
|
||||
}
|
||||
}
|
||||
sort.Strings(results)
|
||||
|
||||
return results, err
|
||||
}
|
||||
3
testdata/ffmpeg.d/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
**/*.sw*
|
||||
/cmd/prune/prune
|
||||
/cmd/prune/testdata/**
|
||||