preserve-video-filename (#49)

This should fix issue #40

I have not made the `allowed_characters` configurable yet nor the `replacement_character`.

Mostly because I couldn't decide if I should define those "globally" on a server basis, or on the library nodes.

Feel free to modify, extend, rip apart. 😁

Reviewed-on: https://git.mills.io/prologic/tube/pulls/49
Co-authored-by: Heinrich 'Henrik' Langos <gumbo2000@noreply@mills.io>
Co-committed-by: Heinrich 'Henrik' Langos <gumbo2000@noreply@mills.io>
This commit is contained in:
Heinrich 'Henrik' Langos 2023-01-16 11:33:12 +00:00 committed by James Mills
parent 9952cc533a
commit 19a1141af3
7 changed files with 111 additions and 26 deletions

View file

@ -96,14 +96,37 @@ Here are some documentation on key configuration items:
"library": [ "library": [
{ {
"path": "videos", "path": "videos",
"prefix": "" "prefix": "",
"preserve_upload_filename": false
} }
], ],
} }
``` ```
Set `path` to the value of the path where you want to store videos and where - Set `path` to the value of the path where you want to store videos
`tube` will look for new videos. and where `tube` will watch for new video files to show up.
- Set `prefix` to add a directory component in the video URL.
- Set the (optional) `preserve_upload_filename` parameter to `true`,
to to preserve the name of files that are uploaded to this location.
You can add more than one location for video files.
```#!json
{
"library": [
{
"path": "/path/to/cat/videos",
"prefix": "cats",
"preserve_upload_filename": true
},
{
"path": "relative/dog/directory/",
"prefix": "dogs"
},
],
}
```
The path will be visible on the upload page and clients can select a
destination for their uploads. Both `prefix` and `path` need to be unique.
### Server Options / Upload Path and Max Upload Size ### Server Options / Upload Path and Max Upload Size
@ -114,6 +137,7 @@ Set `path` to the value of the path where you want to store videos and where
"port": 8000, "port": 8000,
"store_path": "tube.db", "store_path": "tube.db",
"upload_path": "uploads", "upload_path": "uploads",
"preserve_upload_filename": false,
"max_upload_size": 104857600 "max_upload_size": 104857600
} }
} }
@ -129,6 +153,11 @@ Set `path` to the value of the path where you want to store videos and where
- Set `upload_path` to a directory that you wish to use as a temporary working - Set `upload_path` to a directory that you wish to use as a temporary working
space for `tube` to store uploaded videos and process them. This can be a space for `tube` to store uploaded videos and process them. This can be a
tmpfs file system for example for faster I/O. tmpfs file system for example for faster I/O.
- Set `preserve_upload_filename` parameter to `true` and tube will try to
preserve the filename that was transmitted by the client. The default is
to give random filenames to uploaded files.
If you set it to `true` in the "server" node, it will be active for all
library locations.
- Set `max_upload_size` to the maximum number of bytes you wish to impose on - Set `max_upload_size` to the maximum number of bytes you wish to impose on
uploaded and imported videos. Upload(s)/Import(s) that exceed this size will uploaded and imported videos. Upload(s)/Import(s) that exceed this size will
by denied by the server. This is a saftey measure so as to not DoS the by denied by the server. This is a saftey measure so as to not DoS the

View file

@ -21,6 +21,7 @@ import (
"git.mills.io/prologic/tube/templates" "git.mills.io/prologic/tube/templates"
"git.mills.io/prologic/tube/utils" "git.mills.io/prologic/tube/utils"
"github.com/cyphar/filepath-securejoin"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
@ -150,6 +151,7 @@ func (a *App) Run() error {
p := &media.Path{ p := &media.Path{
Path: pc.Path, Path: pc.Path,
Prefix: pc.Prefix, Prefix: pc.Prefix,
PreserveUploadFilename: pc.PreserveUploadFilename,
} }
err := a.Library.AddPath(p) err := a.Library.AddPath(p)
if err != nil { if err != nil {
@ -215,6 +217,11 @@ func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
func filenameWithoutExtension(path string) (stem string) {
var basename string = filepath.Base(path)
return basename[0:len(basename)-len(filepath.Ext(basename))]
}
// HTTP handler for /upload // HTTP handler for /upload
func (a *App) uploadHandler(w http.ResponseWriter, r *http.Request) { func (a *App) uploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" { if r.Method == "GET" {
@ -278,10 +285,49 @@ func (a *App) uploadHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
vf := filepath.Join( // Here we set the final filename for the video file after transcoding.
var vf string
if a.Config.Server.PreserveUploadFilename ||
a.Library.Paths[targetLibraryPath].PreserveUploadFilename {
vf, err = securejoin.SecureJoin(
a.Library.Paths[targetLibraryPath].Path,
fmt.Sprintf("%s.mp4", filenameWithoutExtension(handler.Filename)),
)
} else {
vf, err = securejoin.SecureJoin(
a.Library.Paths[targetLibraryPath].Path, a.Library.Paths[targetLibraryPath].Path,
fmt.Sprintf("%s.mp4", shortuuid.New()), fmt.Sprintf("%s.mp4", shortuuid.New()),
) )
}
if err != nil {
err := fmt.Errorf("error creating file name in target library: %w", err)
log.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// If the (sanitized) original filename collides with an existing file,
// we try to add a shortuuid() to it until we find one that doesn't exist.
for _, err := os.Stat(vf) ; ! os.IsNotExist(err) ; _, err = os.Stat(vf) {
if err != nil {
log.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Warn("File '"+ vf + "' already exists.");
vf, err = securejoin.SecureJoin(
a.Library.Paths[targetLibraryPath].Path,
fmt.Sprintf("%s_%s.mp4", filenameWithoutExtension(vf), shortuuid.New()),
)
if err != nil {
err := fmt.Errorf("error creating file name in target library: %w", err)
log.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Warn("Using filename '" + vf + "' instead.");
}
thumbFn1 := fmt.Sprintf("%s.jpg", strings.TrimSuffix(tf.Name(), filepath.Ext(tf.Name()))) thumbFn1 := fmt.Sprintf("%s.jpg", strings.TrimSuffix(tf.Name(), filepath.Ext(tf.Name())))
thumbFn2 := fmt.Sprintf("%s.jpg", strings.TrimSuffix(vf, filepath.Ext(vf))) thumbFn2 := fmt.Sprintf("%s.jpg", strings.TrimSuffix(vf, filepath.Ext(vf)))

View file

@ -19,6 +19,7 @@ type Config struct {
type PathConfig struct { type PathConfig struct {
Path string `json:"path"` Path string `json:"path"`
Prefix string `json:"prefix"` Prefix string `json:"prefix"`
PreserveUploadFilename bool `json:"preserve_upload_filename,omitempty"`
} }
// ServerConfig settings for App Server. // ServerConfig settings for App Server.
@ -27,6 +28,7 @@ type ServerConfig struct {
Port int `json:"port"` Port int `json:"port"`
StorePath string `json:"store_path"` StorePath string `json:"store_path"`
UploadPath string `json:"upload_path"` UploadPath string `json:"upload_path"`
PreserveUploadFilename bool `json:"preserve_upload_filename,omitempty"`
MaxUploadSize int64 `json:"max_upload_size"` MaxUploadSize int64 `json:"max_upload_size"`
} }
@ -70,6 +72,7 @@ func DefaultConfig() *Config {
&PathConfig{ &PathConfig{
Path: "videos", Path: "videos",
Prefix: "", Prefix: "",
PreserveUploadFilename: false,
}, },
}, },
Server: &ServerConfig{ Server: &ServerConfig{
@ -77,6 +80,7 @@ func DefaultConfig() *Config {
Port: 8000, Port: 8000,
StorePath: "tube.db", StorePath: "tube.db",
UploadPath: "uploads", UploadPath: "uploads",
PreserveUploadFilename: false,
MaxUploadSize: 104857600, MaxUploadSize: 104857600,
}, },
Thumbnailer: &ThumbnailerConfig{ Thumbnailer: &ThumbnailerConfig{

View file

@ -2,7 +2,8 @@
"library": [ "library": [
{ {
"path": "videos", "path": "videos",
"prefix": "" "prefix": "",
"preserve_upload_filename": false
} }
], ],
"server": { "server": {
@ -10,6 +11,7 @@
"port": 8000, "port": 8000,
"store_path": "tube.db", "store_path": "tube.db",
"upload_path": "uploads", "upload_path": "uploads",
"preserve_upload_filename": false,
"max_upload_size": 104857600 "max_upload_size": 104857600
}, },
"thumbnailer": { "thumbnailer": {

1
go.mod
View file

@ -22,6 +22,7 @@ require (
github.com/abcum/lcp v0.0.0-20201209214815-7a3f3840be81 // indirect github.com/abcum/lcp v0.0.0-20201209214815-7a3f3840be81 // indirect
github.com/antchfx/jsonquery v1.3.0 // indirect github.com/antchfx/jsonquery v1.3.0 // indirect
github.com/antchfx/xpath v1.2.1 // indirect github.com/antchfx/xpath v1.2.1 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/gofrs/flock v0.8.1 // indirect github.com/gofrs/flock v0.8.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect

2
go.sum
View file

@ -85,6 +85,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View file

@ -4,4 +4,5 @@ package media
type Path struct { type Path struct {
Path string Path string
Prefix string Prefix string
PreserveUploadFilename bool
} }