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": [
{
"path": "videos",
"prefix": ""
"prefix": "",
"preserve_upload_filename": false
}
],
}
```
Set `path` to the value of the path where you want to store videos and where
`tube` will look for new videos.
- Set `path` to the value of the path where you want to store 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
@ -114,6 +137,7 @@ Set `path` to the value of the path where you want to store videos and where
"port": 8000,
"store_path": "tube.db",
"upload_path": "uploads",
"preserve_upload_filename": false,
"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
space for `tube` to store uploaded videos and process them. This can be a
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
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

View file

@ -21,6 +21,7 @@ import (
"git.mills.io/prologic/tube/templates"
"git.mills.io/prologic/tube/utils"
"github.com/cyphar/filepath-securejoin"
"github.com/dustin/go-humanize"
"github.com/fsnotify/fsnotify"
"github.com/gorilla/handlers"
@ -148,8 +149,9 @@ func (a *App) Run() error {
for _, pc := range a.Config.Library {
pc.Path = filepath.Clean(pc.Path)
p := &media.Path{
Path: pc.Path,
Prefix: pc.Prefix,
Path: pc.Path,
Prefix: pc.Prefix,
PreserveUploadFilename: pc.PreserveUploadFilename,
}
err := a.Library.AddPath(p)
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
func (a *App) uploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
@ -278,10 +285,49 @@ func (a *App) uploadHandler(w http.ResponseWriter, r *http.Request) {
return
}
vf := filepath.Join(
a.Library.Paths[targetLibraryPath].Path,
fmt.Sprintf("%s.mp4", shortuuid.New()),
)
// 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,
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())))
thumbFn2 := fmt.Sprintf("%s.jpg", strings.TrimSuffix(vf, filepath.Ext(vf)))

View file

@ -17,17 +17,19 @@ type Config struct {
// PathConfig settings for media library path.
type PathConfig struct {
Path string `json:"path"`
Prefix string `json:"prefix"`
Path string `json:"path"`
Prefix string `json:"prefix"`
PreserveUploadFilename bool `json:"preserve_upload_filename,omitempty"`
}
// ServerConfig settings for App Server.
type ServerConfig struct {
Host string `json:"host"`
Port int `json:"port"`
StorePath string `json:"store_path"`
UploadPath string `json:"upload_path"`
MaxUploadSize int64 `json:"max_upload_size"`
Host string `json:"host"`
Port int `json:"port"`
StorePath string `json:"store_path"`
UploadPath string `json:"upload_path"`
PreserveUploadFilename bool `json:"preserve_upload_filename,omitempty"`
MaxUploadSize int64 `json:"max_upload_size"`
}
// ThumbnailerConfig settings for Transcoder
@ -68,16 +70,18 @@ func DefaultConfig() *Config {
return &Config{
Library: []*PathConfig{
&PathConfig{
Path: "videos",
Prefix: "",
Path: "videos",
Prefix: "",
PreserveUploadFilename: false,
},
},
Server: &ServerConfig{
Host: "0.0.0.0",
Port: 8000,
StorePath: "tube.db",
UploadPath: "uploads",
MaxUploadSize: 104857600,
Host: "0.0.0.0",
Port: 8000,
StorePath: "tube.db",
UploadPath: "uploads",
PreserveUploadFilename: false,
MaxUploadSize: 104857600,
},
Thumbnailer: &ThumbnailerConfig{
Timeout: 60,

View file

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

1
go.mod
View file

@ -22,6 +22,7 @@ require (
github.com/abcum/lcp v0.0.0-20201209214815-7a3f3840be81 // indirect
github.com/antchfx/jsonquery v1.3.0 // 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/gofrs/flock v0.8.1 // 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/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View file

@ -2,6 +2,7 @@ package media
// Path represents a media library path.
type Path struct {
Path string
Prefix string
Path string
Prefix string
PreserveUploadFilename bool
}