ChandimaPrabath commited on
Commit
da44638
·
1 Parent(s): f0de3f7
.gitignore CHANGED
@@ -3,4 +3,6 @@
3
  # cache
4
  tmp
5
  # pycache
6
- __pycache__
 
 
 
3
  # cache
4
  tmp
5
  # pycache
6
+ __pycache__
7
+ # stream-test.py
8
+ stream-test.py
app.py CHANGED
@@ -1,13 +1,15 @@
1
  from flask import Flask, jsonify, render_template, redirect, request, Response
2
  import os
3
  import json
 
4
  import requests
5
  import urllib.parse
6
  from datetime import datetime, timedelta
7
  from threading import Thread
8
- from hf_scrapper import get_system_proxies, stream_file
9
  from indexer import indexer
10
  from dotenv import load_dotenv
 
11
 
12
  load_dotenv()
13
  INDEX_FILE = os.getenv("INDEX_FILE")
@@ -57,9 +59,9 @@ def get_thetvdb_token():
57
 
58
  def fetch_and_cache_json(original_title, title, media_type, year=None):
59
  if year:
60
- search_url = f"{THETVDB_API_URL}/search?query={title}&type={media_type}&year={year}"
61
  else:
62
- search_url = f"{THETVDB_API_URL}/search?query={title}&type={media_type}"
63
 
64
  token = get_thetvdb_token()
65
  if not token:
@@ -93,19 +95,74 @@ def prefetch_metadata():
93
  media_type = 'series' if item['path'].startswith('tv') else 'movie'
94
  title = original_title
95
  year = None
96
- if any(char.isdigit() for char in original_title):
97
- # Strip year from title if present
98
- parts = original_title.rsplit(' ', 1)
99
- year_str = parts[-1]
 
100
  if year_str.isdigit() and len(year_str) == 4:
101
- title = parts[0]
102
  year = int(year_str)
 
 
 
 
 
 
 
103
  fetch_and_cache_json(original_title, title, media_type, year)
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  # Run prefetch_metadata in a background thread
106
  def start_prefetching():
107
  prefetch_metadata()
108
 
 
 
 
 
109
  # Start prefetching before running the Flask app
110
  thread = Thread(target=start_prefetching)
111
  thread.daemon = True
@@ -131,10 +188,45 @@ def list_tv():
131
  tv_shows = [item for item in file_structure if item['path'].startswith('tv')]
132
  return jsonify([sub_item for show in tv_shows for sub_item in show['contents']])
133
 
134
- @app.route('/play/<path:file_path>')
135
- def play(file_path):
136
- file_url = f"https://huggingface.co/{REPO}/resolve/main/{file_path}"
137
- return redirect(file_url)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
  @app.route('/get_metadata')
140
  def get_metadata():
@@ -154,18 +246,14 @@ def get_metadata():
154
 
155
  @app.route('/stream')
156
  def stream_video():
 
157
  file_path = request.args.get('path')
158
  if not file_path:
159
  return "File path not provided", 400
160
 
161
- proxies = get_system_proxies()
162
  file_url = f"https://huggingface.co/{REPO}/resolve/main/{file_path}"
163
 
164
- def generate():
165
- for chunk in stream_file(file_url, TOKEN, proxies):
166
- yield chunk
167
-
168
- return Response(generate(), content_type='video/mp4')
169
 
170
  if __name__ == '__main__':
171
  app.run(debug=True, host="0.0.0.0", port=7860)
 
1
  from flask import Flask, jsonify, render_template, redirect, request, Response
2
  import os
3
  import json
4
+ import re
5
  import requests
6
  import urllib.parse
7
  from datetime import datetime, timedelta
8
  from threading import Thread
9
+ from hf_scrapper import get_system_proxies, ffmpeg_stream
10
  from indexer import indexer
11
  from dotenv import load_dotenv
12
+ import subprocess
13
 
14
  load_dotenv()
15
  INDEX_FILE = os.getenv("INDEX_FILE")
 
59
 
60
  def fetch_and_cache_json(original_title, title, media_type, year=None):
61
  if year:
62
+ search_url = f"{THETVDB_API_URL}/search?query={urllib.parse.quote(title)}&type={media_type}&year={year}"
63
  else:
64
+ search_url = f"{THETVDB_API_URL}/search?query={urllib.parse.quote(title)}&type={media_type}"
65
 
66
  token = get_thetvdb_token()
67
  if not token:
 
95
  media_type = 'series' if item['path'].startswith('tv') else 'movie'
96
  title = original_title
97
  year = None
98
+
99
+ # Check if the title contains a year in parentheses
100
+ match = re.search(r'\((\d{4})\)', original_title)
101
+ if match:
102
+ year_str = match.group(1)
103
  if year_str.isdigit() and len(year_str) == 4:
104
+ title = original_title[:match.start()].strip()
105
  year = int(year_str)
106
+ else:
107
+ # Check if the title contains a year at the end without parentheses
108
+ parts = original_title.rsplit(' ', 1)
109
+ if len(parts) > 1 and parts[-1].isdigit() and len(parts[-1]) == 4:
110
+ title = parts[0].strip()
111
+ year = int(parts[-1])
112
+
113
  fetch_and_cache_json(original_title, title, media_type, year)
114
 
115
+ def get_film_file_path(title):
116
+ # URL-decode the title
117
+ decoded_title = urllib.parse.unquote(title)
118
+ # Normalize the title for comparison
119
+ normalized_title = decoded_title.split(' (')[0]
120
+ normalized_title = normalized_title.strip()
121
+
122
+ for item in file_structure:
123
+ if item['path'].startswith('films'):
124
+ for sub_item in item['contents']:
125
+ sub_item_title = sub_item['path'].split('/')[-1]
126
+ # Normalize sub_item title
127
+ normalized_sub_item_title = sub_item_title.split(' (')[0]
128
+ normalized_sub_item_title = normalized_sub_item_title.strip()
129
+
130
+ if normalized_title == normalized_sub_item_title:
131
+ for file in sub_item['contents']:
132
+ if file['type'] == 'file':
133
+ return file['path']
134
+ return None
135
+
136
+ def get_tv_show_seasons(title):
137
+ seasons = []
138
+ for item in file_structure:
139
+ if item['path'].startswith('tv'):
140
+ for sub_item in item['contents']:
141
+ if sub_item['type'] == 'directory' and title in sub_item['path']:
142
+ for season in sub_item['contents']:
143
+ if season['type'] == 'directory':
144
+ episodes = []
145
+ for episode in season['contents']:
146
+ if episode['type'] == 'file':
147
+ episodes.append({
148
+ "title": episode['path'].split('/')[-1],
149
+ "path": episode['path']
150
+ })
151
+ seasons.append({
152
+ "season": season['path'].split('/')[-1],
153
+ "episodes": episodes
154
+ })
155
+ return seasons
156
+ return []
157
+
158
  # Run prefetch_metadata in a background thread
159
  def start_prefetching():
160
  prefetch_metadata()
161
 
162
+ def generate(file_url):
163
+ token = TOKEN = os.getenv("TOKEN")
164
+ output_stream = ffmpeg_stream(file_url, token)
165
+
166
  # Start prefetching before running the Flask app
167
  thread = Thread(target=start_prefetching)
168
  thread.daemon = True
 
188
  tv_shows = [item for item in file_structure if item['path'].startswith('tv')]
189
  return jsonify([sub_item for show in tv_shows for sub_item in show['contents']])
190
 
191
+ @app.route('/film/<path:title>')
192
+ def film_page(title):
193
+ title = urllib.parse.unquote(title) # Decode the URL parameter
194
+ film_file_path = get_film_file_path(title)
195
+ if not film_file_path:
196
+ return jsonify({'error': 'Film not found'}), 404
197
+
198
+ # Fetch cached metadata
199
+ json_cache_path = os.path.join(CACHE_DIR, f"{urllib.parse.quote(title)}.json")
200
+ if os.path.exists(json_cache_path):
201
+ with open(json_cache_path, 'r') as f:
202
+ metadata = json.load(f)
203
+ else:
204
+ return jsonify({'error': 'Metadata not found'}), 404
205
+
206
+ return jsonify({
207
+ 'metadata': metadata,
208
+ 'file_path': film_file_path
209
+ })
210
+
211
+ @app.route('/tv/<path:show_title>')
212
+ def tv_page(show_title):
213
+ show_title = urllib.parse.unquote(show_title) # Decode the URL parameter
214
+ seasons = get_tv_show_seasons(show_title)
215
+ if not seasons:
216
+ return jsonify({'error': 'TV show not found'}), 404
217
+
218
+ # Fetch cached metadata
219
+ json_cache_path = os.path.join(CACHE_DIR, f"{urllib.parse.quote(show_title)}.json")
220
+ if os.path.exists(json_cache_path):
221
+ with open(json_cache_path, 'r') as f:
222
+ metadata = json.load(f)
223
+ else:
224
+ return jsonify({'error': 'Metadata not found'}), 404
225
+
226
+ return jsonify({
227
+ 'metadata': metadata,
228
+ 'seasons': seasons
229
+ })
230
 
231
  @app.route('/get_metadata')
232
  def get_metadata():
 
246
 
247
  @app.route('/stream')
248
  def stream_video():
249
+ # this route currently only stream the file from huggingface using ffmpy. can't play them in the web yet. need to implement later.
250
  file_path = request.args.get('path')
251
  if not file_path:
252
  return "File path not provided", 400
253
 
 
254
  file_url = f"https://huggingface.co/{REPO}/resolve/main/{file_path}"
255
 
256
+ return Response(generate(file_url), content_type='video/mp4')
 
 
 
 
257
 
258
  if __name__ == '__main__':
259
  app.run(debug=True, host="0.0.0.0", port=7860)
hf_scrapper.py CHANGED
@@ -1,8 +1,10 @@
1
  import os
 
2
  import requests
3
  import json
4
  import urllib.request
5
  from requests.exceptions import RequestException
 
6
 
7
  def get_system_proxies():
8
  try:
@@ -10,38 +12,46 @@ def get_system_proxies():
10
  print("System proxies:", proxies)
11
  return {
12
  "http": proxies.get("http"),
13
- "https": proxies.get("http")
14
  }
15
  except Exception as e:
16
  print(f"Error getting system proxies: {e}")
17
  return {}
18
 
19
- def stream_file(file_url, token, proxies):
20
- print(f"Streaming file from URL: {file_url} with proxies: {proxies}")
21
  try:
22
- response = requests.get(file_url, headers={'Authorization': f'Bearer {token}'}, proxies=proxies, stream=True)
23
- response.raise_for_status()
24
- for chunk in response.iter_content(chunk_size=8192):
25
- if chunk:
26
- yield chunk
27
- except RequestException as e:
28
- print(f"Error streaming file: {e}")
29
- yield b'' # Return empty bytes to indicate an error
 
 
 
 
 
30
 
31
- def download_file(file_url, token, output_path, proxies):
32
- print(f"Downloading file from URL: {file_url} with proxies: {proxies}")
33
- try:
34
- response = requests.get(file_url, headers={'Authorization': f'Bearer {token}'}, proxies=proxies, stream=True)
35
- response.raise_for_status()
36
- with open(output_path, 'wb') as f:
37
- for chunk in response.iter_content(chunk_size=8192):
38
- if chunk:
39
- f.write(chunk)
40
- print(f'File {output_path} downloaded successfully.')
41
- except RequestException as e:
42
- print(f"Error downloading file: {e}")
43
- except IOError as e:
44
- print(f"Error writing file {output_path}: {e}")
 
 
 
 
45
 
46
  def get_file_structure(repo, token, path="", proxies=None):
47
  api_url = f"https://huggingface.co/api/models/{repo}/tree/main/{path}"
@@ -62,3 +72,12 @@ def write_file_structure_to_json(file_structure, file_path):
62
  print(f'File structure written to {file_path}')
63
  except IOError as e:
64
  print(f"Error writing file structure to JSON: {e}")
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import subprocess
3
  import requests
4
  import json
5
  import urllib.request
6
  from requests.exceptions import RequestException
7
+ from ffmpy import FFmpeg
8
 
9
  def get_system_proxies():
10
  try:
 
12
  print("System proxies:", proxies)
13
  return {
14
  "http": proxies.get("http"),
15
+ "https": proxies.get("https")
16
  }
17
  except Exception as e:
18
  print(f"Error getting system proxies: {e}")
19
  return {}
20
 
21
+ def ffmpeg_stream(file_url,token, output="tmp/cache/output.mp4"):
 
22
  try:
23
+ # Set up the FFmpeg command with quality options
24
+ ff = FFmpeg(
25
+ inputs={
26
+ file_url: None
27
+ },
28
+ outputs={
29
+ f'{output}': '-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 192k'
30
+ },
31
+ global_options= f'-headers "Authorization: Bearer {token}"'
32
+ )
33
+
34
+ # Run the command
35
+ ff.run()
36
 
37
+ except Exception as e:
38
+ print(f"Error using FFmpeg to stream file: {e}")
39
+ return None
40
+
41
+ # def download_file(file_url, token, output_path, proxies):
42
+ # print(f"Downloading file from URL: {file_url} with proxies: {proxies}")
43
+ # try:
44
+ # response = requests.get(file_url, headers={'Authorization': f'Bearer {token}'}, proxies=proxies, stream=True)
45
+ # response.raise_for_status()
46
+ # with open(output_path, 'wb') as f:
47
+ # for chunk in response.iter_content(chunk_size=8192):
48
+ # if chunk:
49
+ # f.write(chunk)
50
+ # print(f'File {output_path} downloaded successfully.')
51
+ # except RequestException as e:
52
+ # print(f"Error downloading file: {e}")
53
+ # except IOError as e:
54
+ # print(f"Error writing file {output_path}: {e}")
55
 
56
  def get_file_structure(repo, token, path="", proxies=None):
57
  api_url = f"https://huggingface.co/api/models/{repo}/tree/main/{path}"
 
72
  print(f'File structure written to {file_path}')
73
  except IOError as e:
74
  print(f"Error writing file structure to JSON: {e}")
75
+
76
+ # Example usage
77
+ if __name__ == "__main__":
78
+ proxies = get_system_proxies()
79
+ token = 'hf token'
80
+ file_url = 'file url'
81
+
82
+ # Stream the video
83
+ output_stream = ffmpeg_stream(file_url, token, output="tmp/cache/video.mp4")
requirements.txt CHANGED
@@ -1,4 +1,5 @@
1
  flask
2
  Flask-Cors
3
  requests
4
- python-dotenv
 
 
1
  flask
2
  Flask-Cors
3
  requests
4
+ python-dotenv
5
+ ffmpy
templates/film_page.html ADDED
File without changes
templates/index.html CHANGED
@@ -39,6 +39,7 @@
39
  overflow: hidden;
40
  text-align: center;
41
  transition: transform 0.2s;
 
42
  }
43
  .card:hover {
44
  transform: scale(1.05);
@@ -57,17 +58,6 @@
57
  margin: 0;
58
  padding: 10px;
59
  }
60
- .card a {
61
- display: block;
62
- padding: 10px;
63
- color: #fff;
64
- background-color: #e50914;
65
- text-decoration: none;
66
- border-radius: 0 0 10px 10px;
67
- }
68
- .card a:hover {
69
- background-color: #f40612;
70
- }
71
  </style>
72
  </head>
73
  <body>
@@ -119,8 +109,15 @@
119
  card.innerHTML = `
120
  <img src="https://via.placeholder.com/340x500.png" alt="${title}">
121
  <h3>${title}</h3>
122
- <a href="/player?path=${encodeURIComponent(item.path)}&type=${mediaType}">Play</a>
123
  `;
 
 
 
 
 
 
 
 
124
  fetchMetadata(title).then(thumbnailUrl => {
125
  if (thumbnailUrl !== null) {
126
  card.querySelector('img').src = thumbnailUrl;
 
39
  overflow: hidden;
40
  text-align: center;
41
  transition: transform 0.2s;
42
+ cursor: pointer;
43
  }
44
  .card:hover {
45
  transform: scale(1.05);
 
58
  margin: 0;
59
  padding: 10px;
60
  }
 
 
 
 
 
 
 
 
 
 
 
61
  </style>
62
  </head>
63
  <body>
 
109
  card.innerHTML = `
110
  <img src="https://via.placeholder.com/340x500.png" alt="${title}">
111
  <h3>${title}</h3>
 
112
  `;
113
+ // Redirect to the appropriate page on card click
114
+ card.onclick = () => {
115
+ if (mediaType === 'movie') {
116
+ window.location.href = `/film/${encodeURIComponent(title)}`;
117
+ } else if (mediaType === 'series') {
118
+ window.location.href = `/tv/${encodeURIComponent(title)}`;
119
+ }
120
+ };
121
  fetchMetadata(title).then(thumbnailUrl => {
122
  if (thumbnailUrl !== null) {
123
  card.querySelector('img').src = thumbnailUrl;
templates/player.html CHANGED
@@ -9,69 +9,42 @@
9
  color: #fff;
10
  margin: 0;
11
  padding: 0;
12
- display: flex;
13
- align-items: center;
14
- justify-content: center;
15
- height: 100vh;
 
 
 
 
 
 
 
 
16
  }
17
  .player-container {
 
 
18
  text-align: center;
19
  }
20
  video {
21
- width: 80%;
22
  height: auto;
23
- margin-top: 20px;
24
  }
25
  </style>
26
  </head>
27
  <body>
28
- <div class="player-container">
29
- <h1 id="title"></h1>
30
- <video id="videoPlayer" controls></video>
 
 
 
 
 
 
 
31
  </div>
32
- <script>
33
- function getQueryParams() {
34
- const params = {};
35
- window.location.search.substring(1).split("&").forEach(pair => {
36
- const [key, value] = pair.split("=");
37
- params[decodeURIComponent(key)] = decodeURIComponent(value);
38
- });
39
- return params;
40
- }
41
-
42
- async function fetchMetadata(title) {
43
- try {
44
- const response = await fetch(`/get_metadata?title=${encodeURIComponent(title)}`);
45
- if (response.ok) {
46
- return await response.json();
47
- }
48
- } catch (error) {
49
- console.error('Metadata fetch error:', error);
50
- }
51
- return null;
52
- }
53
-
54
- async function initializePlayer() {
55
- const params = getQueryParams();
56
- const { path, type } = params;
57
- const title = path.split('/').pop();
58
-
59
- document.getElementById('title').textContent = title;
60
-
61
- const metadata = await fetchMetadata(title);
62
- if (metadata && metadata.data && metadata.data.length > 0) {
63
- console.log(`Loaded metadata for ${title}:`, metadata);
64
- } else {
65
- console.log(`No metadata found for ${title}`);
66
- }
67
-
68
- const videoPlayer = document.getElementById('videoPlayer');
69
- const videoUrl = `https://huggingface.co/${REPO}/resolve/main/${path}`;
70
- videoPlayer.src = videoUrl;
71
- console.log(`Playing video from URL: ${videoUrl}`);
72
- }
73
-
74
- initializePlayer();
75
- </script>
76
  </body>
77
  </html>
 
9
  color: #fff;
10
  margin: 0;
11
  padding: 0;
12
+ }
13
+ .header {
14
+ background-color: #000;
15
+ padding: 20px;
16
+ text-align: center;
17
+ }
18
+ .header h1 {
19
+ margin: 0;
20
+ font-size: 24px;
21
+ }
22
+ .content {
23
+ padding: 20px;
24
  }
25
  .player-container {
26
+ max-width: 100%;
27
+ margin: auto;
28
  text-align: center;
29
  }
30
  video {
31
+ width: 100%;
32
  height: auto;
33
+ background-color: #333;
34
  }
35
  </style>
36
  </head>
37
  <body>
38
+ <div class="header">
39
+ <h1>Media Player</h1>
40
+ </div>
41
+ <div class="content">
42
+ <div class="player-container">
43
+ <video id="videoPlayer" controls>
44
+ <source id="videoSource" type="video/mp4">
45
+ Your browser does not support the video tag.
46
+ </video>
47
+ </div>
48
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  </body>
50
  </html>
templates/tv_page.html ADDED
File without changes