r3hab commited on
Commit
37b09ef
·
verified ·
1 Parent(s): 5c2d2a8

Upload 5 files

Browse files
Files changed (5) hide show
  1. Dockerfile +36 -0
  2. README.md +5 -5
  3. app.py +165 -0
  4. requirements.txt +9 -0
  5. static/index.html +75 -0
Dockerfile ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Start with Python base image
2
+ FROM python:3.9-slim
3
+
4
+ # Install system dependencies as root
5
+ USER root
6
+
7
+ # Install required system packages
8
+ RUN apt-get update && apt-get install -y \
9
+ libtorrent-rasterbar-dev \
10
+ build-essential \
11
+ libssl-dev \
12
+ libboost-system-dev \
13
+ libmagic1 \
14
+ && rm -rf /var/lib/apt/lists/*
15
+
16
+ # Create non-root user
17
+ RUN useradd -m -u 1000 user
18
+ USER user
19
+ ENV PATH="/home/user/.local/bin:$PATH"
20
+ WORKDIR /app
21
+
22
+ # Install Python dependencies
23
+ COPY --chown=user requirements.txt .
24
+ RUN pip install --no-cache-dir --user --upgrade -r requirements.txt
25
+
26
+ # Copy application files
27
+ COPY --chown=user . .
28
+
29
+ # Add this after COPY . .
30
+ RUN mkdir -p /app/static && chown user:user /app/static
31
+
32
+ # Expose Hugging Face default port
33
+ EXPOSE 7860
34
+
35
+ # Start the application
36
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,10 +1,10 @@
1
  ---
2
- title: Stream Yy
3
- emoji: 🦀
4
- colorFrom: yellow
5
- colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: transfer-test
3
+ emoji:
4
+ colorFrom: purple
5
+ colorTo: gray
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import time
4
+ import uuid
5
+ import asyncio
6
+ import libtorrent as lt
7
+ from fastapi import FastAPI, HTTPException
8
+ from fastapi.responses import StreamingResponse
9
+ import magic
10
+ from fastapi import Request
11
+ from fastapi.staticfiles import StaticFiles
12
+ from fastapi.templating import Jinja2Templates
13
+ from bs4 import BeautifulSoup
14
+ import requests
15
+
16
+
17
+ app = FastAPI()
18
+
19
+ # Add to top of app.py
20
+ app.mount("/static", StaticFiles(directory="static"), name="static")
21
+ templates = Jinja2Templates(directory=".")
22
+
23
+ active_sessions = {}
24
+
25
+ # Add search endpoint
26
+ @app.get("/search")
27
+ async def search_torrents(q: str):
28
+ try:
29
+ url = f"https://torrentgalaxy.to/torrents.php?search={q}"
30
+ headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}
31
+
32
+ response = requests.get(url, headers=headers)
33
+ soup = BeautifulSoup(response.text, 'html.parser')
34
+
35
+ torrents = []
36
+ for row in soup.select('div.tgxtablerow'):
37
+ try:
38
+ title = row.select_one('div:nth-child(4)').get_text(strip=True)
39
+ magnet = row.select_one('div:nth-child(5) a[href^="magnet:"]')['href']
40
+ size = row.select_one('div:nth-child(8)').get_text(strip=True)
41
+ seeds = row.select_one('div:nth-child(11) font').get_text(strip=True)
42
+
43
+ torrents.append({
44
+ 'title': title,
45
+ 'magnet': magnet,
46
+ 'size': size,
47
+ 'seeds': seeds
48
+ })
49
+ except:
50
+ continue
51
+
52
+ return torrents[:10] # Return top 10 results
53
+
54
+ except Exception as e:
55
+ raise HTTPException(status_code=500, detail=str(e))
56
+
57
+ # Add mime type endpoint
58
+ @app.get("/stream/{torrent_id}/mime")
59
+ async def get_mime_type(torrent_id: str):
60
+ if torrent_id not in active_sessions:
61
+ raise HTTPException(status_code=404, detail="Torrent session not found")
62
+
63
+ file_path = active_sessions[torrent_id]["file_path"]
64
+ mime = magic.Magic(mime=True)
65
+ return {"mime_type": mime.from_file(file_path)}
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+ def get_largest_file(torrent_info):
74
+ files = torrent_info.files()
75
+ file_sizes = [(i, files.file_size(i)) for i in range(files.num_files())]
76
+ if not file_sizes:
77
+ return None
78
+ return max(file_sizes, key=lambda x: x[1])
79
+
80
+ async def add_torrent(magnet_link: str, save_path: str):
81
+ ses = lt.session()
82
+ params = {
83
+ 'save_path': save_path,
84
+ 'storage_mode': lt.storage_mode_t(2),
85
+ }
86
+
87
+ handle = lt.add_magnet_uri(ses, magnet_link, params)
88
+
89
+ # Wait for metadata
90
+ while not handle.has_metadata():
91
+ await asyncio.sleep(1)
92
+
93
+ torrent_info = handle.get_torrent_info()
94
+ file_info = get_largest_file(torrent_info)
95
+ if not file_info:
96
+ raise Exception("No files found in torrent")
97
+
98
+ file_index, _ = file_info
99
+ file_path = os.path.join(save_path, torrent_info.files().file_path(file_index))
100
+
101
+ # Prioritize largest file
102
+ for i in range(torrent_info.num_files()):
103
+ handle.file_priority(i, 7 if i == file_index else 0)
104
+
105
+ handle.set_sequential_download(True)
106
+ ses.resume()
107
+
108
+ return {
109
+ "session": ses,
110
+ "handle": handle,
111
+ "file_path": file_path,
112
+ "downloading": True
113
+ }
114
+
115
+ @app.post("/start_stream")
116
+ async def start_stream(magnet_link: str):
117
+ torrent_id = str(uuid.uuid4())
118
+ save_path = f"./downloads/{torrent_id}"
119
+ os.makedirs(save_path, exist_ok=True)
120
+
121
+ try:
122
+ session_info = await add_torrent(magnet_link, save_path)
123
+ except Exception as e:
124
+ raise HTTPException(status_code=500, detail=str(e))
125
+
126
+ active_sessions[torrent_id] = session_info
127
+ return {"stream_url": f"/stream/{torrent_id}"}
128
+
129
+ @app.get("/stream/{torrent_id}")
130
+ async def stream_torrent(torrent_id: str):
131
+ if torrent_id not in active_sessions:
132
+ raise HTTPException(status_code=404, detail="Torrent session not found")
133
+
134
+ session_info = active_sessions[torrent_id]
135
+ file_path = session_info["file_path"]
136
+
137
+ if not os.path.exists(file_path):
138
+ raise HTTPException(status_code=404, detail="File not found")
139
+
140
+ mime = magic.Magic(mime=True)
141
+ mime_type = mime.from_file(file_path)
142
+
143
+ def file_generator():
144
+ last_position = 0
145
+ while True:
146
+ if not os.path.exists(file_path):
147
+ time.sleep(1)
148
+ continue
149
+
150
+ with open(file_path, "rb") as f:
151
+ f.seek(last_position)
152
+ data = f.read(1024 * 1024) # 1MB chunks
153
+ if not data:
154
+ if session_info["handle"].is_seed():
155
+ break
156
+ time.sleep(1)
157
+ continue
158
+ last_position = f.tell()
159
+ yield data
160
+
161
+ return StreamingResponse(file_generator(), media_type=mime_type)
162
+
163
+ if __name__ == "__main__":
164
+ import uvicorn
165
+ uvicorn.run(app, host="0.0.0.0", port=8000)
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ pillow
4
+ python-multipart
5
+ python-magic
6
+ libtorrent
7
+ beautifulsoup4
8
+ requests
9
+ jinja2
static/index.html ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- index.html -->
2
+ <!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <title>TorrentGalaxy Streamer</title>
6
+ <style>
7
+ body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
8
+ .search-section, .player-section { margin-bottom: 30px; }
9
+ #results { margin-top: 20px; }
10
+ .torrent-item { padding: 10px; border-bottom: 1px solid #ddd; cursor: pointer; }
11
+ .torrent-item:hover { background: #f5f5f5; }
12
+ #player { width: 100%; height: 500px; }
13
+ </style>
14
+ </head>
15
+ <body>
16
+ <div class="search-section">
17
+ <h1>TorrentGalaxy Streamer</h1>
18
+ <input type="text" id="searchInput" placeholder="Search TorrentGalaxy...">
19
+ <button onclick="searchTorrents()">Search</button>
20
+ <div id="results"></div>
21
+ </div>
22
+
23
+ <div class="player-section">
24
+ <video id="player" controls></video>
25
+ </div>
26
+
27
+ <script>
28
+ async function searchTorrents() {
29
+ const query = document.getElementById('searchInput').value;
30
+ const results = document.getElementById('results');
31
+ results.innerHTML = 'Loading...';
32
+
33
+ try {
34
+ const response = await fetch(`/search?q=${encodeURIComponent(query)}`);
35
+ const torrents = await response.json();
36
+
37
+ results.innerHTML = torrents.map(t => `
38
+ <div class="torrent-item" onclick="startStream('${t.magnet}')">
39
+ <strong>${t.title}</strong><br>
40
+ <small>Size: ${t.size} | Seeds: ${t.seeds}</small>
41
+ </div>
42
+ `).join('');
43
+ } catch (error) {
44
+ results.innerHTML = 'Error loading results';
45
+ }
46
+ }
47
+
48
+ async function startStream(magnet) {
49
+ try {
50
+ // Start the stream
51
+ const startResp = await fetch('/start_stream', {
52
+ method: 'POST',
53
+ headers: {'Content-Type': 'application/json'},
54
+ body: JSON.stringify({ magnet_link: magnet })
55
+ });
56
+
57
+ const { stream_url } = await startResp.json();
58
+
59
+ // Get MIME type and set up player
60
+ const mimeResp = await fetch(`${stream_url}/mime`);
61
+ const { mime_type } = await mimeResp.json();
62
+
63
+ // Set up video player
64
+ const player = document.getElementById('player');
65
+ player.src = stream_url;
66
+ player.type = mime_type;
67
+ player.load();
68
+ player.play();
69
+ } catch (error) {
70
+ alert('Error starting stream: ' + error.message);
71
+ }
72
+ }
73
+ </script>
74
+ </body>
75
+ </html>