Spaces:
Paused
Paused
keshav solanki commited on
Commit ·
48b8079
0
Parent(s):
Initial clean commit with LFS tracking
Browse files- .gitattributes +36 -0
- README.md +10 -0
- build.sh +12 -0
- downloader-service +3 -0
- go.mod +44 -0
- go.sum +133 -0
- main.go +461 -0
- start.sh +14 -0
- templates/index.html +425 -0
.gitattributes
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
downloader-service filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Decompress Dl
|
| 3 |
+
emoji: 📚
|
| 4 |
+
colorFrom: red
|
| 5 |
+
colorTo: red
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
build.sh
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# Build script for Hugging Face Space
|
| 4 |
+
echo "Building Go application..."
|
| 5 |
+
|
| 6 |
+
# Install dependencies
|
| 7 |
+
go mod download
|
| 8 |
+
|
| 9 |
+
# Build the binary
|
| 10 |
+
go build -o downloader-service .
|
| 11 |
+
|
| 12 |
+
echo "Build complete!"
|
downloader-service
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:64c802d0cccd0d6619adfd96e551552d2dbc8dd99a358a16fa319ccd61d5504f
|
| 3 |
+
size 29087714
|
go.mod
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
module downloader-service
|
| 2 |
+
|
| 3 |
+
go 1.25.1
|
| 4 |
+
|
| 5 |
+
require (
|
| 6 |
+
github.com/aws/aws-sdk-go v1.55.8 // indirect
|
| 7 |
+
github.com/bytedance/gopkg v0.1.3 // indirect
|
| 8 |
+
github.com/bytedance/sonic v1.14.2 // indirect
|
| 9 |
+
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
| 10 |
+
github.com/cloudwego/base64x v0.1.6 // indirect
|
| 11 |
+
github.com/gabriel-vasile/mimetype v1.4.11 // indirect
|
| 12 |
+
github.com/gin-contrib/sse v1.1.0 // indirect
|
| 13 |
+
github.com/gin-gonic/gin v1.11.0 // indirect
|
| 14 |
+
github.com/go-playground/locales v0.14.1 // indirect
|
| 15 |
+
github.com/go-playground/universal-translator v0.18.1 // indirect
|
| 16 |
+
github.com/go-playground/validator/v10 v10.28.0 // indirect
|
| 17 |
+
github.com/goccy/go-json v0.10.5 // indirect
|
| 18 |
+
github.com/goccy/go-yaml v1.19.0 // indirect
|
| 19 |
+
github.com/google/uuid v1.6.0 // indirect
|
| 20 |
+
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
| 21 |
+
github.com/json-iterator/go v1.1.12 // indirect
|
| 22 |
+
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
| 23 |
+
github.com/leodido/go-urn v1.4.0 // indirect
|
| 24 |
+
github.com/mattn/go-isatty v0.0.20 // indirect
|
| 25 |
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
| 26 |
+
github.com/modern-go/reflect2 v1.0.2 // indirect
|
| 27 |
+
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
| 28 |
+
github.com/quic-go/qpack v0.6.0 // indirect
|
| 29 |
+
github.com/quic-go/quic-go v0.57.1 // indirect
|
| 30 |
+
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
| 31 |
+
github.com/u2takey/ffmpeg-go v0.5.0 // indirect
|
| 32 |
+
github.com/u2takey/go-utils v0.3.1 // indirect
|
| 33 |
+
github.com/ugorji/go/codec v1.3.1 // indirect
|
| 34 |
+
go.uber.org/mock v0.6.0 // indirect
|
| 35 |
+
golang.org/x/arch v0.23.0 // indirect
|
| 36 |
+
golang.org/x/crypto v0.45.0 // indirect
|
| 37 |
+
golang.org/x/mod v0.30.0 // indirect
|
| 38 |
+
golang.org/x/net v0.47.0 // indirect
|
| 39 |
+
golang.org/x/sync v0.18.0 // indirect
|
| 40 |
+
golang.org/x/sys v0.38.0 // indirect
|
| 41 |
+
golang.org/x/text v0.31.0 // indirect
|
| 42 |
+
golang.org/x/tools v0.39.0 // indirect
|
| 43 |
+
google.golang.org/protobuf v1.36.10 // indirect
|
| 44 |
+
)
|
go.sum
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
| 2 |
+
github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
|
| 3 |
+
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
|
| 4 |
+
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
| 5 |
+
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
| 6 |
+
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
|
| 7 |
+
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
|
| 8 |
+
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
|
| 9 |
+
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
| 10 |
+
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
| 11 |
+
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
| 12 |
+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
| 13 |
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
| 14 |
+
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
| 15 |
+
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
| 16 |
+
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
|
| 17 |
+
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
| 18 |
+
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
| 19 |
+
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
| 20 |
+
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
| 21 |
+
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
| 22 |
+
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
| 23 |
+
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
| 24 |
+
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
| 25 |
+
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
| 26 |
+
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
| 27 |
+
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
| 28 |
+
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
| 29 |
+
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
| 30 |
+
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
| 31 |
+
github.com/goccy/go-yaml v1.19.0 h1:EmkZ9RIsX+Uq4DYFowegAuJo8+xdX3T/2dwNPXbxEYE=
|
| 32 |
+
github.com/goccy/go-yaml v1.19.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
| 33 |
+
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
| 34 |
+
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
| 35 |
+
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
| 36 |
+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
| 37 |
+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
| 38 |
+
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
| 39 |
+
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
| 40 |
+
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
| 41 |
+
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
| 42 |
+
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
| 43 |
+
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
| 44 |
+
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
| 45 |
+
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
| 46 |
+
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
| 47 |
+
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
| 48 |
+
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
| 49 |
+
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
| 50 |
+
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
| 51 |
+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
| 52 |
+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
| 53 |
+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
| 54 |
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
| 55 |
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
| 56 |
+
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
| 57 |
+
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
| 58 |
+
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
| 59 |
+
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
| 60 |
+
github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
|
| 61 |
+
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
| 62 |
+
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
| 63 |
+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
| 64 |
+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
| 65 |
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
| 66 |
+
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
| 67 |
+
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
| 68 |
+
github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10=
|
| 69 |
+
github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
| 70 |
+
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
| 71 |
+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
| 72 |
+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
| 73 |
+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
| 74 |
+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
| 75 |
+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
| 76 |
+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
| 77 |
+
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
| 78 |
+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
| 79 |
+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
| 80 |
+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
| 81 |
+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
| 82 |
+
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
| 83 |
+
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
| 84 |
+
github.com/u2takey/ffmpeg-go v0.5.0 h1:r7d86XuL7uLWJ5mzSeQ03uvjfIhiJYvsRAJFCW4uklU=
|
| 85 |
+
github.com/u2takey/ffmpeg-go v0.5.0/go.mod h1:ruZWkvC1FEiUNjmROowOAps3ZcWxEiOpFoHCvk97kGc=
|
| 86 |
+
github.com/u2takey/go-utils v0.3.1 h1:TaQTgmEZZeDHQFYfd+AdUT1cT4QJgJn/XVPELhHw4ys=
|
| 87 |
+
github.com/u2takey/go-utils v0.3.1/go.mod h1:6e+v5vEZ/6gu12w/DC2ixZdZtCrNokVxD0JUklcqdCs=
|
| 88 |
+
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
| 89 |
+
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
| 90 |
+
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
| 91 |
+
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
| 92 |
+
gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs=
|
| 93 |
+
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
| 94 |
+
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
| 95 |
+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
| 96 |
+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
| 97 |
+
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
| 98 |
+
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
| 99 |
+
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
| 100 |
+
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
| 101 |
+
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
| 102 |
+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
| 103 |
+
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
| 104 |
+
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
| 105 |
+
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
| 106 |
+
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
| 107 |
+
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
| 108 |
+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
| 109 |
+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 110 |
+
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 111 |
+
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 112 |
+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 113 |
+
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
| 114 |
+
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
| 115 |
+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
| 116 |
+
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
| 117 |
+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
| 118 |
+
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
| 119 |
+
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
| 120 |
+
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
| 121 |
+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
| 122 |
+
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
| 123 |
+
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
| 124 |
+
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
| 125 |
+
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
| 126 |
+
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
| 127 |
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
| 128 |
+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
| 129 |
+
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
| 130 |
+
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
| 131 |
+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
| 132 |
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
| 133 |
+
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
main.go
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package main
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"archive/tar"
|
| 5 |
+
"archive/zip"
|
| 6 |
+
"compress/gzip"
|
| 7 |
+
"fmt"
|
| 8 |
+
"io"
|
| 9 |
+
"mime"
|
| 10 |
+
"net/http"
|
| 11 |
+
"os"
|
| 12 |
+
"os/exec"
|
| 13 |
+
"path/filepath"
|
| 14 |
+
"strings"
|
| 15 |
+
"sync"
|
| 16 |
+
|
| 17 |
+
"github.com/gin-gonic/gin"
|
| 18 |
+
"github.com/google/uuid"
|
| 19 |
+
ffmpeg "github.com/u2takey/ffmpeg-go"
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
// --- Data Structures ---
|
| 23 |
+
type JobStatus string
|
| 24 |
+
|
| 25 |
+
const (
|
| 26 |
+
StatusPending JobStatus = "pending"
|
| 27 |
+
StatusProcessing JobStatus = "processing"
|
| 28 |
+
StatusCompleted JobStatus = "completed"
|
| 29 |
+
StatusFailed JobStatus = "failed"
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
type Job struct {
|
| 33 |
+
ID string `json:"id"`
|
| 34 |
+
Type string `json:"type"`
|
| 35 |
+
Status JobStatus `json:"status"`
|
| 36 |
+
Details string `json:"details"`
|
| 37 |
+
ResultURL string `json:"result_url,omitempty"` // URL to download the result
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
var jobStore = sync.Map{}
|
| 41 |
+
|
| 42 |
+
// --- Helper Functions ---
|
| 43 |
+
|
| 44 |
+
func updateJob(id string, status JobStatus, details string, resultURL string) {
|
| 45 |
+
val, ok := jobStore.Load(id)
|
| 46 |
+
if !ok {
|
| 47 |
+
return
|
| 48 |
+
}
|
| 49 |
+
job := val.(Job)
|
| 50 |
+
job.Status = status
|
| 51 |
+
job.Details = details
|
| 52 |
+
if resultURL != "" {
|
| 53 |
+
job.ResultURL = resultURL
|
| 54 |
+
}
|
| 55 |
+
jobStore.Store(id, job)
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
// Security: Prevent Zip Slip
|
| 59 |
+
func sanitizePath(dest, path string) (string, error) {
|
| 60 |
+
fpath := filepath.Join(dest, path)
|
| 61 |
+
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
|
| 62 |
+
return "", fmt.Errorf("illegal file path: %s", path)
|
| 63 |
+
}
|
| 64 |
+
return fpath, nil
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
// --- Decompression Logic ---
|
| 68 |
+
func unzipSource(source, dest string) error {
|
| 69 |
+
r, err := zip.OpenReader(source)
|
| 70 |
+
if err != nil {
|
| 71 |
+
return err
|
| 72 |
+
}
|
| 73 |
+
defer r.Close()
|
| 74 |
+
os.MkdirAll(dest, 0755)
|
| 75 |
+
|
| 76 |
+
for _, f := range r.File {
|
| 77 |
+
fpath, err := sanitizePath(dest, f.Name)
|
| 78 |
+
if err != nil {
|
| 79 |
+
return err
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
if f.FileInfo().IsDir() {
|
| 83 |
+
os.MkdirAll(fpath, os.ModePerm)
|
| 84 |
+
continue
|
| 85 |
+
}
|
| 86 |
+
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
| 87 |
+
return err
|
| 88 |
+
}
|
| 89 |
+
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
| 90 |
+
if err != nil {
|
| 91 |
+
return err
|
| 92 |
+
}
|
| 93 |
+
rc, err := f.Open()
|
| 94 |
+
if err != nil {
|
| 95 |
+
outFile.Close()
|
| 96 |
+
return err
|
| 97 |
+
}
|
| 98 |
+
_, err = io.Copy(outFile, rc)
|
| 99 |
+
outFile.Close()
|
| 100 |
+
rc.Close()
|
| 101 |
+
if err != nil {
|
| 102 |
+
return err
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
return nil
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
func untarSource(source, dest string) error {
|
| 109 |
+
f, err := os.Open(source)
|
| 110 |
+
if err != nil {
|
| 111 |
+
return err
|
| 112 |
+
}
|
| 113 |
+
defer f.Close()
|
| 114 |
+
gzr, err := gzip.NewReader(f)
|
| 115 |
+
if err != nil {
|
| 116 |
+
return err
|
| 117 |
+
}
|
| 118 |
+
defer gzr.Close()
|
| 119 |
+
tr := tar.NewReader(gzr)
|
| 120 |
+
os.MkdirAll(dest, 0755)
|
| 121 |
+
|
| 122 |
+
for {
|
| 123 |
+
header, err := tr.Next()
|
| 124 |
+
if err == io.EOF {
|
| 125 |
+
break
|
| 126 |
+
}
|
| 127 |
+
if err != nil {
|
| 128 |
+
return err
|
| 129 |
+
}
|
| 130 |
+
fpath, err := sanitizePath(dest, header.Name)
|
| 131 |
+
if err != nil {
|
| 132 |
+
return err
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
switch header.Typeflag {
|
| 136 |
+
case tar.TypeDir:
|
| 137 |
+
if err := os.MkdirAll(fpath, 0755); err != nil {
|
| 138 |
+
return err
|
| 139 |
+
}
|
| 140 |
+
case tar.TypeReg:
|
| 141 |
+
if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
|
| 142 |
+
return err
|
| 143 |
+
}
|
| 144 |
+
outFile, err := os.Create(fpath)
|
| 145 |
+
if err != nil {
|
| 146 |
+
return err
|
| 147 |
+
}
|
| 148 |
+
if _, err := io.Copy(outFile, tr); err != nil {
|
| 149 |
+
outFile.Close()
|
| 150 |
+
return err
|
| 151 |
+
}
|
| 152 |
+
outFile.Close()
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
return nil
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
func unrarSource(source, dest string) error {
|
| 159 |
+
os.MkdirAll(dest, 0755)
|
| 160 |
+
cmd := exec.Command("unrar", "x", "-y", source, dest+string(os.PathSeparator))
|
| 161 |
+
return cmd.Run()
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
// --- Download & Zip Logic ---
|
| 165 |
+
func downloadFile(url string, customName string, destFolder string) (string, error) {
|
| 166 |
+
resp, err := http.Get(url)
|
| 167 |
+
if err != nil {
|
| 168 |
+
return "", err
|
| 169 |
+
}
|
| 170 |
+
defer resp.Body.Close()
|
| 171 |
+
|
| 172 |
+
filename := customName
|
| 173 |
+
if filename == "" {
|
| 174 |
+
if cd := resp.Header.Get("Content-Disposition"); cd != "" {
|
| 175 |
+
_, params, err := mime.ParseMediaType(cd)
|
| 176 |
+
if err == nil {
|
| 177 |
+
filename = params["filename"]
|
| 178 |
+
}
|
| 179 |
+
}
|
| 180 |
+
if filename == "" {
|
| 181 |
+
filename = filepath.Base(resp.Request.URL.Path)
|
| 182 |
+
}
|
| 183 |
+
if filename == "" || filename == "." || filename == "/" {
|
| 184 |
+
filename = "download_" + uuid.New().String()
|
| 185 |
+
}
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
filename = filepath.Base(filename)
|
| 189 |
+
finalPath := filepath.Join(destFolder, filename)
|
| 190 |
+
out, err := os.Create(finalPath)
|
| 191 |
+
if err != nil {
|
| 192 |
+
return "", err
|
| 193 |
+
}
|
| 194 |
+
defer out.Close()
|
| 195 |
+
_, err = io.Copy(out, resp.Body)
|
| 196 |
+
return finalPath, err
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
func zipFile(sourcePath string, destFolder string) (string, error) {
|
| 200 |
+
filename := filepath.Base(sourcePath)
|
| 201 |
+
zipName := fmt.Sprintf("%s_%s.zip", strings.TrimSuffix(filename, filepath.Ext(filename)), uuid.New().String()[:8])
|
| 202 |
+
zipPath := filepath.Join(destFolder, zipName)
|
| 203 |
+
outFile, err := os.Create(zipPath)
|
| 204 |
+
if err != nil {
|
| 205 |
+
return "", err
|
| 206 |
+
}
|
| 207 |
+
defer outFile.Close()
|
| 208 |
+
w := zip.NewWriter(outFile)
|
| 209 |
+
defer w.Close()
|
| 210 |
+
srcFile, err := os.Open(sourcePath)
|
| 211 |
+
if err != nil {
|
| 212 |
+
return "", err
|
| 213 |
+
}
|
| 214 |
+
defer srcFile.Close()
|
| 215 |
+
f, err := w.Create(filename)
|
| 216 |
+
if err != nil {
|
| 217 |
+
return "", err
|
| 218 |
+
}
|
| 219 |
+
_, err = io.Copy(f, srcFile)
|
| 220 |
+
return zipPath, nil
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
// --- REMUX LOGIC (In-Place) ---
|
| 224 |
+
func remuxFile(relativePath string, container string, outputName string) (string, error) {
|
| 225 |
+
// Full path to source
|
| 226 |
+
sourcePath := filepath.Join("./downloads", relativePath)
|
| 227 |
+
|
| 228 |
+
// Determine directory of the source file
|
| 229 |
+
sourceDir := filepath.Dir(sourcePath)
|
| 230 |
+
|
| 231 |
+
// Determine Output Filename
|
| 232 |
+
filename := filepath.Base(sourcePath)
|
| 233 |
+
var finalName string
|
| 234 |
+
if outputName != "" {
|
| 235 |
+
if !strings.HasSuffix(outputName, "."+container) {
|
| 236 |
+
finalName = outputName + "." + container
|
| 237 |
+
} else {
|
| 238 |
+
finalName = outputName
|
| 239 |
+
}
|
| 240 |
+
} else {
|
| 241 |
+
finalName = strings.TrimSuffix(filename, filepath.Ext(filename)) + "." + container
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
// Output goes to same directory as source
|
| 245 |
+
outPath := filepath.Join(sourceDir, finalName)
|
| 246 |
+
|
| 247 |
+
// FFmpeg command: -c copy (Remuxing, no re-encoding)
|
| 248 |
+
err := ffmpeg.Input(sourcePath).
|
| 249 |
+
Output(outPath, ffmpeg.KwArgs{"c": "copy"}).
|
| 250 |
+
OverWriteOutput().
|
| 251 |
+
Run()
|
| 252 |
+
|
| 253 |
+
if err != nil {
|
| 254 |
+
return "", fmt.Errorf("ffmpeg error: %v", err)
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
// Return the relative path so the frontend can find it
|
| 258 |
+
relPath, _ := filepath.Rel("./downloads", outPath)
|
| 259 |
+
return relPath, nil
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
// --- Worker ---
|
| 263 |
+
func processJob(job Job, payload map[string]interface{}) {
|
| 264 |
+
updateJob(job.ID, StatusProcessing, "Starting...", "")
|
| 265 |
+
|
| 266 |
+
var err error
|
| 267 |
+
var resultPath string // Relative path inside ./downloads
|
| 268 |
+
|
| 269 |
+
switch job.Type {
|
| 270 |
+
case "download":
|
| 271 |
+
url := payload["url"].(string)
|
| 272 |
+
customName := payload["custom_name"].(string)
|
| 273 |
+
absPath, e := downloadFile(url, customName, "./downloads")
|
| 274 |
+
err = e
|
| 275 |
+
if err == nil {
|
| 276 |
+
resultPath = filepath.Base(absPath)
|
| 277 |
+
if val, ok := payload["auto_zip"]; ok && val.(bool) {
|
| 278 |
+
updateJob(job.ID, StatusProcessing, "Zipping...", "")
|
| 279 |
+
// Zip goes to downloads too for simplicity now? Or keep output?
|
| 280 |
+
// Let's keep existing zip logic but return that URL
|
| 281 |
+
zipP, zErr := zipFile(absPath, "./downloads")
|
| 282 |
+
if zErr == nil {
|
| 283 |
+
resultPath = filepath.Base(zipP)
|
| 284 |
+
}
|
| 285 |
+
}
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
case "remux":
|
| 289 |
+
// Payload: "filename" is actually the relative path (e.g. "360p/video.mp4")
|
| 290 |
+
relPath := payload["filename"].(string)
|
| 291 |
+
container := payload["container"].(string)
|
| 292 |
+
customOut, _ := payload["custom_out"].(string)
|
| 293 |
+
|
| 294 |
+
resultPath, err = remuxFile(relPath, container, customOut)
|
| 295 |
+
|
| 296 |
+
case "extract":
|
| 297 |
+
relPath := payload["filename"].(string)
|
| 298 |
+
sourcePath := filepath.Join("./downloads", relPath)
|
| 299 |
+
// Extract to: downloads/folder_name/
|
| 300 |
+
destFolder := strings.TrimSuffix(sourcePath, filepath.Ext(sourcePath))
|
| 301 |
+
|
| 302 |
+
ext := strings.ToLower(filepath.Ext(sourcePath))
|
| 303 |
+
updateJob(job.ID, StatusProcessing, "Extracting...", "")
|
| 304 |
+
|
| 305 |
+
if ext == ".zip" {
|
| 306 |
+
err = unzipSource(sourcePath, destFolder)
|
| 307 |
+
} else if ext == ".gz" || strings.HasSuffix(sourcePath, ".tar.gz") {
|
| 308 |
+
err = untarSource(sourcePath, destFolder)
|
| 309 |
+
} else if ext == ".rar" {
|
| 310 |
+
err = unrarSource(sourcePath, destFolder)
|
| 311 |
+
} else {
|
| 312 |
+
err = fmt.Errorf("unsupported format: %s", ext)
|
| 313 |
+
}
|
| 314 |
+
// Result path for extraction is the folder name
|
| 315 |
+
if err == nil {
|
| 316 |
+
resultPath, _ = filepath.Rel("./downloads", destFolder)
|
| 317 |
+
}
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
if err != nil {
|
| 321 |
+
updateJob(job.ID, StatusFailed, err.Error(), "")
|
| 322 |
+
} else {
|
| 323 |
+
// Public URL is /raw/ + relative path
|
| 324 |
+
publicURL := "/raw/" + resultPath
|
| 325 |
+
updateJob(job.ID, StatusCompleted, "Done", publicURL)
|
| 326 |
+
}
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
// --- Main ---
|
| 330 |
+
|
| 331 |
+
func main() {
|
| 332 |
+
r := gin.Default()
|
| 333 |
+
r.LoadHTMLGlob("templates/*")
|
| 334 |
+
|
| 335 |
+
os.MkdirAll("./downloads", 0755)
|
| 336 |
+
|
| 337 |
+
// Serve downloads folder directly
|
| 338 |
+
r.StaticFS("/raw", http.Dir("./downloads"))
|
| 339 |
+
|
| 340 |
+
r.GET("/", func(c *gin.Context) { c.HTML(http.StatusOK, "index.html", nil) })
|
| 341 |
+
|
| 342 |
+
// List files with dir support
|
| 343 |
+
r.GET("/api/files", func(c *gin.Context) {
|
| 344 |
+
reqDir := c.DefaultQuery("dir", "")
|
| 345 |
+
baseDir := "./downloads"
|
| 346 |
+
targetDir := filepath.Join(baseDir, reqDir)
|
| 347 |
+
|
| 348 |
+
if !strings.HasPrefix(filepath.Clean(targetDir), filepath.Clean(baseDir)) {
|
| 349 |
+
c.JSON(400, gin.H{"error": "Invalid directory"})
|
| 350 |
+
return
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
entries, err := os.ReadDir(targetDir)
|
| 354 |
+
if err != nil {
|
| 355 |
+
c.JSON(500, gin.H{"error": "Cannot read directory"})
|
| 356 |
+
return
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
var files []gin.H
|
| 360 |
+
if reqDir != "" {
|
| 361 |
+
parent := filepath.Dir(reqDir)
|
| 362 |
+
if parent == "." {
|
| 363 |
+
parent = ""
|
| 364 |
+
}
|
| 365 |
+
files = append(files, gin.H{"name": "..", "type": "dir", "path": parent})
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
for _, e := range entries {
|
| 369 |
+
info, _ := e.Info()
|
| 370 |
+
fileType := "file"
|
| 371 |
+
if e.IsDir() {
|
| 372 |
+
fileType = "dir"
|
| 373 |
+
}
|
| 374 |
+
relPath := filepath.Join(reqDir, e.Name())
|
| 375 |
+
files = append(files, gin.H{
|
| 376 |
+
"name": e.Name(), "size": info.Size(), "type": fileType, "path": relPath,
|
| 377 |
+
})
|
| 378 |
+
}
|
| 379 |
+
c.JSON(http.StatusOK, gin.H{"files": files, "current": reqDir})
|
| 380 |
+
})
|
| 381 |
+
|
| 382 |
+
r.DELETE("/api/files", func(c *gin.Context) {
|
| 383 |
+
relativePath := c.Query("path")
|
| 384 |
+
if relativePath == "" {
|
| 385 |
+
c.JSON(400, gin.H{"error": "path required"})
|
| 386 |
+
return
|
| 387 |
+
}
|
| 388 |
+
targetPath := filepath.Join("./downloads", relativePath)
|
| 389 |
+
if !strings.HasPrefix(filepath.Clean(targetPath), filepath.Clean("./downloads")) {
|
| 390 |
+
c.JSON(400, gin.H{"error": "Invalid path"})
|
| 391 |
+
return
|
| 392 |
+
}
|
| 393 |
+
err := os.RemoveAll(targetPath)
|
| 394 |
+
if err != nil {
|
| 395 |
+
c.JSON(500, gin.H{"error": err.Error()})
|
| 396 |
+
return
|
| 397 |
+
}
|
| 398 |
+
c.JSON(200, gin.H{"status": "deleted"})
|
| 399 |
+
})
|
| 400 |
+
|
| 401 |
+
r.POST("/api/download", func(c *gin.Context) {
|
| 402 |
+
var req struct {
|
| 403 |
+
URL string `json:"url"`
|
| 404 |
+
CustomName string `json:"custom_name"`
|
| 405 |
+
AutoZip bool `json:"auto_zip"`
|
| 406 |
+
}
|
| 407 |
+
if err := c.ShouldBindJSON(&req); err != nil {
|
| 408 |
+
c.JSON(400, gin.H{"error": err.Error()})
|
| 409 |
+
return
|
| 410 |
+
}
|
| 411 |
+
jobID := uuid.New().String()
|
| 412 |
+
job := Job{ID: jobID, Type: "download", Status: StatusPending}
|
| 413 |
+
jobStore.Store(jobID, job)
|
| 414 |
+
go processJob(job, map[string]interface{}{"url": req.URL, "custom_name": req.CustomName, "auto_zip": req.AutoZip})
|
| 415 |
+
c.JSON(202, gin.H{"job_id": jobID})
|
| 416 |
+
})
|
| 417 |
+
|
| 418 |
+
r.POST("/api/remux", func(c *gin.Context) {
|
| 419 |
+
var req struct {
|
| 420 |
+
Filename string `json:"filename"`
|
| 421 |
+
Container string `json:"container"`
|
| 422 |
+
CustomOut string `json:"custom_out"`
|
| 423 |
+
}
|
| 424 |
+
if err := c.ShouldBindJSON(&req); err != nil {
|
| 425 |
+
c.JSON(400, gin.H{"error": err.Error()})
|
| 426 |
+
return
|
| 427 |
+
}
|
| 428 |
+
jobID := uuid.New().String()
|
| 429 |
+
job := Job{ID: jobID, Type: "remux", Status: StatusPending}
|
| 430 |
+
jobStore.Store(jobID, job)
|
| 431 |
+
go processJob(job, map[string]interface{}{"filename": req.Filename, "container": req.Container, "custom_out": req.CustomOut})
|
| 432 |
+
c.JSON(202, gin.H{"job_id": jobID})
|
| 433 |
+
})
|
| 434 |
+
|
| 435 |
+
r.POST("/api/extract", func(c *gin.Context) {
|
| 436 |
+
var req struct {
|
| 437 |
+
Filename string `json:"filename"`
|
| 438 |
+
}
|
| 439 |
+
if err := c.ShouldBindJSON(&req); err != nil {
|
| 440 |
+
c.JSON(400, gin.H{"error": err.Error()})
|
| 441 |
+
return
|
| 442 |
+
}
|
| 443 |
+
jobID := uuid.New().String()
|
| 444 |
+
job := Job{ID: jobID, Type: "extract", Status: StatusPending}
|
| 445 |
+
jobStore.Store(jobID, job)
|
| 446 |
+
go processJob(job, map[string]interface{}{"filename": req.Filename})
|
| 447 |
+
c.JSON(202, gin.H{"job_id": jobID})
|
| 448 |
+
})
|
| 449 |
+
|
| 450 |
+
r.GET("/api/job/:id", func(c *gin.Context) {
|
| 451 |
+
id := c.Param("id")
|
| 452 |
+
if val, ok := jobStore.Load(id); ok {
|
| 453 |
+
c.JSON(200, val)
|
| 454 |
+
} else {
|
| 455 |
+
c.JSON(404, gin.H{"error": "Not found"})
|
| 456 |
+
}
|
| 457 |
+
})
|
| 458 |
+
|
| 459 |
+
fmt.Println("Running on http://localhost:8080")
|
| 460 |
+
r.Run(":8080")
|
| 461 |
+
}
|
start.sh
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# Hugging Face Space startup script
|
| 4 |
+
echo "Starting Hugging Face Space..."
|
| 5 |
+
|
| 6 |
+
# Build the application if it doesn't exist
|
| 7 |
+
if [ ! -f "downloader-service" ]; then
|
| 8 |
+
echo "Building application..."
|
| 9 |
+
./build.sh
|
| 10 |
+
fi
|
| 11 |
+
|
| 12 |
+
# Start the service
|
| 13 |
+
echo "Starting downloader service..."
|
| 14 |
+
./downloader-service
|
templates/index.html
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<title>Go Media Downloader</title>
|
| 6 |
+
<style>
|
| 7 |
+
body {
|
| 8 |
+
font-family:
|
| 9 |
+
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
| 10 |
+
Helvetica, Arial, sans-serif;
|
| 11 |
+
max-width: 900px;
|
| 12 |
+
margin: 2rem auto;
|
| 13 |
+
padding: 0 1rem;
|
| 14 |
+
background-color: #f4f6f8;
|
| 15 |
+
color: #333;
|
| 16 |
+
}
|
| 17 |
+
.card {
|
| 18 |
+
background: white;
|
| 19 |
+
border: 1px solid #e1e4e8;
|
| 20 |
+
padding: 1.5rem;
|
| 21 |
+
border-radius: 8px;
|
| 22 |
+
margin-bottom: 1rem;
|
| 23 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
| 24 |
+
}
|
| 25 |
+
h3 {
|
| 26 |
+
margin-top: 0;
|
| 27 |
+
color: #24292e;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
/* Inputs & Buttons */
|
| 31 |
+
.input-group {
|
| 32 |
+
display: flex;
|
| 33 |
+
gap: 10px;
|
| 34 |
+
margin-bottom: 10px;
|
| 35 |
+
}
|
| 36 |
+
input[type="text"] {
|
| 37 |
+
flex-grow: 1;
|
| 38 |
+
padding: 10px;
|
| 39 |
+
border: 1px solid #ccc;
|
| 40 |
+
border-radius: 6px;
|
| 41 |
+
}
|
| 42 |
+
select {
|
| 43 |
+
padding: 10px;
|
| 44 |
+
border: 1px solid #ccc;
|
| 45 |
+
border-radius: 6px;
|
| 46 |
+
background: white;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
button {
|
| 50 |
+
padding: 8px 14px;
|
| 51 |
+
cursor: pointer;
|
| 52 |
+
color: white;
|
| 53 |
+
border: none;
|
| 54 |
+
border-radius: 6px;
|
| 55 |
+
font-weight: 600;
|
| 56 |
+
transition: background 0.2s;
|
| 57 |
+
}
|
| 58 |
+
button.primary {
|
| 59 |
+
background: #0366d6;
|
| 60 |
+
}
|
| 61 |
+
button.primary:hover {
|
| 62 |
+
background: #005cc5;
|
| 63 |
+
}
|
| 64 |
+
button.secondary {
|
| 65 |
+
background: #6c757d;
|
| 66 |
+
}
|
| 67 |
+
button.secondary:hover {
|
| 68 |
+
background: #5a6268;
|
| 69 |
+
}
|
| 70 |
+
button.green {
|
| 71 |
+
background: #28a745;
|
| 72 |
+
}
|
| 73 |
+
button.green:hover {
|
| 74 |
+
background: #218838;
|
| 75 |
+
}
|
| 76 |
+
button.red {
|
| 77 |
+
background: #d73a49;
|
| 78 |
+
}
|
| 79 |
+
button.red:hover {
|
| 80 |
+
background: #c03440;
|
| 81 |
+
}
|
| 82 |
+
button.orange {
|
| 83 |
+
background: #fd7e14;
|
| 84 |
+
}
|
| 85 |
+
button.orange:hover {
|
| 86 |
+
background: #e36d0d;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
/* File List */
|
| 90 |
+
.file-item {
|
| 91 |
+
display: flex;
|
| 92 |
+
align-items: center;
|
| 93 |
+
justify-content: space-between;
|
| 94 |
+
padding: 12px;
|
| 95 |
+
border-bottom: 1px solid #eee;
|
| 96 |
+
}
|
| 97 |
+
.file-item:last-child {
|
| 98 |
+
border-bottom: none;
|
| 99 |
+
}
|
| 100 |
+
.file-item:hover {
|
| 101 |
+
background-color: #f8f9fa;
|
| 102 |
+
}
|
| 103 |
+
.dir-link {
|
| 104 |
+
color: #0366d6;
|
| 105 |
+
cursor: pointer;
|
| 106 |
+
font-weight: bold;
|
| 107 |
+
}
|
| 108 |
+
.dir-link:hover {
|
| 109 |
+
text-decoration: underline;
|
| 110 |
+
}
|
| 111 |
+
.file-actions {
|
| 112 |
+
display: flex;
|
| 113 |
+
gap: 8px;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
#jobResult {
|
| 117 |
+
margin-top: 15px;
|
| 118 |
+
padding: 15px;
|
| 119 |
+
background: #e6fffa;
|
| 120 |
+
border: 1px solid #b2f5ea;
|
| 121 |
+
border-radius: 6px;
|
| 122 |
+
display: none;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
/* Modal Styles */
|
| 126 |
+
.modal-overlay {
|
| 127 |
+
position: fixed;
|
| 128 |
+
top: 0;
|
| 129 |
+
left: 0;
|
| 130 |
+
width: 100%;
|
| 131 |
+
height: 100%;
|
| 132 |
+
background: rgba(0, 0, 0, 0.5);
|
| 133 |
+
display: none;
|
| 134 |
+
justify-content: center;
|
| 135 |
+
align-items: center;
|
| 136 |
+
z-index: 1000;
|
| 137 |
+
}
|
| 138 |
+
.modal {
|
| 139 |
+
background: white;
|
| 140 |
+
padding: 2rem;
|
| 141 |
+
border-radius: 8px;
|
| 142 |
+
width: 400px;
|
| 143 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
| 144 |
+
}
|
| 145 |
+
.modal h2 {
|
| 146 |
+
margin-top: 0;
|
| 147 |
+
}
|
| 148 |
+
.modal-buttons {
|
| 149 |
+
margin-top: 1.5rem;
|
| 150 |
+
display: flex;
|
| 151 |
+
justify-content: flex-end;
|
| 152 |
+
gap: 10px;
|
| 153 |
+
}
|
| 154 |
+
</style>
|
| 155 |
+
</head>
|
| 156 |
+
<body>
|
| 157 |
+
<h1>Media Processor</h1>
|
| 158 |
+
|
| 159 |
+
<!-- Download Section -->
|
| 160 |
+
<div class="card">
|
| 161 |
+
<h3>1. Download File</h3>
|
| 162 |
+
<div class="input-group">
|
| 163 |
+
<input
|
| 164 |
+
type="text"
|
| 165 |
+
id="urlInput"
|
| 166 |
+
placeholder="URL (e.g. https://example.com/file.zip)"
|
| 167 |
+
/>
|
| 168 |
+
</div>
|
| 169 |
+
<div class="input-group">
|
| 170 |
+
<input
|
| 171 |
+
type="text"
|
| 172 |
+
id="nameInput"
|
| 173 |
+
placeholder="Save as... (Optional, e.g. archive.zip)"
|
| 174 |
+
/>
|
| 175 |
+
</div>
|
| 176 |
+
<div style="margin-bottom: 10px">
|
| 177 |
+
<label
|
| 178 |
+
><input type="checkbox" id="autoZip" /> Zip file after
|
| 179 |
+
download</label
|
| 180 |
+
>
|
| 181 |
+
</div>
|
| 182 |
+
<button class="primary" onclick="startDownload()">
|
| 183 |
+
Start Download
|
| 184 |
+
</button>
|
| 185 |
+
<div id="jobResult"></div>
|
| 186 |
+
</div>
|
| 187 |
+
|
| 188 |
+
<!-- File List Section -->
|
| 189 |
+
<div class="card">
|
| 190 |
+
<div
|
| 191 |
+
style="
|
| 192 |
+
display: flex;
|
| 193 |
+
justify-content: space-between;
|
| 194 |
+
align-items: center;
|
| 195 |
+
"
|
| 196 |
+
>
|
| 197 |
+
<h3>2. Local Files</h3>
|
| 198 |
+
<button class="green" onclick="loadFiles(currentDir)">
|
| 199 |
+
Refresh
|
| 200 |
+
</button>
|
| 201 |
+
</div>
|
| 202 |
+
<div
|
| 203 |
+
id="currentPath"
|
| 204 |
+
style="font-family: monospace; color: #666; margin: 10px 0"
|
| 205 |
+
>
|
| 206 |
+
/downloads
|
| 207 |
+
</div>
|
| 208 |
+
<div id="fileList">Loading...</div>
|
| 209 |
+
</div>
|
| 210 |
+
|
| 211 |
+
<!-- REMUX MODAL -->
|
| 212 |
+
<div class="modal-overlay" id="remuxModal">
|
| 213 |
+
<div class="modal">
|
| 214 |
+
<h2>Remux File</h2>
|
| 215 |
+
<p
|
| 216 |
+
id="remuxTargetFile"
|
| 217 |
+
style="color: #666; word-break: break-all"
|
| 218 |
+
></p>
|
| 219 |
+
|
| 220 |
+
<label
|
| 221 |
+
style="display: block; margin-top: 10px; font-weight: bold"
|
| 222 |
+
>Output Format:</label
|
| 223 |
+
>
|
| 224 |
+
<select id="remuxContainer" style="width: 100%">
|
| 225 |
+
<option value="mkv">MKV (Matroska)</option>
|
| 226 |
+
<option value="mp4">MP4 (MPEG-4)</option>
|
| 227 |
+
<option value="avi">AVI</option>
|
| 228 |
+
<option value="mov">MOV</option>
|
| 229 |
+
</select>
|
| 230 |
+
|
| 231 |
+
<label
|
| 232 |
+
style="display: block; margin-top: 10px; font-weight: bold"
|
| 233 |
+
>Output Filename:</label
|
| 234 |
+
>
|
| 235 |
+
<input
|
| 236 |
+
type="text"
|
| 237 |
+
id="remuxOutName"
|
| 238 |
+
style="width: 94%"
|
| 239 |
+
placeholder="Leave empty to auto-name"
|
| 240 |
+
/>
|
| 241 |
+
|
| 242 |
+
<div class="modal-buttons">
|
| 243 |
+
<button class="secondary" onclick="closeRemuxModal()">
|
| 244 |
+
Cancel
|
| 245 |
+
</button>
|
| 246 |
+
<button class="primary" onclick="submitRemux()">
|
| 247 |
+
Convert
|
| 248 |
+
</button>
|
| 249 |
+
</div>
|
| 250 |
+
</div>
|
| 251 |
+
</div>
|
| 252 |
+
|
| 253 |
+
<script>
|
| 254 |
+
let currentDir = "";
|
| 255 |
+
let selectedFileForRemux = ""; // Stores path for modal
|
| 256 |
+
|
| 257 |
+
// --- Core Functions ---
|
| 258 |
+
|
| 259 |
+
async function startDownload() {
|
| 260 |
+
const url = document.getElementById("urlInput").value;
|
| 261 |
+
const customName = document.getElementById("nameInput").value;
|
| 262 |
+
const autoZip = document.getElementById("autoZip").checked;
|
| 263 |
+
if (!url) return alert("Please enter a URL");
|
| 264 |
+
|
| 265 |
+
const res = await fetch("/api/download", {
|
| 266 |
+
method: "POST",
|
| 267 |
+
headers: { "Content-Type": "application/json" },
|
| 268 |
+
body: JSON.stringify({
|
| 269 |
+
url,
|
| 270 |
+
custom_name: customName,
|
| 271 |
+
auto_zip: autoZip,
|
| 272 |
+
}),
|
| 273 |
+
});
|
| 274 |
+
const data = await res.json();
|
| 275 |
+
trackJob(data.job_id);
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
async function extract(filename) {
|
| 279 |
+
const res = await fetch("/api/extract", {
|
| 280 |
+
method: "POST",
|
| 281 |
+
headers: { "Content-Type": "application/json" },
|
| 282 |
+
body: JSON.stringify({ filename }),
|
| 283 |
+
});
|
| 284 |
+
const data = await res.json();
|
| 285 |
+
trackJob(data.job_id);
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
async function deleteFile(path) {
|
| 289 |
+
if (!confirm(`Delete ${path}? Cannot be undone.`)) return;
|
| 290 |
+
await fetch(`/api/files?path=${encodeURIComponent(path)}`, {
|
| 291 |
+
method: "DELETE",
|
| 292 |
+
});
|
| 293 |
+
loadFiles(currentDir);
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
// --- Remux Modal Logic ---
|
| 297 |
+
|
| 298 |
+
function openRemuxModal(path) {
|
| 299 |
+
selectedFileForRemux = path;
|
| 300 |
+
const filename = path.split("/").pop();
|
| 301 |
+
|
| 302 |
+
// Auto-fill form
|
| 303 |
+
document.getElementById("remuxTargetFile").innerText = filename;
|
| 304 |
+
document.getElementById("remuxOutName").value =
|
| 305 |
+
filename.split(".")[0];
|
| 306 |
+
|
| 307 |
+
// If it's mp4, default to mkv, else default to mp4
|
| 308 |
+
const ext = filename.split(".").pop().toLowerCase();
|
| 309 |
+
document.getElementById("remuxContainer").value =
|
| 310 |
+
ext === "mp4" ? "mkv" : "mp4";
|
| 311 |
+
|
| 312 |
+
document.getElementById("remuxModal").style.display = "flex";
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
function closeRemuxModal() {
|
| 316 |
+
document.getElementById("remuxModal").style.display = "none";
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
async function submitRemux() {
|
| 320 |
+
const container =
|
| 321 |
+
document.getElementById("remuxContainer").value;
|
| 322 |
+
const customOut = document.getElementById("remuxOutName").value;
|
| 323 |
+
|
| 324 |
+
closeRemuxModal();
|
| 325 |
+
|
| 326 |
+
const res = await fetch("/api/remux", {
|
| 327 |
+
method: "POST",
|
| 328 |
+
headers: { "Content-Type": "application/json" },
|
| 329 |
+
body: JSON.stringify({
|
| 330 |
+
filename: selectedFileForRemux,
|
| 331 |
+
container,
|
| 332 |
+
custom_out: customOut,
|
| 333 |
+
}),
|
| 334 |
+
});
|
| 335 |
+
const data = await res.json();
|
| 336 |
+
trackJob(data.job_id);
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
// --- Job Tracking & Listing ---
|
| 340 |
+
|
| 341 |
+
function trackJob(id) {
|
| 342 |
+
const el = document.getElementById("jobResult");
|
| 343 |
+
el.style.display = "block";
|
| 344 |
+
el.innerHTML = `Job started...`;
|
| 345 |
+
|
| 346 |
+
const interval = setInterval(async () => {
|
| 347 |
+
const res = await fetch(`/api/job/${id}`);
|
| 348 |
+
const job = await res.json();
|
| 349 |
+
|
| 350 |
+
let html = `<strong>Status:</strong> ${job.status} <br> <strong>Details:</strong> ${job.details}`;
|
| 351 |
+
if (job.result_url) {
|
| 352 |
+
html += `<br><br><a href="${job.result_url}" download><button class="green">Download Result</button></a>`;
|
| 353 |
+
}
|
| 354 |
+
el.innerHTML = html;
|
| 355 |
+
|
| 356 |
+
if (job.status === "completed" || job.status === "failed") {
|
| 357 |
+
clearInterval(interval);
|
| 358 |
+
loadFiles(currentDir); // Refresh list to see new files
|
| 359 |
+
}
|
| 360 |
+
}, 1000);
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
async function loadFiles(dir = "") {
|
| 364 |
+
const res = await fetch(
|
| 365 |
+
`/api/files?dir=${encodeURIComponent(dir)}`,
|
| 366 |
+
);
|
| 367 |
+
const data = await res.json();
|
| 368 |
+
currentDir = data.current;
|
| 369 |
+
document.getElementById("currentPath").innerText =
|
| 370 |
+
"/downloads/" + currentDir;
|
| 371 |
+
|
| 372 |
+
const list = document.getElementById("fileList");
|
| 373 |
+
list.innerHTML = "";
|
| 374 |
+
|
| 375 |
+
if (!data.files || data.files.length === 0) {
|
| 376 |
+
list.innerHTML = '<p style="color:#777">Empty folder.</p>';
|
| 377 |
+
return;
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
data.files.forEach((f) => {
|
| 381 |
+
const div = document.createElement("div");
|
| 382 |
+
div.className = "file-item";
|
| 383 |
+
|
| 384 |
+
const ext = f.name.split(".").pop().toLowerCase();
|
| 385 |
+
const isArchive = [
|
| 386 |
+
"zip",
|
| 387 |
+
"rar",
|
| 388 |
+
"gz",
|
| 389 |
+
"tar",
|
| 390 |
+
"7z",
|
| 391 |
+
].includes(ext);
|
| 392 |
+
const isVideo = [
|
| 393 |
+
"mp4",
|
| 394 |
+
"mkv",
|
| 395 |
+
"avi",
|
| 396 |
+
"mov",
|
| 397 |
+
"webm",
|
| 398 |
+
].includes(ext);
|
| 399 |
+
|
| 400 |
+
let icon = f.type === "dir" ? "📁" : "📄";
|
| 401 |
+
let nameHtml =
|
| 402 |
+
f.type === "dir"
|
| 403 |
+
? `<span class="dir-link" onclick="loadFiles('${f.path}')">${f.name}</span>`
|
| 404 |
+
: `<span>${f.name} <small style="color:#999">(${(f.size / 1024 / 1024).toFixed(2)} MB)</small></span>`;
|
| 405 |
+
|
| 406 |
+
let btns = "";
|
| 407 |
+
if (f.name !== "..") {
|
| 408 |
+
if (f.type !== "dir")
|
| 409 |
+
btns += `<a href="/raw/${f.path}" download><button class="secondary">⬇</button></a> `;
|
| 410 |
+
if (isArchive)
|
| 411 |
+
btns += `<button class="orange" onclick="extract('${f.path}')">Extract</button> `;
|
| 412 |
+
if (isVideo)
|
| 413 |
+
btns += `<button class="primary" onclick="openRemuxModal('${f.path}')">Remux</button> `;
|
| 414 |
+
btns += `<button class="red" onclick="deleteFile('${f.path}')">X</button>`;
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
div.innerHTML = `<div><span style="margin-right:10px">${icon}</span> ${nameHtml}</div> <div class="file-actions">${btns}</div>`;
|
| 418 |
+
list.appendChild(div);
|
| 419 |
+
});
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
loadFiles("");
|
| 423 |
+
</script>
|
| 424 |
+
</body>
|
| 425 |
+
</html>
|