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:
parent
9952cc533a
commit
19a1141af3
7 changed files with 111 additions and 26 deletions
35
README.md
35
README.md
|
@ -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
|
||||||
|
|
58
app/app.go
58
app/app.go
|
@ -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"
|
||||||
|
@ -148,8 +149,9 @@ func (a *App) Run() error {
|
||||||
for _, pc := range a.Config.Library {
|
for _, pc := range a.Config.Library {
|
||||||
pc.Path = filepath.Clean(pc.Path)
|
pc.Path = filepath.Clean(pc.Path)
|
||||||
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.
|
||||||
a.Library.Paths[targetLibraryPath].Path,
|
var vf string
|
||||||
fmt.Sprintf("%s.mp4", shortuuid.New()),
|
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())))
|
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)))
|
||||||
|
|
||||||
|
|
|
@ -17,17 +17,19 @@ type Config struct {
|
||||||
|
|
||||||
// PathConfig settings for media library path.
|
// PathConfig settings for media library path.
|
||||||
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.
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
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"`
|
||||||
MaxUploadSize int64 `json:"max_upload_size"`
|
PreserveUploadFilename bool `json:"preserve_upload_filename,omitempty"`
|
||||||
|
MaxUploadSize int64 `json:"max_upload_size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ThumbnailerConfig settings for Transcoder
|
// ThumbnailerConfig settings for Transcoder
|
||||||
|
@ -68,16 +70,18 @@ func DefaultConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Library: []*PathConfig{
|
Library: []*PathConfig{
|
||||||
&PathConfig{
|
&PathConfig{
|
||||||
Path: "videos",
|
Path: "videos",
|
||||||
Prefix: "",
|
Prefix: "",
|
||||||
|
PreserveUploadFilename: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Server: &ServerConfig{
|
Server: &ServerConfig{
|
||||||
Host: "0.0.0.0",
|
Host: "0.0.0.0",
|
||||||
Port: 8000,
|
Port: 8000,
|
||||||
StorePath: "tube.db",
|
StorePath: "tube.db",
|
||||||
UploadPath: "uploads",
|
UploadPath: "uploads",
|
||||||
MaxUploadSize: 104857600,
|
PreserveUploadFilename: false,
|
||||||
|
MaxUploadSize: 104857600,
|
||||||
},
|
},
|
||||||
Thumbnailer: &ThumbnailerConfig{
|
Thumbnailer: &ThumbnailerConfig{
|
||||||
Timeout: 60,
|
Timeout: 60,
|
||||||
|
|
|
@ -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
1
go.mod
|
@ -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
2
go.sum
|
@ -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=
|
||||||
|
|
|
@ -2,6 +2,7 @@ package media
|
||||||
|
|
||||||
// Path represents a media library path.
|
// Path represents a media library path.
|
||||||
type Path struct {
|
type Path struct {
|
||||||
Path string
|
Path string
|
||||||
Prefix string
|
Prefix string
|
||||||
|
PreserveUploadFilename bool
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue