package main import ( "fmt" "net/http" "os" "path/filepath" "github.com/h2non/filetype" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/urfave/cli/v2" "strings" ) var Commands = []*cli.Command{} var version = "dev" func main() { app := &cli.App{ Flags: []cli.Flag{ &cli.StringFlag{ Name: "debug", }, }, Commands: []*cli.Command{ { Name: "serve", Aliases: []string{"c"}, Flags: []cli.Flag{ &cli.StringFlag{ Name: "listen", Aliases: []string{"l"}, Value: "127.0.0.1", }, &cli.StringFlag{ Name: "port", Aliases: []string{"p"}, Value: "4000", }, &cli.StringFlag{ Name: "api-url", Aliases: []string{"u"}, Value: "http://127.0.0.1:4000", }, &cli.StringFlag{ Name: "directory", Aliases: []string{"d"}, Value: ".", }, }, Usage: "complete a task on the list", Action: func(ctx *cli.Context) error { listen := ctx.String("listen") port := ctx.Int64("port") directory := strings.TrimSuffix(ctx.String("directory"), "/") url := strings.TrimSuffix(ctx.String("api-url"), "/") e := echo.New() if ctx.Bool("debug") { e.Use(middleware.Logger()) e.Use(middleware.Recover()) } e.GET("/api/list", func(ctx echo.Context) error { return apiList(ctx, directory, url) }) e.GET("/api/stream", func(ctx echo.Context) error { return apiStream(ctx, directory, url) }) e.Logger.Fatal(e.Start(fmt.Sprintf("%s:%d", listen, port))) return nil }, }, }, } if err := app.Run(os.Args); err != nil { fmt.Println(err) } } type File struct { Name string `json:"name"` Url string `json:"url"` Path string `json:"-"` RelativePath string `json:"-"` Head []byte `json:"-"` ContentType string `json:"-"` } func (f *File) GenerateHeadAndContentType() { fo, _ := os.Open(f.Path) head := make([]byte, 261) fo.Read(head) f.Head = head f.ContentType = http.DetectContentType(head) fo.Close() } func (f File) IsSupported() bool { return filetype.IsVideo(f.Head) } type ApiError struct { Error string `json:"error"` } func getFiles(directory, url string) ([]File, error) { files := []File{} absoluteRootPath, err := filepath.Abs(directory) if err != nil { return nil, err } err = filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } basename := string(info.Name()) relativePath := strings.Replace(path, absoluteRootPath, "", 1) file := File{ Name: basename, Path: path, RelativePath: relativePath, Url: fmt.Sprintf("%s/api/stream?path=%s", url, relativePath), } file.GenerateHeadAndContentType() if file.IsSupported() { files = append(files, file) } return nil }) if err != nil { return nil, err } return files, nil } func apiStream(e echo.Context, directory, url string) error { files, err := getFiles(directory, url) path := e.QueryParam("path") if err != nil { return e.JSON(http.StatusInternalServerError, ApiError{Error: fmt.Sprintf("%s", err)}) } fmt.Printf("%+v\n", path) if path == "" { return e.JSON(http.StatusBadRequest, ApiError{Error: "\"path\" query param is missing"}) } for _, file := range files { if file.RelativePath == path { stat, _ := os.Stat(file.Path) e.Response().Header().Add("Content-Length", string(stat.Size())) http.ServeFile(e.Response(), e.Request(), file.Path) } } return e.JSON(http.StatusNotFound, ApiError{Error: "file not found"}) } func apiList(e echo.Context, directory, url string) error { files, err := getFiles(directory, url) if err != nil { return e.JSON(http.StatusInternalServerError, ApiError{Error: fmt.Sprintf("%s", err)}) } return e.JSONPretty(http.StatusOK, files, " ") }