Upload 5 files
Browse files- .dockerignore +7 -0
- DEPLOYMENT.md +171 -0
- Dockerfile +20 -0
- package.json +20 -0
- server.js +307 -0
.dockerignore
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules
|
| 2 |
+
npm-debug.log
|
| 3 |
+
uploads/*
|
| 4 |
+
.git
|
| 5 |
+
.gitignore
|
| 6 |
+
README.md
|
| 7 |
+
.env
|
DEPLOYMENT.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hugging Face Deployment - Step by Step
|
| 2 |
+
|
| 3 |
+
## Step 1: Hugging Face Account ΰΆΰΆΰΆΰ· ΰ·ΰΆ―ΰ·ΰΆΰΆ±ΰ·ΰΆ±
|
| 4 |
+
|
| 5 |
+
1. https://huggingface.co/join ΰ·ΰ·ΰΆΆΰ· ΰΆ
ΰΆ©ΰ·ΰ·ΰΆΊΰΆ§ ΰΆΊΰΆ±ΰ·ΰΆ±
|
| 6 |
+
2. Email ΰΆΰΆΰΆΰ· ΰΆ―ΰ·ΰΆ½ΰ· Sign Up ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 7 |
+
3. Email ΰΆΰΆΰ· verification link ΰΆΰΆ click ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 8 |
+
4. Login ΰ·ΰ·ΰΆ±ΰ·ΰΆ±
|
| 9 |
+
|
| 10 |
+
## Step 2: ΰΆ±ΰ· Space ΰΆΰΆΰΆΰ· Create ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 11 |
+
|
| 12 |
+
1. Profile picture ΰΆΰΆ click ΰΆΰΆ»ΰΆ±ΰ·ΰΆ± (top right corner)
|
| 13 |
+
2. **"New Space"** button ΰΆΰΆ click ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 14 |
+
3. ΰ·ΰ·ΰ·ΰ·ΰΆΰΆ» fill ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±:
|
| 15 |
+
- **Space name**: `dulaksha-stream` (ΰ·ΰ· ΰΆΰΆ± ΰΆ±ΰΆΈΰΆΰ·)
|
| 16 |
+
- **License**: MIT
|
| 17 |
+
- **Select SDK**: **Docker** (ΰΆΈΰ·ΰΆ select ΰΆΰΆ»ΰΆ±ΰ·ΰΆ± ΰΆΰΆ±ΰ·)
|
| 18 |
+
- **Space hardware**: CPU basic - Free
|
| 19 |
+
4. **"Create Space"** click ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 20 |
+
|
| 21 |
+
## Step 3: Files Upload ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 22 |
+
|
| 23 |
+
### Method 1: Web Interface ΰΆΰΆΰ·ΰΆ±ΰ· (Easy)
|
| 24 |
+
|
| 25 |
+
1. **"Files"** tab ΰΆΰΆ click ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 26 |
+
2. **"Add file"** > **"Upload files"** click ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 27 |
+
3. ΰΆΈΰ· files upload ΰΆΰΆ»ΰΆ±ΰ·ΰΆ± ΰΆΰΆΰ·ΰΆ±ΰ· ΰΆΰΆ:
|
| 28 |
+
- `server.js`
|
| 29 |
+
- `package.json`
|
| 30 |
+
- `Dockerfile`
|
| 31 |
+
- `.dockerignore`
|
| 32 |
+
4. Upload ΰ·ΰ·ΰΆ½ΰ· ΰΆ±ΰ·ΰΆΰ· files ΰΆΰ·ΰΆΊΰ·ΰΆ±ΰ·ΰΆ±ΰΆΈΰ· repeat ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 33 |
+
|
| 34 |
+
### Method 2: Git ΰΆΰΆΰ·ΰΆ±ΰ· (Advanced)
|
| 35 |
+
|
| 36 |
+
```bash
|
| 37 |
+
git clone https://huggingface.co/spaces/YOUR-USERNAME/dulaksha-stream
|
| 38 |
+
cd dulaksha-stream
|
| 39 |
+
# Copy all files to this folder
|
| 40 |
+
git add .
|
| 41 |
+
git commit -m "Initial commit"
|
| 42 |
+
git push
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
## Step 4: Build ΰΆΰΆ Wait ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 46 |
+
|
| 47 |
+
1. **"App"** tab ΰΆΰΆΰΆ§ ΰΆΊΰΆ±ΰ·ΰΆ±
|
| 48 |
+
2. **"Building"** status ΰΆΰΆ ΰΆ΄ΰ·ΰΆ±ΰ·ΰΆ±ΰΆ± ΰΆΰΆ ΰΆΆΰΆ½ΰΆ±ΰ·ΰΆ±
|
| 49 |
+
3. **5-10 minutes** ΰ·ΰ·ΰΆΰΆ» wait ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 50 |
+
4. **"Running"** ΰΆΰ·ΰΆΊΰΆ½ΰ· status ΰΆΰΆ change ΰ·ΰ·ΰΆ±ΰΆΰΆ½ΰ· wait ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 51 |
+
|
| 52 |
+
Build ΰΆΰΆ fail ΰ·ΰ·ΰΆ±ΰ·ΰΆ±ΰΆΈΰ·:
|
| 53 |
+
- **"Logs"** tab ΰΆΰΆ check ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 54 |
+
- Error message ΰΆΰΆ copy ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 55 |
+
|
| 56 |
+
## Step 5: URL ΰΆΰΆ ΰΆΰΆ±ΰ·ΰΆ±
|
| 57 |
+
|
| 58 |
+
Build ΰΆΰΆ success ΰ·ΰ·ΰΆ«ΰ·ΰΆΈ ΰΆΰΆΊΰ·ΰΆΰ· app ΰΆΰΆ ΰΆΈΰ· URL ΰΆΰΆΰ· available ΰ·ΰ·ΰΆΊΰ·:
|
| 59 |
+
|
| 60 |
+
```
|
| 61 |
+
https://YOUR-USERNAME-dulaksha-stream.hf.space
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
ΰΆΰΆ―ΰ·ΰ·ΰΆ»ΰΆ«:
|
| 65 |
+
```
|
| 66 |
+
https://dulaksha-dulaksha-stream.hf.space
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
## Step 6: Test ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 70 |
+
|
| 71 |
+
### Browser ΰΆΰΆΰ·ΰΆ±ΰ· Test:
|
| 72 |
+
1. URL ΰΆΰΆ browser ΰΆΰΆΰ· open ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 73 |
+
2. Server running ΰΆΰ·ΰΆΊΰΆ½ΰ· ΰΆ΄ΰ·ΰΆ±ΰ·ΰ·ΰΆ±ΰ·ΰΆ± ΰΆΰΆ±
|
| 74 |
+
|
| 75 |
+
### Video Upload Test (Postman):
|
| 76 |
+
|
| 77 |
+
1. Postman open ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 78 |
+
2. **New Request** create ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 79 |
+
3. Settings:
|
| 80 |
+
- **Method**: POST
|
| 81 |
+
- **URL**: `https://YOUR-USERNAME-dulaksha-stream.hf.space/upload`
|
| 82 |
+
- **Body**: form-data
|
| 83 |
+
- **Key**: `file` (type: File)
|
| 84 |
+
- **Value**: video file ΰΆΰΆΰΆΰ· select ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 85 |
+
4. **Send** click ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 86 |
+
|
| 87 |
+
### cURL ΰΆΰΆΰ·ΰΆ±ΰ· Test:
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
curl -X POST -F "file=@video.mp4" https://YOUR-USERNAME-dulaksha-stream.hf.space/upload
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
## Step 7: Response ΰΆΰΆ Check ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 94 |
+
|
| 95 |
+
Upload success ΰ·ΰ·ΰΆ«ΰ·ΰΆΈ ΰΆΈΰ· ΰ·ΰ·ΰΆ―ΰ·ΰ·ΰ· response ΰΆΰΆΰΆΰ· ΰΆΰΆ±ΰ·ΰ·:
|
| 96 |
+
|
| 97 |
+
```json
|
| 98 |
+
{
|
| 99 |
+
"status": "success",
|
| 100 |
+
"stream_link": "https://YOUR-SPACE.hf.space/stream/123456/video.mp4",
|
| 101 |
+
"qualities": ["1080p", "720p", "480p"]
|
| 102 |
+
}
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
## Video Quality Conversion
|
| 106 |
+
|
| 107 |
+
- **2160p (4K) video**: Convert to 1080p, 720p, 480p
|
| 108 |
+
- **1080p video**: Convert to 1080p, 720p, 480p
|
| 109 |
+
- **720p video**: Convert to 720p, 480p
|
| 110 |
+
- **480p video**: Keep as 480p
|
| 111 |
+
|
| 112 |
+
## API Endpoints
|
| 113 |
+
|
| 114 |
+
### Upload Video
|
| 115 |
+
```
|
| 116 |
+
POST /upload
|
| 117 |
+
Content-Type: multipart/form-data
|
| 118 |
+
Body: file (video file)
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
### Stream File
|
| 122 |
+
```
|
| 123 |
+
GET /stream/:id/:filename
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
### Download File
|
| 127 |
+
```
|
| 128 |
+
GET /download/:id/:filename
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
## Important Notes
|
| 132 |
+
|
| 133 |
+
- **Bot connect ΰ·ΰ·ΰΆ±ΰ·ΰΆ± ΰ·ΰ·ΰΆ±ΰ·ΰΆ©ΰ· 2-3ΰΆΰ· ΰΆΊΰΆ±ΰ·ΰ·** - First upload ΰΆΰΆΰΆ§ wait ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 134 |
+
- **Large files ΰ·ΰΆ½ΰΆ§ processing time ΰ·ΰ·ΰΆ©ΰ·ΰΆΊΰ·** - 4K videos ΰ·ΰΆ½ΰΆ§ 10-20 minutes ΰΆΊΰΆ±ΰ·ΰ·
|
| 135 |
+
- **Stream links permanent** - ΰΆΰ·ΰΆ―ΰ·ΰ·ΰΆΰ· expire ΰ·ΰ·ΰΆ±ΰ·ΰΆ±ΰ· ΰΆ±ΰ·ΰ·ΰ·
|
| 136 |
+
- **All files Telegram ΰΆΰΆΰ· store ΰ·ΰ·ΰΆ±ΰ·ΰ·** - Backup ΰΆΰΆΰΆΰ· ΰ·ΰ·ΰΆ―ΰ·ΰ·ΰΆ§
|
| 137 |
+
|
| 138 |
+
## Common Issues
|
| 139 |
+
|
| 140 |
+
### 1. Build Failed
|
| 141 |
+
**Solution**: Dockerfile ΰΆΰΆ ΰ·ΰΆ»ΰ·ΰΆΊΰΆ§ΰΆΈ copy ΰ·ΰ·ΰΆ½ΰ· ΰΆΰ·ΰΆΊΰ·ΰΆ±ΰ·ΰΆ― check ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 142 |
+
|
| 143 |
+
### 2. Service Unavailable
|
| 144 |
+
**Solution**: ΰ·ΰ·ΰΆ±ΰ·ΰΆ©ΰ· 2-3ΰΆΰ· wait ΰΆΰΆ»ΰΆ½ΰ· retry ΰΆΰΆ»ΰΆ±ΰ·ΰΆ± (bot connecting)
|
| 145 |
+
|
| 146 |
+
### 3. Upload Timeout
|
| 147 |
+
**Solution**:
|
| 148 |
+
- File size ΰΆ
ΰΆ©ΰ· ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 149 |
+
- Better hardware tier ΰΆΰΆΰΆΰ· select ΰΆΰΆ»ΰΆ±ΰ·ΰΆ± (Settings > Hardware)
|
| 150 |
+
|
| 151 |
+
### 4. FFmpeg Error
|
| 152 |
+
**Solution**: Video file ΰΆΰΆ corrupt ΰ·ΰ·ΰΆ½ΰ· ΰΆ±ΰ·ΰΆ―ΰ·ΰΆ― check ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±
|
| 153 |
+
|
| 154 |
+
## Hardware Upgrade (Optional)
|
| 155 |
+
|
| 156 |
+
Free tier slow ΰΆ±ΰΆΈΰ·:
|
| 157 |
+
1. Space Settings > Hardware
|
| 158 |
+
2. Upgrade ΰΆΰΆ»ΰΆ±ΰ·ΰΆ±:
|
| 159 |
+
- CPU basic (Free) β CPU upgrade ($0.60/hour)
|
| 160 |
+
- GPU ΰΆΰΆΰΆΰ· select ΰΆΰΆ»ΰΆ±ΰ·ΰΆ± (faster conversion)
|
| 161 |
+
|
| 162 |
+
## Files ΰΆ±ΰ·ΰΆ»ΰΆ΄ΰΆ±ΰ·ΰΆ± ΰΆΰΆ±ΰ· ΰΆ±ΰΆΈΰ·
|
| 163 |
+
|
| 164 |
+
```bash
|
| 165 |
+
# Hugging Face Space ΰΆΰΆΰ·
|
| 166 |
+
Files > Delete ΰΆΰΆ»ΰΆ±ΰ·ΰΆ± files
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
---
|
| 170 |
+
|
| 171 |
+
**ΰ·ΰ·ΰΆΊΰΆ½ΰ· files ΰΆΈΰ· folder ΰΆΰΆΰ· ΰΆΰ·ΰΆΊΰ·ΰΆ±ΰ·ΰ·. Upload ΰΆΰΆ»ΰΆ±ΰ·ΰΆ± ΰ·ΰ·ΰΆΰΆ»ΰΆΊΰ· ΰΆΰΆ±!**
|
Dockerfile
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:18-slim
|
| 2 |
+
|
| 3 |
+
RUN apt-get update && apt-get install -y \
|
| 4 |
+
ffmpeg \
|
| 5 |
+
python3 \
|
| 6 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 7 |
+
|
| 8 |
+
WORKDIR /app
|
| 9 |
+
|
| 10 |
+
COPY package*.json ./
|
| 11 |
+
|
| 12 |
+
RUN npm install
|
| 13 |
+
|
| 14 |
+
COPY . .
|
| 15 |
+
|
| 16 |
+
RUN mkdir -p uploads
|
| 17 |
+
|
| 18 |
+
EXPOSE 7860
|
| 19 |
+
|
| 20 |
+
CMD ["npm", "start"]
|
package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "dulaksha-stream",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"description": "Video streaming service with multi-quality HLS support",
|
| 5 |
+
"main": "server.js",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"start": "node server.js"
|
| 8 |
+
},
|
| 9 |
+
"dependencies": {
|
| 10 |
+
"express": "^4.18.2",
|
| 11 |
+
"multer": "^1.4.5-lts.1",
|
| 12 |
+
"telegram": "^2.22.2",
|
| 13 |
+
"input": "^1.0.1",
|
| 14 |
+
"fluent-ffmpeg": "^2.1.2",
|
| 15 |
+
"ffmpeg-static": "^5.2.0"
|
| 16 |
+
},
|
| 17 |
+
"engines": {
|
| 18 |
+
"node": ">=18.0.0"
|
| 19 |
+
}
|
| 20 |
+
}
|
server.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const express = require("express");
|
| 2 |
+
const multer = require("multer");
|
| 3 |
+
const { TelegramClient } = require("telegram");
|
| 4 |
+
const { StringSession } = require("telegram/sessions");
|
| 5 |
+
const input = require("input");
|
| 6 |
+
const fs = require("fs");
|
| 7 |
+
const path = require("path");
|
| 8 |
+
const ffmpeg = require("fluent-ffmpeg");
|
| 9 |
+
const ffmpegPath = require("ffmpeg-static");
|
| 10 |
+
|
| 11 |
+
ffmpeg.setFfmpegPath(ffmpegPath);
|
| 12 |
+
|
| 13 |
+
const app = express();
|
| 14 |
+
const port = process.env.PORT || 7860;
|
| 15 |
+
|
| 16 |
+
const API_ID = 24947404;
|
| 17 |
+
const API_HASH = "a27b63b5e9a4c4e9279d1db641aca338";
|
| 18 |
+
const BOT_TOKEN = "8300006322:AAHnZf0xlt5mrzlaBjXYmVceZwo3xmwTZaA";
|
| 19 |
+
const BIN_CHANNEL = -1003569314973;
|
| 20 |
+
const SYSTEM_URL = process.env.SPACE_HOST
|
| 21 |
+
? `https://${process.env.SPACE_ID.replace('/', '-')}-${process.env.SPACE_HOST}`
|
| 22 |
+
: "http://localhost:7860";
|
| 23 |
+
|
| 24 |
+
const clients = [];
|
| 25 |
+
const upload = multer({ dest: "uploads/" });
|
| 26 |
+
|
| 27 |
+
app.use(express.json());
|
| 28 |
+
|
| 29 |
+
async function getNextClient() {
|
| 30 |
+
const stringSession = new StringSession("");
|
| 31 |
+
const client = new TelegramClient(stringSession, API_ID, API_HASH, {
|
| 32 |
+
connectionRetries: 5,
|
| 33 |
+
});
|
| 34 |
+
|
| 35 |
+
await client.start({
|
| 36 |
+
botAuthToken: BOT_TOKEN,
|
| 37 |
+
});
|
| 38 |
+
|
| 39 |
+
clients.push(client);
|
| 40 |
+
return client;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
async function startAllBots() {
|
| 44 |
+
console.log("Starting Dulaksha Stream bot...");
|
| 45 |
+
await getNextClient();
|
| 46 |
+
console.log("Bot connected successfully!");
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
function getVideoResolution(inputPath) {
|
| 50 |
+
return new Promise((resolve, reject) => {
|
| 51 |
+
ffmpeg.ffprobe(inputPath, (err, metadata) => {
|
| 52 |
+
if (err) return reject(err);
|
| 53 |
+
const videoStream = metadata.streams.find(s => s.codec_type === 'video');
|
| 54 |
+
if (!videoStream) return reject(new Error("No video stream found"));
|
| 55 |
+
resolve({ width: videoStream.width, height: videoStream.height });
|
| 56 |
+
});
|
| 57 |
+
});
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
function convertToMultiQualityHLS(inputPath, outputDir) {
|
| 61 |
+
return new Promise(async (resolve, reject) => {
|
| 62 |
+
try {
|
| 63 |
+
const resolution = await getVideoResolution(inputPath);
|
| 64 |
+
const height = resolution.height;
|
| 65 |
+
|
| 66 |
+
const qualities = [];
|
| 67 |
+
if (height >= 2160) {
|
| 68 |
+
qualities.push({ name: "1080p", height: 1080, bitrate: "5000k" });
|
| 69 |
+
qualities.push({ name: "720p", height: 720, bitrate: "2800k" });
|
| 70 |
+
qualities.push({ name: "480p", height: 480, bitrate: "1400k" });
|
| 71 |
+
} else if (height >= 1080) {
|
| 72 |
+
qualities.push({ name: "1080p", height: 1080, bitrate: "5000k" });
|
| 73 |
+
qualities.push({ name: "720p", height: 720, bitrate: "2800k" });
|
| 74 |
+
qualities.push({ name: "480p", height: 480, bitrate: "1400k" });
|
| 75 |
+
} else if (height >= 720) {
|
| 76 |
+
qualities.push({ name: "720p", height: 720, bitrate: "2800k" });
|
| 77 |
+
qualities.push({ name: "480p", height: 480, bitrate: "1400k" });
|
| 78 |
+
} else {
|
| 79 |
+
qualities.push({ name: "480p", height: 480, bitrate: "1400k" });
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
const masterPlaylist = path.join(outputDir, "master.m3u8");
|
| 83 |
+
let masterContent = "#EXTM3U\n#EXT-X-VERSION:3\n";
|
| 84 |
+
|
| 85 |
+
const conversionPromises = qualities.map((quality, index) => {
|
| 86 |
+
return new Promise((res, rej) => {
|
| 87 |
+
const qualityDir = path.join(outputDir, quality.name);
|
| 88 |
+
fs.mkdirSync(qualityDir, { recursive: true });
|
| 89 |
+
const playlistPath = path.join(qualityDir, "index.m3u8");
|
| 90 |
+
|
| 91 |
+
ffmpeg(inputPath)
|
| 92 |
+
.outputOptions([
|
| 93 |
+
`-vf scale=-2:${quality.height}`,
|
| 94 |
+
`-c:v libx264`,
|
| 95 |
+
`-b:v ${quality.bitrate}`,
|
| 96 |
+
`-c:a aac`,
|
| 97 |
+
`-b:a 128k`,
|
| 98 |
+
`-start_number 0`,
|
| 99 |
+
`-hls_time 10`,
|
| 100 |
+
`-hls_list_size 0`,
|
| 101 |
+
`-f hls`
|
| 102 |
+
])
|
| 103 |
+
.output(playlistPath)
|
| 104 |
+
.on("end", () => {
|
| 105 |
+
masterContent += `#EXT-X-STREAM-INF:BANDWIDTH=${parseInt(quality.bitrate) * 1000},RESOLUTION=${Math.floor(quality.height * 16 / 9)}x${quality.height}\n`;
|
| 106 |
+
masterContent += `${quality.name}/index.m3u8\n`;
|
| 107 |
+
res();
|
| 108 |
+
})
|
| 109 |
+
.on("error", rej)
|
| 110 |
+
.run();
|
| 111 |
+
});
|
| 112 |
+
});
|
| 113 |
+
|
| 114 |
+
await Promise.all(conversionPromises);
|
| 115 |
+
|
| 116 |
+
fs.writeFileSync(masterPlaylist, masterContent);
|
| 117 |
+
resolve(masterPlaylist);
|
| 118 |
+
|
| 119 |
+
} catch (err) {
|
| 120 |
+
reject(err);
|
| 121 |
+
}
|
| 122 |
+
});
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
async function uploadHLSFiles(client, hlsDir, originalName) {
|
| 126 |
+
const uploadedFiles = { qualities: {} };
|
| 127 |
+
|
| 128 |
+
const masterPath = path.join(hlsDir, "master.m3u8");
|
| 129 |
+
const masterResult = await client.sendFile(BIN_CHANNEL, {
|
| 130 |
+
file: masterPath,
|
| 131 |
+
caption: `π ${originalName} - master.m3u8`,
|
| 132 |
+
forceDocument: true
|
| 133 |
+
});
|
| 134 |
+
uploadedFiles.master = masterResult.id;
|
| 135 |
+
|
| 136 |
+
const qualityDirs = fs.readdirSync(hlsDir).filter(f =>
|
| 137 |
+
fs.statSync(path.join(hlsDir, f)).isDirectory()
|
| 138 |
+
);
|
| 139 |
+
|
| 140 |
+
for (const qualityDir of qualityDirs) {
|
| 141 |
+
const qualityPath = path.join(hlsDir, qualityDir);
|
| 142 |
+
const files = fs.readdirSync(qualityPath);
|
| 143 |
+
|
| 144 |
+
const m3u8File = files.find(f => f.endsWith(".m3u8"));
|
| 145 |
+
const tsFiles = files.filter(f => f.endsWith(".ts"));
|
| 146 |
+
|
| 147 |
+
uploadedFiles.qualities[qualityDir] = { segments: [] };
|
| 148 |
+
|
| 149 |
+
const m3u8Path = path.join(qualityPath, m3u8File);
|
| 150 |
+
const m3u8Result = await client.sendFile(BIN_CHANNEL, {
|
| 151 |
+
file: m3u8Path,
|
| 152 |
+
caption: `π ${originalName} - ${qualityDir}/index.m3u8`,
|
| 153 |
+
forceDocument: true
|
| 154 |
+
});
|
| 155 |
+
uploadedFiles.qualities[qualityDir].index = m3u8Result.id;
|
| 156 |
+
|
| 157 |
+
for (const tsFile of tsFiles) {
|
| 158 |
+
const tsPath = path.join(qualityPath, tsFile);
|
| 159 |
+
const tsResult = await client.sendFile(BIN_CHANNEL, {
|
| 160 |
+
file: tsPath,
|
| 161 |
+
caption: `π ${originalName} - ${qualityDir}/${tsFile}`,
|
| 162 |
+
forceDocument: true
|
| 163 |
+
});
|
| 164 |
+
uploadedFiles.qualities[qualityDir].segments.push({
|
| 165 |
+
name: tsFile,
|
| 166 |
+
id: tsResult.id
|
| 167 |
+
});
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
return uploadedFiles;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
app.post("/upload", upload.single('file'), async (req, res) => {
|
| 175 |
+
if (!req.file) return res.status(400).json({ error: "No file" });
|
| 176 |
+
|
| 177 |
+
const originalName = req.file.originalname;
|
| 178 |
+
const safeName = encodeURIComponent(originalName.replace(/\s+/g, '_'));
|
| 179 |
+
const extension = path.extname(originalName).toLowerCase();
|
| 180 |
+
const tempPath = req.file.path;
|
| 181 |
+
const newPath = tempPath + extension;
|
| 182 |
+
|
| 183 |
+
const videoExtensions = ['.mp4', '.mkv', '.avi', '.mov', '.wmv', '.flv', '.webm'];
|
| 184 |
+
const isVideo = videoExtensions.includes(extension);
|
| 185 |
+
|
| 186 |
+
try {
|
| 187 |
+
fs.renameSync(tempPath, newPath);
|
| 188 |
+
const client = clients[0] || await getNextClient();
|
| 189 |
+
if (!client) throw new Error("Server Busy");
|
| 190 |
+
|
| 191 |
+
if (isVideo) {
|
| 192 |
+
const hlsDir = path.join("uploads", `hls_${Date.now()}`);
|
| 193 |
+
fs.mkdirSync(hlsDir, { recursive: true });
|
| 194 |
+
|
| 195 |
+
await convertToMultiQualityHLS(newPath, hlsDir);
|
| 196 |
+
const hlsFiles = await uploadHLSFiles(client, hlsDir, originalName);
|
| 197 |
+
|
| 198 |
+
fs.rmSync(hlsDir, { recursive: true, force: true });
|
| 199 |
+
if (fs.existsSync(newPath)) fs.unlinkSync(newPath);
|
| 200 |
+
|
| 201 |
+
res.json({
|
| 202 |
+
status: "success",
|
| 203 |
+
stream_link: `${SYSTEM_URL}/stream/${hlsFiles.master}/${safeName}`,
|
| 204 |
+
qualities: Object.keys(hlsFiles.qualities)
|
| 205 |
+
});
|
| 206 |
+
} else {
|
| 207 |
+
const result = await client.sendFile(BIN_CHANNEL, {
|
| 208 |
+
file: newPath,
|
| 209 |
+
caption: `π Uploaded: ${originalName}`,
|
| 210 |
+
forceDocument: false
|
| 211 |
+
});
|
| 212 |
+
|
| 213 |
+
if (fs.existsSync(newPath)) fs.unlinkSync(newPath);
|
| 214 |
+
|
| 215 |
+
res.json({
|
| 216 |
+
status: "success",
|
| 217 |
+
link: `${SYSTEM_URL}/stream/${result.id}`,
|
| 218 |
+
stream_link: `${SYSTEM_URL}/stream/${result.id}/${safeName}`,
|
| 219 |
+
download_link: `${SYSTEM_URL}/download/${result.id}/${safeName}`
|
| 220 |
+
});
|
| 221 |
+
}
|
| 222 |
+
} catch (e) {
|
| 223 |
+
console.error("Upload error:", e);
|
| 224 |
+
res.status(500).json({ error: e.message });
|
| 225 |
+
if (fs.existsSync(newPath)) fs.unlinkSync(newPath);
|
| 226 |
+
else if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
|
| 227 |
+
}
|
| 228 |
+
});
|
| 229 |
+
|
| 230 |
+
app.get("/stream/:id/:filename?", async (req, res) => {
|
| 231 |
+
try {
|
| 232 |
+
const messageId = parseInt(req.params.id);
|
| 233 |
+
const client = clients[0];
|
| 234 |
+
if (!client) return res.status(503).send("Service unavailable");
|
| 235 |
+
|
| 236 |
+
const messages = await client.getMessages(BIN_CHANNEL, { ids: [messageId] });
|
| 237 |
+
const message = messages[0];
|
| 238 |
+
|
| 239 |
+
if (!message || !message.media) {
|
| 240 |
+
return res.status(404).send("File not found");
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
const fileSize = message.media.document.size;
|
| 244 |
+
const range = req.headers.range;
|
| 245 |
+
|
| 246 |
+
if (range) {
|
| 247 |
+
const parts = range.replace(/bytes=/, "").split("-");
|
| 248 |
+
const start = parseInt(parts[0], 10);
|
| 249 |
+
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
|
| 250 |
+
const chunkSize = (end - start) + 1;
|
| 251 |
+
|
| 252 |
+
res.writeHead(206, {
|
| 253 |
+
"Content-Range": `bytes ${start}-${end}/${fileSize}`,
|
| 254 |
+
"Accept-Ranges": "bytes",
|
| 255 |
+
"Content-Length": chunkSize,
|
| 256 |
+
"Content-Type": "application/octet-stream"
|
| 257 |
+
});
|
| 258 |
+
|
| 259 |
+
const buffer = await client.downloadMedia(message, {
|
| 260 |
+
offset: start,
|
| 261 |
+
limit: chunkSize
|
| 262 |
+
});
|
| 263 |
+
res.end(buffer);
|
| 264 |
+
} else {
|
| 265 |
+
res.writeHead(200, {
|
| 266 |
+
"Content-Length": fileSize,
|
| 267 |
+
"Content-Type": "application/octet-stream"
|
| 268 |
+
});
|
| 269 |
+
|
| 270 |
+
const buffer = await client.downloadMedia(message);
|
| 271 |
+
res.end(buffer);
|
| 272 |
+
}
|
| 273 |
+
} catch (e) {
|
| 274 |
+
console.error("Stream error:", e);
|
| 275 |
+
res.status(500).send("Error streaming file");
|
| 276 |
+
}
|
| 277 |
+
});
|
| 278 |
+
|
| 279 |
+
app.get("/download/:id/:filename", async (req, res) => {
|
| 280 |
+
try {
|
| 281 |
+
const messageId = parseInt(req.params.id);
|
| 282 |
+
const filename = decodeURIComponent(req.params.filename);
|
| 283 |
+
const client = clients[0];
|
| 284 |
+
if (!client) return res.status(503).send("Service unavailable");
|
| 285 |
+
|
| 286 |
+
const messages = await client.getMessages(BIN_CHANNEL, { ids: [messageId] });
|
| 287 |
+
const message = messages[0];
|
| 288 |
+
|
| 289 |
+
if (!message || !message.media) {
|
| 290 |
+
return res.status(404).send("File not found");
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
|
| 294 |
+
res.setHeader("Content-Type", "application/octet-stream");
|
| 295 |
+
|
| 296 |
+
const buffer = await client.downloadMedia(message);
|
| 297 |
+
res.end(buffer);
|
| 298 |
+
} catch (e) {
|
| 299 |
+
console.error("Download error:", e);
|
| 300 |
+
res.status(500).send("Error downloading file");
|
| 301 |
+
}
|
| 302 |
+
});
|
| 303 |
+
|
| 304 |
+
app.listen(port, () => {
|
| 305 |
+
console.log(`Dulaksha Stream Server Running on Port ${port}`);
|
| 306 |
+
startAllBots().catch(console.error);
|
| 307 |
+
});
|