diff --git a/.env b/.env deleted file mode 100644 index d7b8f7b..0000000 --- a/.env +++ /dev/null @@ -1,4 +0,0 @@ -DEV=1 - -# go run test/github_server/main.go -# URL_LATEST_RELEASE=http://localhost:5678/releases/latest diff --git a/README.md b/README.md index 0e5e48e..bcd6599 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,12 @@ Usage of avrp: starts in dev mode, serves 'index.html' from current directory -dir string path to video files + -get-ffmpeg + downloads ffmpeg + -no-thumb + disables thumbnail generation + -port int + port to serve on (default 5000) (default 5000) -reset removes all configs, thumbnails & 'aframe-vr-player' files -sha string diff --git a/avrp.go b/avrp.go index 08990dc..b16a7c1 100644 --- a/avrp.go +++ b/avrp.go @@ -9,11 +9,10 @@ import ( "log" "os" + "github.com/mysterion/avrp/internal/dist" + "github.com/mysterion/avrp/internal/server" "github.com/mysterion/avrp/internal/thumbnails" "github.com/mysterion/avrp/internal/utils" - "github.com/mysterion/avrp/thirdparty" - "github.com/mysterion/avrp/web/api" - "github.com/mysterion/avrp/web/dist" ) func isPathValid(path string) bool { @@ -46,20 +45,43 @@ func main() { var sha string var update bool var reset bool + var port int + var getFfmpeg bool + var noThumb bool flag.BoolVar(&utils.DEV, "dev", false, "starts in dev mode, serves 'index.html' from current directory") flag.BoolVar(&update, "update", false, "checks & downloads the latest version(commit) of 'aframe-vr-player'") flag.StringVar(&sha, "sha", "latest", "Optional - download a specific commit of aframe-vr-player") flag.StringVar(&servDir, "dir", "", "path to video files") flag.BoolVar(&reset, "reset", false, "removes all configs, thumbnails & 'aframe-vr-player' files") + flag.IntVar(&port, "port", 5000, "port to serve on (default 5000)") + flag.BoolVar(&getFfmpeg, "get-ffmpeg", false, "downloads ffmpeg") + flag.BoolVar(&noThumb, "no-thumb", false, "disables thumbnail generation") flag.Parse() utils.Init() dist.Init() - thirdparty.Init() thumbnails.Init() + // commands 👇 + + if getFfmpeg { + utils.Panic(thumbnails.DownloadFfmpeg()) + return + } + + if noThumb { + if thumbnails.NoFfmpeg() { + thumbnails.NoFfmpegFileRemove() + log.Println("thumbnail generation enabled👍") + } else { + thumbnails.NoFfmpegFileCreate() + log.Println("thumbnail generation disabled👎") + } + return + } + if reset { err := os.RemoveAll(utils.ConfigDir) if err != nil { @@ -72,10 +94,10 @@ func main() { if update { dist.Update(sha) + return } - /// do i need this anymore? - utils.GoRunGatekeeper() + // commands 👆 // running for the first time if !dist.Valid() && !utils.DEV { @@ -90,6 +112,6 @@ func main() { } } - api.Init(servDir) - api.Start(5000) + server.Init(servDir) + server.Start(port) } diff --git a/bake b/bake deleted file mode 100755 index 13afbae..0000000 --- a/bake +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash - -# nsfu -cd "$(dirname "$0")" -if [ -f ".env" ]; then - export $(grep -v '^#' .env | xargs -d '\n') -fi - -function nze() { - if [[ $1 -ne 0 ]]; then - exit 1 - fi -} - -which go -nze - -set -x - -if [ "$1" = "run" ]; then - go run avrp.go - -elif [ "$1" = "play" ]; then - go run cmd/playground/playground.go - -elif [ "$1" = "build" ]; then - mkdir -p build/thirdparty - go build -o build/ avrp.go - - cp -r thirdparty/*.md build/thirdparty/ - -elif [ "$1" = "buildffmpeg" ]; then - mkdir -p build/thirdparty - go build -o build/ avrp.go - - cp -r thirdparty/ffmpeg* thirdparty/ffprobe* build/thirdparty/ - rm build/thirdparty/*.go - -elif [ "$1" = "testserver" ]; then - go run test/github_server/main.go - - -elif [ "$1" = "test" ]; then - go test -v ${@:2} - -elif [ "$1" = "clean" ]; then - rm -rf build/ - -else - set +x - echo "Shiver me timbers!!.. Invalid arrghh - $1" - exit 1 -fi diff --git a/build/thirdparty/README.md b/build/thirdparty/README.md deleted file mode 100644 index 37c370d..0000000 --- a/build/thirdparty/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Download FFMPEG Static Binaries and place 'em here. - -[https://ffmpeg.org/download.html](https://ffmpeg.org/download.html) \ No newline at end of file diff --git a/cmd/playground/playground.go b/cmd/playground/playground.go deleted file mode 100644 index 287e56f..0000000 --- a/cmd/playground/playground.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - "log" -) - -func main() { - log.SetFlags(log.Default().Flags() | log.Lshortfile) -} diff --git a/go.mod b/go.mod index 653784b..377571d 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module github.com/mysterion/avrp -go 1.22.3 - -require github.com/rs/cors v1.11.0 // indirect +go 1.23 diff --git a/go.sum b/go.sum index 0679f02..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +0,0 @@ -github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= -github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= diff --git a/web/dist/dist.go b/internal/dist/dist.go similarity index 94% rename from web/dist/dist.go rename to internal/dist/dist.go index 99d0718..5544615 100644 --- a/web/dist/dist.go +++ b/internal/dist/dist.go @@ -1,37 +1,37 @@ -package dist - -import ( - "log" - "os" - "path/filepath" - - "github.com/mysterion/avrp/internal/utils" -) - -var VersionFile string - -func Init() { - VersionFile = filepath.Join(utils.ConfigDir, "VERSION") -} - -// checks if dist is present -func Valid() bool { - _, err := os.Stat(filepath.Join(utils.DistDir, "index.html")) - return err == nil -} - -func Delete() error { - return os.RemoveAll(utils.DistDir) -} - -// returns sha of aframe-vr-player dist -func Ver() string { - d, err := os.ReadFile(VersionFile) - - if err != nil { - log.Printf("WARN: while reading dist version, %v\n", err.Error()) - return "" - } - - return string(d) -} +package dist + +import ( + "log" + "os" + "path/filepath" + + "github.com/mysterion/avrp/internal/utils" +) + +var VersionFile string + +func Init() { + VersionFile = filepath.Join(utils.ConfigDir, "VERSION") +} + +// checks if dist is present +func Valid() bool { + _, err := os.Stat(filepath.Join(utils.DistDir, "index.html")) + return err == nil +} + +func Delete() error { + return os.RemoveAll(utils.DistDir) +} + +// returns sha of aframe-vr-player dist +func Ver() string { + d, err := os.ReadFile(VersionFile) + + if err != nil { + log.Printf("WARN: while reading dist version, %v\n", err.Error()) + return "" + } + + return string(d) +} diff --git a/web/dist/download.go b/internal/dist/download.go similarity index 94% rename from web/dist/download.go rename to internal/dist/download.go index e5023b8..129cdf1 100644 --- a/web/dist/download.go +++ b/internal/dist/download.go @@ -1,105 +1,105 @@ -package dist - -import ( - "archive/zip" - "fmt" - "io" - "log" - "net/http" - "os" - "path/filepath" - "strings" - - "github.com/mysterion/avrp/internal/utils" -) - -func DownloadCommit(sha string) error { - - url := fmt.Sprintf("https://api.github.com/repos/%s/%s/zipball/%s", RepoOwner, RepoName, sha) - log.Printf("Downloading - %v\n", url) - - zipFile, err := os.CreateTemp("", "aframe-vr-player") - if err != nil { - return err - } - defer zipFile.Close() - - log.Println("Downloading to - ", zipFile.Name()) - - resp, err := http.Get(url) - if err != nil { - return err - } - defer resp.Body.Close() - - _, err = io.Copy(zipFile, resp.Body) - if err != nil { - return err - } - - err = extractZip(sha, zipFile.Name()) - if err != nil { - log.Println("ERR: Failed to extract the zip") - return err - } - - err = os.WriteFile(VersionFile, []byte(sha), 0644) - - if err != nil { - log.Println("ERR: Failed to write version file") - return err - } - - log.Println("Extracted successfully") - - log.Println("Updating `last updated` file") - - return nil -} - -func extractZip(sha string, srcZip string) error { - - log.Printf("Extracting %v", srcZip) - r, err := zip.OpenReader(srcZip) - if err != nil { - return err - } - defer r.Close() - - err = os.MkdirAll(utils.ConfigDir, 0755) - if err != nil { - return err - } - - folderName := fmt.Sprintf("%s-%s-%s", RepoOwner, RepoName, sha[:7]) - - for _, f := range r.File { - fi := f.FileInfo() - target := filepath.Join(utils.ConfigDir, strings.Replace(f.Name, folderName, "dist", 1)) - log.Println("Extracting - ", target) - if !fi.IsDir() { - err := os.MkdirAll(filepath.Dir(target), 0755) - if err != nil { - return err - } - - file, err := os.Create(target) - if err != nil { - return err - } - - eFile, err := f.Open() - if err != nil { - return err - } - defer eFile.Close() - - _, err = io.Copy(file, eFile) - if err != nil { - return err - } - } - } - - return nil -} +package dist + +import ( + "archive/zip" + "fmt" + "io" + "log" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/mysterion/avrp/internal/utils" +) + +func DownloadCommit(sha string) error { + + url := fmt.Sprintf("https://api.github.com/repos/%s/%s/zipball/%s", RepoOwner, RepoName, sha) + log.Printf("Downloading - %v\n", url) + + zipFile, err := os.CreateTemp("", "aframe-vr-player") + if err != nil { + return err + } + defer zipFile.Close() + + log.Println("Downloading to - ", zipFile.Name()) + + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + _, err = io.Copy(zipFile, resp.Body) + if err != nil { + return err + } + + err = extractZip(sha, zipFile.Name()) + if err != nil { + log.Println("ERR: Failed to extract the zip") + return err + } + + err = os.WriteFile(VersionFile, []byte(sha), 0644) + + if err != nil { + log.Println("ERR: Failed to write version file") + return err + } + + log.Println("Extracted successfully") + + log.Println("Updating `last updated` file") + + return nil +} + +func extractZip(sha string, srcZip string) error { + + log.Printf("Extracting %v", srcZip) + r, err := zip.OpenReader(srcZip) + if err != nil { + return err + } + defer r.Close() + + err = os.MkdirAll(utils.ConfigDir, 0755) + if err != nil { + return err + } + + folderName := fmt.Sprintf("%s-%s-%s", RepoOwner, RepoName, sha[:7]) + + for _, f := range r.File { + fi := f.FileInfo() + target := filepath.Join(utils.ConfigDir, strings.Replace(f.Name, folderName, "dist", 1)) + log.Println("Extracting - ", target) + if !fi.IsDir() { + err := os.MkdirAll(filepath.Dir(target), 0755) + if err != nil { + return err + } + + file, err := os.Create(target) + if err != nil { + return err + } + + eFile, err := f.Open() + if err != nil { + return err + } + defer eFile.Close() + + _, err = io.Copy(file, eFile) + if err != nil { + return err + } + } + } + + return nil +} diff --git a/web/dist/update.go b/internal/dist/update.go similarity index 95% rename from web/dist/update.go rename to internal/dist/update.go index b70ed05..a93065c 100644 --- a/web/dist/update.go +++ b/internal/dist/update.go @@ -1,149 +1,149 @@ -package dist - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "io/fs" - "log" - "net/http" - "time" -) - -const RepoOwner = "mysterion" -const RepoName = "aframe-vr-player" - -// curl https://api.github.com/repos/mysterion/aframe-vr-player/commits?per_page=1 -type Commit struct { - Sha string `json:"sha"` - Details CommitDetails `json:"commit"` -} - -type CommitDetails struct { - Sha string `json:"sha"` - Author struct { - Name string `json:"name"` - Date time.Time `json:"date"` - } `json:"author"` - Message string `json:"message"` -} - -func GetLatestCommit() (Commit, error) { - - url := fmt.Sprintf("https://api.github.com/repos/%s/%s/commits?per_page=1", RepoOwner, RepoName) - - var commit Commit - var commits []Commit - - client := &http.Client{} - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return commit, err - } - - req.Header.Set("Accept", "application/json") - - resp, err := client.Do(req) - if err != nil { - return commit, err - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return commit, err - } - err = json.Unmarshal(body, &commits) - if err != nil { - return commit, err - } - if len(commits) < 1 { - return commit, fmt.Errorf("No commits found 🤨") - } - return commits[0], err -} - -func GetCommit(sha string) (CommitDetails, error) { - url := fmt.Sprintf("https://api.github.com/repos/%s/%s/git/commits/%s", RepoOwner, RepoName, sha) - - var commit CommitDetails - - client := &http.Client{} - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return commit, err - } - - req.Header.Set("Accept", "application/json") - - resp, err := client.Do(req) - if err != nil { - return commit, err - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return commit, err - } - err = json.Unmarshal(body, &commit) - if err != nil { - return commit, err - } - return commit, nil -} - -func Update(sha string) error { - - if sha == "latest" { - v := Ver() - log.Printf("Current version: %v\n", v) - log.Println("Checking for updates") - c, err := GetLatestCommit() - if err != nil { - log.Println("ERR: Failed to fetch latest version", err) - return err - } - - log.Printf("Commit Details :-\n\nDate: %v\nMessage:\n%v\n\n", c.Details.Author.Date, c.Details.Message) - - if Valid() { - if v == c.Sha { - o := "this" - if sha == "latest" { - o = "the latest" - } - log.Printf("Already on %s version\n", o) - return nil - } - } - - sha = c.Sha - - } else { - c, err := GetCommit(sha) - if err != nil { - log.Printf("ERR: Failed to get %s of aframe-vr-player\n", sha) - log.Printf("ERR: %v\n", err.Error()) - return err - } - log.Println(c, err) - log.Printf("Commit Details :-\n\nDate: %v\nMessage:\n%v\n\n", c.Author.Date, c.Message) - } - - fmt.Println("\n\nPress Enter to update") - fmt.Scanln() - - err := DownloadCommit(sha) - if err != nil { - log.Println("ERR: Failed to fetch latest Release", err) - if !Valid() { - if err := Delete(); errors.Is(err, fs.ErrNotExist) { - panic(err) - } - } - return err - } - return nil -} +package dist + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "log" + "net/http" + "time" +) + +const RepoOwner = "mysterion" +const RepoName = "aframe-vr-player" + +// curl https://api.github.com/repos/mysterion/aframe-vr-player/commits?per_page=1 +type Commit struct { + Sha string `json:"sha"` + Details CommitDetails `json:"commit"` +} + +type CommitDetails struct { + Sha string `json:"sha"` + Author struct { + Name string `json:"name"` + Date time.Time `json:"date"` + } `json:"author"` + Message string `json:"message"` +} + +func GetLatestCommit() (Commit, error) { + + url := fmt.Sprintf("https://api.github.com/repos/%s/%s/commits?per_page=1", RepoOwner, RepoName) + + var commit Commit + var commits []Commit + + client := &http.Client{} + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return commit, err + } + + req.Header.Set("Accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + return commit, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return commit, err + } + err = json.Unmarshal(body, &commits) + if err != nil { + return commit, err + } + if len(commits) < 1 { + return commit, fmt.Errorf("No commits found 🤨") + } + return commits[0], err +} + +func GetCommit(sha string) (CommitDetails, error) { + url := fmt.Sprintf("https://api.github.com/repos/%s/%s/git/commits/%s", RepoOwner, RepoName, sha) + + var commit CommitDetails + + client := &http.Client{} + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return commit, err + } + + req.Header.Set("Accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + return commit, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return commit, err + } + err = json.Unmarshal(body, &commit) + if err != nil { + return commit, err + } + return commit, nil +} + +func Update(sha string) error { + + if sha == "latest" { + v := Ver() + log.Printf("Current version: %v\n", v) + log.Println("Checking for updates") + c, err := GetLatestCommit() + if err != nil { + log.Println("ERR: Failed to fetch latest version", err) + return err + } + + log.Printf("Commit Details :-\n\nDate: %v\nMessage:\n%v\n\n", c.Details.Author.Date, c.Details.Message) + + if Valid() { + if v == c.Sha { + o := "this" + if sha == "latest" { + o = "the latest" + } + log.Printf("Already on %s version\n", o) + return nil + } + } + + sha = c.Sha + + } else { + c, err := GetCommit(sha) + if err != nil { + log.Printf("ERR: Failed to get %s of aframe-vr-player\n", sha) + log.Printf("ERR: %v\n", err.Error()) + return err + } + log.Println(c, err) + log.Printf("Commit Details :-\n\nDate: %v\nMessage:\n%v\n\n", c.Author.Date, c.Message) + } + + fmt.Println("\n\nPress Enter to update") + fmt.Scanln() + + err := DownloadCommit(sha) + if err != nil { + log.Println("ERR: Failed to fetch latest Release", err) + if !Valid() { + if err := Delete(); errors.Is(err, fs.ErrNotExist) { + panic(err) + } + } + return err + } + return nil +} diff --git a/internal/logg/logg.go b/internal/logg/logg.go deleted file mode 100644 index 71aed9b..0000000 --- a/internal/logg/logg.go +++ /dev/null @@ -1,37 +0,0 @@ -package logg - -import ( - "log" - - "github.com/mysterion/avrp/internal/utils" -) - -func Info(s ...any) { - var v []any - v = append(v, "INFO: ") - v = append(v, s...) - log.Println(v...) -} - -func Warn(s ...any) { - var v []any - v = append(v, "WARNING: ") - v = append(v, s...) - log.Println(v...) -} - -func Error(s ...any) { - var v []any - v = append(v, "ERROR: ") - v = append(v, s...) - log.Println(v...) -} - -func Debug(s ...any) { - if utils.DEV { - var v []any - v = append(v, "DEBUG: ") - v = append(v, s...) - log.Println(v...) - } -} diff --git a/web/api/certs/cert.pem b/internal/server/certs/cert.pem similarity index 98% rename from web/api/certs/cert.pem rename to internal/server/certs/cert.pem index 193e0c1..f7915ea 100644 --- a/web/api/certs/cert.pem +++ b/internal/server/certs/cert.pem @@ -1,33 +1,33 @@ ------BEGIN CERTIFICATE----- -MIIFnTCCA4WgAwIBAgIUNmG+My915s9o1EoSwLG0uyxgyFcwDQYJKoZIhvcNAQEL -BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAoM -CW15c3RlcmlvbjELMAkGA1UECwwCVlIxGTAXBgNVBAMMEGFmcmFtZS12ci1wbGF5 -ZXIwHhcNMjQwNTEwMDg0NzE0WhcNMzQwNTA4MDg0NzE0WjBeMQswCQYDVQQGEwJB -VTETMBEGA1UECAwKU29tZS1TdGF0ZTESMBAGA1UECgwJbXlzdGVyaW9uMQswCQYD -VQQLDAJWUjEZMBcGA1UEAwwQYWZyYW1lLXZyLXBsYXllcjCCAiIwDQYJKoZIhvcN -AQEBBQADggIPADCCAgoCggIBAKnR4NheDVcLqv3jBSBmtmSud9KrRUuJ4fppJp/M -7n2FVZvxKwKaWsIW5xeNwAW5vbQ2lhYixSPaxhKniOsociek6drDLUnN1DyWHngz -lZpxkzkUl4WF6Nh+V2oYNSMNr0YI74Zb/jFPMZDTqNs7761iRH6i/aWlGmCJQm3U -Ra6U8J4LuMT0tNZLC4tT8GAxEIVg3eUXtahoaslMhsAzMmEPIGzPP+vecNOVrKwo -noeIzT0tYWhgtWdYe9ayjg/R2+ioBIBUaxt9FsDTDCvfOHAVQ0lluFU/5uCcESOI -w8QM9kpvfj6vlEUweys1ycRRxRcus/SOMShhOtt7sCoK+IU3RLSOlzYZgjxuMpZi -gm9N8ebYrVXPRSfF4evwdU46k6Xx1nOMCw8KwT2aqEYif7ImFIdik0/5+TB47Ezr -66Bwe8Cu0NFdyiQz8MvASPGGkAlPKUOO+8wQJCkFc1nWnkqATQ5IyvmSgj/dVc9W -oBmKy6aArDEEOrj8TucbJ/J6IuUGPGPMQJCzasWewRav7JZ97LWn96PdwYejSM1t -PLxzukbPZ/o4IFVG/sbwC6UCDgofcEz5yjgqwF1BeKiXbi0CmOAGRYdfpc44lYIS -SHNmfz0BW/i/Gu4wJerrucBZS/I0uUj9Qawwrf6BZkTJz5fvXs7YyC8iT4DGKmiu -vxQjAgMBAAGjUzBRMB0GA1UdDgQWBBRC8T4jL8WzeBYu9+HYkFur79xFmTAfBgNV -HSMEGDAWgBRC8T4jL8WzeBYu9+HYkFur79xFmTAPBgNVHRMBAf8EBTADAQH/MA0G -CSqGSIb3DQEBCwUAA4ICAQByX2j0bUa8umQ43ZJd6Yr78yQ31ERcnL6dtWiYSspn -7AkySTwq94oTKWPqKMbmjPwANHxRX5MopD/5y2dMVU+08z0hExgNk3AyFL43E4xT -CRXGx7rcqhwzQRYwDsOWfcUFTi2yCA6x8C08Xh0gZ1kMBB4L8Ms2rzTEjTAAK2C/ -KK70htUj1jF4EZjkq/WX3qcaLxcn8lnuveRYu0RryVabxr+ENiBJvs+XinklUYIh -8C32u70x2/uq7e3/D6ir811JDA2F0dFoGawjTnKRGFn6vpB1GkJvlwecgeemT829 -9/tzW9aoyEDqVxJbvfgyOY+UBraHRuWOy0gSWrgIKhPbQeZjJTW+sFOpyjxF1oN/ -QVnGAPkySCwInrwI6jol1jbWQzUmN8Zk12Gp+GWxMp0xP9v/OqfAPs+VwOn5OPQT -SXB/dngDvrf58c7FljjUZU10/g+T6/Vh1M1NwDc8d55+Ogzc3GvD6msd0TSxmtGQ -Bas75YYpEH1m8dCxn/hgQ1lcbYwdlVz0zlSI2rV3gHirPxDGlcWOy21DLpw79KCg -hFl3gY1AKEGd7WhHAPISm4nLyW/V795jqVFCo2B08QMWYVBfocl2CgqUKEWU+REl -pRYIqtCPNNjBpVpHDqY21nHx7PIa+lKLlhoQPzzGqUbHtefnml3a20LcTkjFeGS+ -VQ== ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFnTCCA4WgAwIBAgIUNmG+My915s9o1EoSwLG0uyxgyFcwDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAoM +CW15c3RlcmlvbjELMAkGA1UECwwCVlIxGTAXBgNVBAMMEGFmcmFtZS12ci1wbGF5 +ZXIwHhcNMjQwNTEwMDg0NzE0WhcNMzQwNTA4MDg0NzE0WjBeMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTESMBAGA1UECgwJbXlzdGVyaW9uMQswCQYD +VQQLDAJWUjEZMBcGA1UEAwwQYWZyYW1lLXZyLXBsYXllcjCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAKnR4NheDVcLqv3jBSBmtmSud9KrRUuJ4fppJp/M +7n2FVZvxKwKaWsIW5xeNwAW5vbQ2lhYixSPaxhKniOsociek6drDLUnN1DyWHngz +lZpxkzkUl4WF6Nh+V2oYNSMNr0YI74Zb/jFPMZDTqNs7761iRH6i/aWlGmCJQm3U +Ra6U8J4LuMT0tNZLC4tT8GAxEIVg3eUXtahoaslMhsAzMmEPIGzPP+vecNOVrKwo +noeIzT0tYWhgtWdYe9ayjg/R2+ioBIBUaxt9FsDTDCvfOHAVQ0lluFU/5uCcESOI +w8QM9kpvfj6vlEUweys1ycRRxRcus/SOMShhOtt7sCoK+IU3RLSOlzYZgjxuMpZi +gm9N8ebYrVXPRSfF4evwdU46k6Xx1nOMCw8KwT2aqEYif7ImFIdik0/5+TB47Ezr +66Bwe8Cu0NFdyiQz8MvASPGGkAlPKUOO+8wQJCkFc1nWnkqATQ5IyvmSgj/dVc9W +oBmKy6aArDEEOrj8TucbJ/J6IuUGPGPMQJCzasWewRav7JZ97LWn96PdwYejSM1t +PLxzukbPZ/o4IFVG/sbwC6UCDgofcEz5yjgqwF1BeKiXbi0CmOAGRYdfpc44lYIS +SHNmfz0BW/i/Gu4wJerrucBZS/I0uUj9Qawwrf6BZkTJz5fvXs7YyC8iT4DGKmiu +vxQjAgMBAAGjUzBRMB0GA1UdDgQWBBRC8T4jL8WzeBYu9+HYkFur79xFmTAfBgNV +HSMEGDAWgBRC8T4jL8WzeBYu9+HYkFur79xFmTAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4ICAQByX2j0bUa8umQ43ZJd6Yr78yQ31ERcnL6dtWiYSspn +7AkySTwq94oTKWPqKMbmjPwANHxRX5MopD/5y2dMVU+08z0hExgNk3AyFL43E4xT +CRXGx7rcqhwzQRYwDsOWfcUFTi2yCA6x8C08Xh0gZ1kMBB4L8Ms2rzTEjTAAK2C/ +KK70htUj1jF4EZjkq/WX3qcaLxcn8lnuveRYu0RryVabxr+ENiBJvs+XinklUYIh +8C32u70x2/uq7e3/D6ir811JDA2F0dFoGawjTnKRGFn6vpB1GkJvlwecgeemT829 +9/tzW9aoyEDqVxJbvfgyOY+UBraHRuWOy0gSWrgIKhPbQeZjJTW+sFOpyjxF1oN/ +QVnGAPkySCwInrwI6jol1jbWQzUmN8Zk12Gp+GWxMp0xP9v/OqfAPs+VwOn5OPQT +SXB/dngDvrf58c7FljjUZU10/g+T6/Vh1M1NwDc8d55+Ogzc3GvD6msd0TSxmtGQ +Bas75YYpEH1m8dCxn/hgQ1lcbYwdlVz0zlSI2rV3gHirPxDGlcWOy21DLpw79KCg +hFl3gY1AKEGd7WhHAPISm4nLyW/V795jqVFCo2B08QMWYVBfocl2CgqUKEWU+REl +pRYIqtCPNNjBpVpHDqY21nHx7PIa+lKLlhoQPzzGqUbHtefnml3a20LcTkjFeGS+ +VQ== +-----END CERTIFICATE----- diff --git a/web/api/certs/key.pem b/internal/server/certs/key.pem similarity index 98% rename from web/api/certs/key.pem rename to internal/server/certs/key.pem index cd576eb..07eb568 100644 --- a/web/api/certs/key.pem +++ b/internal/server/certs/key.pem @@ -1,52 +1,52 @@ ------BEGIN PRIVATE KEY----- -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 -kjpd0TjX4fbYHPRuLvqwl7Zjm3aUBg== ------END PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +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 +kjpd0TjX4fbYHPRuLvqwl7Zjm3aUBg== +-----END PRIVATE KEY----- diff --git a/web/api/handlers.go b/internal/server/handlers.go similarity index 87% rename from web/api/handlers.go rename to internal/server/handlers.go index dbfd9a5..e42c73b 100644 --- a/web/api/handlers.go +++ b/internal/server/handlers.go @@ -1,122 +1,113 @@ -package api - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - "os" - "path" - "path/filepath" - - "github.com/mysterion/avrp/internal/thumbnails" - "github.com/mysterion/avrp/internal/utils" - "github.com/rs/cors" -) - -type File struct { - Name string `json:"name"` - Duration int `json:"duration"` -} - -type ListData struct { - Files []File `json:"files"` - Folders []string `json:"folders"` -} - -func wrapCors(h http.HandlerFunc) func(http.ResponseWriter, *http.Request) { - ret := func(w http.ResponseWriter, r *http.Request) { - cors.AllowAll().HandlerFunc(w, r) - h(w, r) - } - return ret -} - -const listPath = "/list/" - -func listHandler(w http.ResponseWriter, r *http.Request) { - listDir, err := url.PathUnescape(filepath.Join(servDir, filepath.FromSlash(r.URL.Path[len(listPath):]))) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - files, folders, err := listFilesAndFolders(listDir) - if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - listData := ListData{ - Files: files, - Folders: folders, - } - jsonData, err := json.Marshal(listData) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(jsonData) -} - -func listFilesAndFolders(dirPath string) ([]File, []string, error) { - entries, err := os.ReadDir(dirPath) - if err != nil { - return nil, nil, err - } - files := make([]File, 0) - folders := make([]string, 0) - for _, entry := range entries { - if entry.IsDir() { - folders = append(folders, entry.Name()) - } else { - if !utils.CanServe(entry.Name()) { - continue - } - - secs, _ := thumbnails.GetDuration(path.Join(dirPath, entry.Name())) - - files = append(files, File{Name: entry.Name(), Duration: int(secs)}) - } - } - return files, folders, nil -} - -const thumbPath = "/thumb/" - -func thumbHandler(w http.ResponseWriter, r *http.Request) { - - if !thumbnails.Available { - w.WriteHeader(http.StatusServiceUnavailable) - fmt.Fprintln(w, "Thumbnails not available for now. Check logs for more info.") - return - } - - id := r.URL.Query().Get("id") - file, err := url.PathUnescape(filepath.Join(servDir, filepath.FromSlash(r.URL.Path[len(thumbPath):]))) - - if id == "" || err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - _, err = os.Stat(file) - if err != nil { - w.WriteHeader(http.StatusNotFound) - fmt.Fprintln(w, "Not Found") - return - } - - if !thumbnails.Generated(file) { - thumbnails.Generate(file) - } - - p, err := thumbnails.Get(id, file) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintln(w, err.Error()) - return - } - - http.ServeFile(w, r, p) -} +package server + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + + "github.com/mysterion/avrp/internal/thumbnails" + "github.com/mysterion/avrp/internal/utils" +) + +type File struct { + Name string `json:"name"` + Duration int `json:"duration"` +} + +type ListData struct { + Files []File `json:"files"` + Folders []string `json:"folders"` +} + +const listPath = "/list/" + +func listHandler(w http.ResponseWriter, r *http.Request) { + listDir, err := url.PathUnescape(filepath.Join(servDir, filepath.FromSlash(r.URL.Path[len(listPath):]))) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + files, folders, err := listFilesAndFolders(listDir) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + listData := ListData{ + Files: files, + Folders: folders, + } + jsonData, err := json.Marshal(listData) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(jsonData) +} + +func listFilesAndFolders(dirPath string) ([]File, []string, error) { + entries, err := os.ReadDir(dirPath) + if err != nil { + return nil, nil, err + } + files := make([]File, 0) + folders := make([]string, 0) + for _, entry := range entries { + if entry.IsDir() { + folders = append(folders, entry.Name()) + } else { + if !utils.CanServe(entry.Name()) { + continue + } + + secs, _ := thumbnails.GetDuration(path.Join(dirPath, entry.Name())) + + files = append(files, File{Name: entry.Name(), Duration: int(secs)}) + } + } + return files, folders, nil +} + +const thumbPath = "/thumb/" + +func thumbHandler(w http.ResponseWriter, r *http.Request) { + + if !thumbnails.Available { + w.WriteHeader(http.StatusServiceUnavailable) + fmt.Fprintln(w, "Thumbnails not available for now. Check logs for more info.") + return + } + + id := r.URL.Query().Get("id") + file, err := url.PathUnescape(filepath.Join(servDir, filepath.FromSlash(r.URL.Path[len(thumbPath):]))) + + if id == "" || err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + _, err = os.Stat(file) + if err != nil { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintln(w, "Not Found") + return + } + + if !thumbnails.Generated(file) { + thumbnails.Generate(file) + } + + p, err := thumbnails.Get(id, file) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintln(w, err.Error()) + return + } + + http.ServeFile(w, r, p) +} diff --git a/web/api/init.go b/internal/server/init.go similarity index 80% rename from web/api/init.go rename to internal/server/init.go index 7bc43b4..d243b67 100644 --- a/web/api/init.go +++ b/internal/server/init.go @@ -1,4 +1,4 @@ -package api +package server var servDir string diff --git a/web/api/server.go b/internal/server/server.go similarity index 61% rename from web/api/server.go rename to internal/server/server.go index 073ec6e..7bfd1c0 100644 --- a/web/api/server.go +++ b/internal/server/server.go @@ -1,148 +1,166 @@ -package api - -import ( - "context" - "crypto/tls" - "embed" - "fmt" - "log" - "net" - "net/http" - "os" - "os/signal" - "time" - - "github.com/mysterion/avrp/internal/utils" - "github.com/mysterion/avrp/web" - "github.com/rs/cors" -) - -//go:embed certs/* -var Certs embed.FS - -func GetTlsConfig() (*tls.Config, error) { - config := &tls.Config{ - MinVersion: tls.VersionTLS12, - PreferServerCipherSuites: true, - Certificates: make([]tls.Certificate, 1), - } - - certBlock, err := Certs.ReadFile("certs/cert.pem") - if err != nil { - return nil, err - } - - keyBlock, err := Certs.ReadFile("certs/key.pem") - if err != nil { - return nil, err - } - - config.Certificates[0], err = tls.X509KeyPair(certBlock, keyBlock) - if err != nil { - return nil, err - } - - return config, nil -} - -// port = 0, for a random port -// -// mux can be nil -// -// tlsConfig can be nil -// -// returns [<-ready], [<-done], [*http.Server] -func New(port int, mux http.Handler, tlsConfig *tls.Config) (<-chan bool, <-chan bool, *http.Server) { - - ready := make(chan bool, 1) - done := make(chan bool, 1) - - server := http.Server{ - Handler: mux, - TLSConfig: tlsConfig, - } - - go func() { - defer close(ready) - defer close(done) - - listener, err := net.Listen("tcp4", fmt.Sprintf("%s:%d", "0.0.0.0", port)) - utils.Panic(err) - - server.Addr = listener.Addr().String() - - ready <- true - - err = server.ServeTLS(listener, "", "") - - if err != nil && err != http.ErrServerClosed { - fmt.Println(err.Error()) - panic(err) - } - }() - - return ready, done, &server -} - -func Start(port int) { - tlsConfig, err := GetTlsConfig() - if err != nil { - panic(err) - } - - mux := http.NewServeMux() - - const filePath = "/file/" - var fileHandler = http.StripPrefix(filePath, http.FileServer(http.Dir(servDir))) - - const distPath = "/" - var distHandler = http.FileServer(http.Dir(utils.DistDir)) - - listH := listHandler - thumbH := thumbHandler - - distH := distHandler - fileH := fileHandler - - if utils.DEV { - log.Println("Enabling CORS") - listH = wrapCors(listHandler) - thumbH = wrapCors(thumbHandler) - - distH = cors.AllowAll().Handler(distHandler) - fileH = cors.AllowAll().Handler(fileHandler) - } - - mux.Handle(distPath, distH) - mux.HandleFunc(listPath, listH) - mux.HandleFunc(thumbPath, thumbH) - mux.Handle(filePath, fileH) - - sigint := make(chan os.Signal, 1) - signal.Notify(sigint, os.Interrupt) - - ready, done, s := New(port, mux, tlsConfig) - <-ready - - fmt.Println("Server listening on: ") - for _, ip := range web.GetIps() { - fmt.Printf("https://%v:%v\n", ip, port) - } - <-sigint - - ctx, cancel := context.WithDeadline(context.TODO(), time.Now().Add(time.Second*3)) - defer cancel() - - s.Shutdown(ctx) - - log.Println("Shutting down..") - - select { - case <-done: - log.Println("bye") - case <-ctx.Done(): - log.Println("shutdown request timedout..") - log.Println("ok..") - } - -} +package server + +import ( + "context" + "crypto/tls" + "embed" + "fmt" + "log" + "net" + "net/http" + "os" + "os/signal" + "time" + + "github.com/mysterion/avrp/internal/utils" +) + +//go:embed certs/* +var certs embed.FS + +func getIps() []string { + if utils.IsGoRun() { + return []string{"127.0.0.1"} + } + ips := make([]string, 0) + ifaces, err := net.Interfaces() + if err != nil { + panic(err) + } + for _, i := range ifaces { + addrs, err := i.Addrs() + if err != nil { + panic(err) + } + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if ip.To4() != nil && (ip.IsPrivate() || ip.IsLoopback()) { + ips = append(ips, ip.String()) + } + } + } + return ips +} + +func getTlsConfig() (*tls.Config, error) { + config := &tls.Config{ + MinVersion: tls.VersionTLS12, + PreferServerCipherSuites: true, + Certificates: make([]tls.Certificate, 1), + } + + certBlock, err := certs.ReadFile("certs/cert.pem") + if err != nil { + return nil, err + } + + keyBlock, err := certs.ReadFile("certs/key.pem") + if err != nil { + return nil, err + } + + config.Certificates[0], err = tls.X509KeyPair(certBlock, keyBlock) + if err != nil { + return nil, err + } + + return config, nil +} + +// port = 0, for a random port +// +// mux can be nil +// +// tlsConfig can be nil +// +// returns [<-ready], [<-done], [*http.Server] +func new(port int, mux http.Handler, tlsConfig *tls.Config) (<-chan bool, <-chan bool, *http.Server) { + + ready := make(chan bool, 1) + done := make(chan bool, 1) + + server := http.Server{ + Handler: mux, + TLSConfig: tlsConfig, + } + + go func() { + defer close(ready) + defer close(done) + + url := "0.0.0.0" + if utils.IsGoRun() { + url = "127.0.0.1" + } + + listener, err := net.Listen("tcp4", fmt.Sprintf("%s:%d", url, port)) + utils.Panic(err) + + server.Addr = listener.Addr().String() + + ready <- true + + err = server.ServeTLS(listener, "", "") + + if err != nil && err != http.ErrServerClosed { + fmt.Println(err.Error()) + panic(err) + } + }() + + return ready, done, &server +} + +func Start(port int) { + tlsConfig, err := getTlsConfig() + if err != nil { + panic(err) + } + + mux := http.NewServeMux() + + const filePath = "/file/" + var fileHandler = http.StripPrefix(filePath, http.FileServer(http.Dir(servDir))) + mux.Handle(filePath, fileHandler) + + const distPath = "/" + var distHandler = http.FileServer(http.Dir(utils.DistDir)) + mux.Handle(distPath, distHandler) + + mux.HandleFunc(listPath, listHandler) + mux.HandleFunc(thumbPath, thumbHandler) + + sigint := make(chan os.Signal, 1) + signal.Notify(sigint, os.Interrupt) + + ready, done, s := new(port, mux, tlsConfig) + <-ready + + fmt.Println("Server listening on: ") + for _, ip := range getIps() { + fmt.Printf("https://%v:%v\n", ip, port) + } + <-sigint + + ctx, cancel := context.WithDeadline(context.TODO(), time.Now().Add(time.Second*3)) + defer cancel() + + s.Shutdown(ctx) + + log.Println("Shutting down..") + + select { + case <-done: + log.Println("bye") + case <-ctx.Done(): + log.Println("shutdown request timedout..") + log.Println("ok..") + } + +} diff --git a/internal/thumbnails/ffmpeg.go b/internal/thumbnails/ffmpeg.go new file mode 100644 index 0000000..2188e51 --- /dev/null +++ b/internal/thumbnails/ffmpeg.go @@ -0,0 +1,254 @@ +package thumbnails + +import ( + "archive/zip" + "bufio" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "strings" + "time" + + "github.com/mysterion/avrp/internal/utils" +) + +var ( + ErrNotImpl = errors.New("not implemented") + ErrNoDownload = errors.New("no download found in the latest release") +) + +var ( + binFfmpeg string + binFfprobe string +) + +var ( + noffmpegfile string + ffmpegDir = "" +) + +type release struct { + ID int `json:"id"` + Name string `json:"name"` + Tag string `json:"tag_name"` + URL string `json:"html_url"` + Assets []struct { + ID int `json:"id"` + Size int `json:"size"` + CreatedAt time.Time `json:"created_at"` + BrowserDownloadUrl string `json:"browser_download_url"` + } `json:"assets"` +} + +func latestFfmpeg() (release, error) { + repoOwner := "GyanD" + repoName := "codexffmpeg" + var r release + + url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", repoOwner, repoName) + + client := &http.Client{} + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return r, err + } + + req.Header.Set("Accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + return r, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return r, err + } + + err = json.Unmarshal(body, &r) + if err != nil { + return r, err + } + + return r, nil +} + +func DownloadFfmpeg() error { + if runtime.GOOS != "windows" || runtime.GOARCH != "amd64" { + fmt.Println("\nPlease install ffmpeg from your package manager and make sure its in $PATH") + return ErrNotImpl + } + + r, err := latestFfmpeg() + + if err != nil { + log.Printf("ERR: Fetching latest ffmpeg release - %s", err.Error()) + return err + } + + if len(r.Assets) == 0 { + return ErrNoDownload + } + + url := "" + + for _, a := range r.Assets { + if strings.Contains(a.BrowserDownloadUrl, "essentials") && + strings.HasSuffix(a.BrowserDownloadUrl, ".zip") { + url = a.BrowserDownloadUrl + break + } + } + + if url == "" { + return ErrNoDownload + } + + zipName := path.Base(url) + zipPath := filepath.Join(os.TempDir(), zipName) + + zipFile, err := os.Create(zipPath) + if err != nil { + return err + } + defer zipFile.Close() + + log.Println("Downloading to - ", zipPath) + + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + _, err = io.Copy(zipFile, resp.Body) + if err != nil { + return err + } + + err = extractFfmpeg(zipName, zipPath) + if err != nil { + log.Println("ERR: Failed to extract the zip") + return err + } + + log.Println("Extracted successfully") + + if NoFfmpeg() { + NoFfmpegFileRemove() + } + + return nil + +} + +func extractFfmpeg(zipName string, zipPath string) error { + + log.Printf("Extracting %v", zipPath) + r, err := zip.OpenReader(zipPath) + if err != nil { + return err + } + defer r.Close() + + folderName := strings.TrimSuffix(zipName, filepath.Ext(zipName)) + + for _, f := range r.File { + fi := f.FileInfo() + target := filepath.Join(utils.ConfigDir, strings.Replace(f.Name, folderName, "ffmpeg", 1)) + log.Println("Extracting - ", target) + if !fi.IsDir() { + err := os.MkdirAll(filepath.Dir(target), 0755) + if err != nil { + return err + } + + file, err := os.Create(target) + if err != nil { + return err + } + + eFile, err := f.Open() + if err != nil { + return err + } + defer eFile.Close() + + _, err = io.Copy(file, eFile) + if err != nil { + return err + } + } + } + + return nil +} + +func promptDownloadFfmpeg() bool { + fmt.Println("\nffmpeg is required to show thumbnails in aframe-vr-player") + fmt.Print("Download ffmpeg? \"yes\" or \"no\": ") + scanner := bufio.NewScanner(os.Stdin) + scanner.Scan() + ans := strings.ToLower(strings.TrimSpace(scanner.Text())) + return ans == "yes" +} + +func CheckFfmpegInPath() (bool, string, string) { + + ffmpeg := "ffmpeg" + ffprobe := "ffprobe" + + if runtime.GOOS == "windows" { + ffmpeg += ".exe" + ffprobe += ".exe" + } + + ffmpegPath, err1 := exec.LookPath(ffmpeg) + + ffprobePath, err2 := exec.LookPath(ffprobe) + + return err1 == nil && err2 == nil, ffmpegPath, ffprobePath +} + +func CheckFfmpeg() (bool, string, string) { + + ffmpeg := filepath.Join("bin", "ffmpeg") + ffprobe := filepath.Join("bin", "ffprobe") + + if runtime.GOOS == "windows" { + ffmpeg += ".exe" + ffprobe += ".exe" + } + + ffmpegPath := filepath.Join(ffmpegDir, ffmpeg) + ffprobePath := filepath.Join(ffmpegDir, ffprobe) + + _, err1 := os.Stat(ffmpegPath) + _, err2 := os.Stat(ffprobePath) + + return err1 == nil && err2 == nil, ffmpegPath, ffprobePath +} + +func NoFfmpegFileCreate() { + _, err := os.Create(noffmpegfile) + utils.Panic(err) +} + +func NoFfmpegFileRemove() { + err := os.Remove(noffmpegfile) + utils.Panic(err) +} + +func NoFfmpeg() bool { + _, err := os.Stat(noffmpegfile) + return err == nil +} diff --git a/internal/thumbnails/hash.go b/internal/thumbnails/hash.go index 074c7b5..4f4a78d 100644 --- a/internal/thumbnails/hash.go +++ b/internal/thumbnails/hash.go @@ -9,10 +9,6 @@ import ( "github.com/mysterion/avrp/internal/cache" ) -func logg(args ...string) { - -} - func Hash(file string) (string, error) { c := cache.Get("HASH_" + file) diff --git a/internal/thumbnails/thumbnails.go b/internal/thumbnails/thumbnails.go index bd12925..40a1208 100644 --- a/internal/thumbnails/thumbnails.go +++ b/internal/thumbnails/thumbnails.go @@ -14,35 +14,71 @@ import ( "github.com/mysterion/avrp/internal/cache" "github.com/mysterion/avrp/internal/utils" - "github.com/mysterion/avrp/thirdparty" ) var thumbdir string -var ErrNotVideo = errors.New("not a video") +var ErrUnavailable = errors.New("not available") -var Available = true +var Available = false var muGen sync.Mutex func Init() { + noffmpegfile = filepath.Join(utils.ConfigDir, "noffmpeg") + thumbdir = filepath.Join(utils.ConfigDir, "thumbnails") err := os.MkdirAll(thumbdir, 0755) + if err != nil { + log.Printf("ERR: Thumbnails not available - %v\n", err) + return + } + ffmpegDir = filepath.Join(utils.ConfigDir, "ffmpeg") + err = os.MkdirAll(ffmpegDir, 0755) if err != nil { - log.Printf("Thumbnails not available - %v\n", err) - Available = false + log.Printf("ERR: Thumbnails not available - %v\n", err) + return + } + + if NoFfmpeg() { + return + } + + var found = false + found, binFfmpeg, binFfprobe = CheckFfmpegInPath() + if found { + Available = true + return + } + + found, binFfmpeg, binFfprobe = CheckFfmpeg() + if found { + Available = true + return } - if thirdparty.FfmpegBin == "" || thirdparty.FfprobeBin == "" { - log.Printf("Thumbnails not available - ffmpeg not found\n") - Available = false + accept := promptDownloadFfmpeg() + + if !accept { + fmt.Printf("\n\nYou can disable this message, by running: avrp --no-thumb\n\n") + return + } + + utils.Panic(DownloadFfmpeg()) + + found, binFfmpeg, binFfprobe = CheckFfmpeg() + if found { + Available = true + return + } else { + log.Println("Something went wrong, please re-download ffmpeg: avrp --get-ffmpeg") } } func GetDuration(file string) (float64, error) { - if !utils.IsVideo(file) { - return 0, ErrNotVideo + if !utils.IsVideo(file) || !Available { + return 0, ErrUnavailable } var secs string secs = cache.Get("DUR_" + file) @@ -54,7 +90,7 @@ func GetDuration(file string) (float64, error) { "-of", "default=noprint_wrappers=1:nokey=1", file, } - cmd := exec.Command(thirdparty.FfprobeBin, args...) + cmd := exec.Command(binFfprobe, args...) stdout, err := cmd.CombinedOutput() if err != nil { log.Printf("ERR - Failed to get Duration for %v - %v\nSTDOUT:\n%s\n", file, err, stdout) @@ -70,6 +106,10 @@ func GetDuration(file string) (float64, error) { func Generated(file string) bool { + if !Available { + return false + } + h, err := Hash(file) if err != nil { @@ -91,6 +131,9 @@ func Generated(file string) bool { // TODO: keep error state for a particular file with eviction policy func Generate(file string) { + if !Available { + return + } muGen.Lock() defer muGen.Unlock() if Generated(file) { @@ -129,8 +172,7 @@ func Generate(file string) { "-vf", "crop=in_w/2:in_h/2:in_w:in_h/4,scale=320:-1", filepath.Join(outDir, fmt.Sprintf("%v.jpg", i)), } - cmd := exec.Command(thirdparty.FfmpegBin, cmdArgs...) - log.Println("EXEC - ", thirdparty.FfmpegBin, cmdArgs) + cmd := exec.Command(binFfmpeg, cmdArgs...) stdout, err := cmd.CombinedOutput() if err != nil { log.Printf("ERR while generating thumbnail %vth for %v - %v\nSTDOUT:\n%v\n", i, file, err, string(stdout)) @@ -146,6 +188,9 @@ func Generate(file string) { } func Get(id string, file string) (string, error) { + if !Available { + return "", ErrUnavailable + } h, err := Hash(file) if err != nil { return "", err diff --git a/internal/utils/utils.go b/internal/utils/utils.go index f5cdf48..066ba93 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -4,6 +4,7 @@ import ( "log" "os" "path/filepath" + "runtime" "strings" ) @@ -51,14 +52,13 @@ func Init() { } func Panic(err error) { + _, filename, lno, _ := runtime.Caller(1) if err != nil { + log.Printf("Panic call from : %v:%v\n", filename, lno) panic(err) } } -func GoRunGatekeeper() { - if strings.HasPrefix(AppDir, filepath.Join(os.TempDir(), "go-build")) { - panic("MAYBE YOU FORGOT DEV=1 ? I'M NOT LETTING YOU RUN STUFF FROM TEMP DIRECTORY") - } - +func IsGoRun() bool { + return strings.HasPrefix(AppDir, filepath.Join(os.TempDir(), "go-build")) } diff --git a/thirdparty/README.md b/thirdparty/README.md deleted file mode 100644 index 37c370d..0000000 --- a/thirdparty/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Download FFMPEG Static Binaries and place 'em here. - -[https://ffmpeg.org/download.html](https://ffmpeg.org/download.html) \ No newline at end of file diff --git a/thirdparty/ffmpeg.go b/thirdparty/ffmpeg.go deleted file mode 100644 index d7916fa..0000000 --- a/thirdparty/ffmpeg.go +++ /dev/null @@ -1,65 +0,0 @@ -package thirdparty - -import ( - "os" - "os/exec" - "path/filepath" - "runtime" - - "github.com/mysterion/avrp/internal/logg" - "github.com/mysterion/avrp/internal/utils" -) - -// empty if ffmpeg is not found in system -var FfmpegBin = "" - -// empty if ffprobe is not found in system -var FfprobeBin = "" - -func Init() { - - ffmpeg := "ffmpeg" - ffprobe := "ffprobe" - - if runtime.GOOS == "windows" { - ffmpeg += ".exe" - ffprobe += ".exe" - } - - // check in distribution - FfmpegBin = filepath.Join(utils.AppDir, "thirdparty", ffmpeg) - _, err := os.Stat(FfmpegBin) - if err != nil { - logg.Debug(err) - FfmpegBin = "" - } - FfprobeBin = filepath.Join(utils.AppDir, "thirdparty", ffprobe) - _, err = os.Stat(FfprobeBin) - if err != nil { - logg.Debug(err) - FfprobeBin = "" - } - - // check in PATH - if FfmpegBin == "" { - ffmpegPath, err := exec.LookPath(ffmpeg) - if err == nil { - FfmpegBin = ffmpegPath - } else { - logg.Debug(err) - } - - } - if FfprobeBin == "" { - ffprobePath, err := exec.LookPath(ffprobe) - - if err == nil { - FfprobeBin = ffprobePath - } else { - logg.Debug(err) - } - } - - logg.Debug("Ffmpeg", FfmpegBin) - logg.Debug("Ffprobe", FfprobeBin) -} diff --git a/web/ip.go b/web/ip.go deleted file mode 100644 index 9529ef0..0000000 --- a/web/ip.go +++ /dev/null @@ -1,44 +0,0 @@ -package web - -import ( - "fmt" - "net" -) - -func GetOutboundIp() net.IP { - conn, err := net.Dial("udp", "8.8.8.8:80") - if err != nil { - fmt.Println(err) - } - defer conn.Close() - - localAddr := conn.LocalAddr().(*net.UDPAddr) - return localAddr.IP -} - -func GetIps() []string { - ips := make([]string, 0) - ifaces, err := net.Interfaces() - if err != nil { - panic(err) - } - for _, i := range ifaces { - addrs, err := i.Addrs() - if err != nil { - panic(err) - } - for _, addr := range addrs { - var ip net.IP - switch v := addr.(type) { - case *net.IPNet: - ip = v.IP - case *net.IPAddr: - ip = v.IP - } - if ip.To4() != nil && (ip.IsPrivate() || ip.IsLoopback()) { - ips = append(ips, ip.String()) - } - } - } - return ips -}