keshav solanki commited on
Commit
48b8079
·
0 Parent(s):

Initial clean commit with LFS tracking

Browse files
Files changed (9) hide show
  1. .gitattributes +36 -0
  2. README.md +10 -0
  3. build.sh +12 -0
  4. downloader-service +3 -0
  5. go.mod +44 -0
  6. go.sum +133 -0
  7. main.go +461 -0
  8. start.sh +14 -0
  9. 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>