ChandimaPrabath commited on
Commit
e264f26
·
1 Parent(s): 6fdb1da
app.py CHANGED
@@ -1,34 +1,58 @@
1
- from flask import Flask, jsonify, render_template, request, Response, abort, send_from_directory
2
  import os
3
- import urllib.parse
4
- from hf_scrapper import download_and_cache_file, get_system_proxies
 
5
  from indexer import indexer
6
  from tvdb import fetch_and_cache_json
7
- from dotenv import load_dotenv
8
- import json
9
  import re
10
- from threading import Thread
11
- from flask_socketio import SocketIO, emit
12
 
13
- load_dotenv()
 
 
 
14
  INDEX_FILE = os.getenv("INDEX_FILE")
15
  TOKEN = os.getenv("TOKEN")
16
- REPO = os.getenv("REPO")
17
- CACHE_DIR = os.getenv("CACHE_DIR")
18
 
19
  # Ensure CACHE_DIR exists
20
  if not os.path.exists(CACHE_DIR):
21
  os.makedirs(CACHE_DIR)
22
 
 
23
  indexer()
24
 
 
25
  if not os.path.exists(INDEX_FILE):
26
  raise FileNotFoundError(f"{INDEX_FILE} not found. Please make sure the file exists.")
27
 
28
  with open(INDEX_FILE, 'r') as f:
29
  file_structure = json.load(f)
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  def prefetch_metadata():
 
32
  for item in file_structure:
33
  if 'contents' in item:
34
  for sub_item in item['contents']:
@@ -37,6 +61,7 @@ def prefetch_metadata():
37
  title = original_title
38
  year = None
39
 
 
40
  match = re.search(r'\((\d{4})\)', original_title)
41
  if match:
42
  year_str = match.group(1)
@@ -52,204 +77,60 @@ def prefetch_metadata():
52
  fetch_and_cache_json(original_title, title, media_type, year)
53
 
54
  def start_prefetching():
 
55
  prefetch_metadata()
56
 
57
- thread = Thread(target=start_prefetching)
 
58
  thread.daemon = True
59
  thread.start()
60
 
61
- app = Flask(__name__)
62
- socketio = SocketIO(app)
63
-
64
- def get_film_file_path(title):
65
- decoded_title = urllib.parse.unquote(title)
66
- normalized_title = decoded_title.split(' (')[0].strip()
67
-
68
- for item in file_structure:
69
- if item['path'].startswith('films'):
70
- for sub_item in item['contents']:
71
- sub_item_title = sub_item['path'].split('/')[-1]
72
- normalized_sub_item_title = sub_item_title.split(' (')[0].strip()
73
-
74
- if normalized_title == normalized_sub_item_title:
75
- for file in sub_item['contents']:
76
- if file['type'] == 'file':
77
- return file['path']
78
- return None
79
 
80
- def get_tv_show_seasons(title):
81
- seasons = []
82
- for item in file_structure:
83
- if item['path'].startswith('tv'):
84
- for sub_item in item['contents']:
85
- if sub_item['type'] == 'directory' and title in sub_item['path']:
86
- for season in sub_item['contents']:
87
- if season['type'] == 'directory':
88
- episodes = [
89
- {"title": episode['path'].split('/')[-1], "path": episode['path']}
90
- for episode in season['contents'] if episode['type'] == 'file'
91
- ]
92
- seasons.append({
93
- "season": season['path'].split('/')[-1],
94
- "episodes": episodes
95
- })
96
- return seasons
97
- return []
98
-
99
- def download_film_if_needed(file_path):
100
- cached_file_path = os.path.join(CACHE_DIR, file_path)
101
- if not os.path.exists(cached_file_path):
102
- file_url = f"https://huggingface.co/{REPO}/resolve/main/{file_path}"
103
- success = download_and_cache_file(file_url, TOKEN, cached_file_path, proxies=get_system_proxies())
104
- if not success:
105
- abort(404, description="File not found or failed to download.")
106
- return cached_file_path
107
-
108
- @app.route('/')
109
- def home():
110
- return render_template('index.html')
111
-
112
- @app.route('/films')
113
- def films():
114
- return render_template('films.html')
115
-
116
- @app.route('/api/films')
117
- def list_films():
118
- films = [item for item in file_structure if item['path'].startswith('films')]
119
- return jsonify([sub_item for film in films for sub_item in film['contents']])
120
-
121
- @app.route('/tv')
122
- def tv_shows():
123
- return render_template('tvshows.html')
124
-
125
- @app.route('/api/tv')
126
- def list_tv():
127
- tv_shows = [item for item in file_structure if item['path'].startswith('tv')]
128
- return jsonify([sub_item for show in tv_shows for sub_item in show['contents']])
129
-
130
- @app.route('/api/film/<path:title>')
131
- def film_page(title):
132
- title = urllib.parse.unquote(title)
133
- film_file_path = get_film_file_path(title)
134
-
135
- if not film_file_path:
136
- return jsonify({'error': 'Film not found'}), 404
137
-
138
- json_cache_path = os.path.join(CACHE_DIR, f"{urllib.parse.quote(title)}.json")
139
- if os.path.exists(json_cache_path):
140
- with open(json_cache_path, 'r') as f:
141
- metadata = json.load(f)
142
- else:
143
- return jsonify({'error': 'Metadata not found'}), 404
144
-
145
- cached_url = f"/cached_films/{urllib.parse.quote(title)}"
146
- return jsonify({
147
- 'metadata': metadata,
148
- 'cached_url': cached_url
149
- })
150
-
151
- @app.route('/api/tv/<path:show_title>')
152
- def tv_page(show_title):
153
- show_title = urllib.parse.unquote(show_title)
154
- seasons = get_tv_show_seasons(show_title)
155
- if not seasons:
156
- return jsonify({'error': 'TV show not found'}), 404
157
-
158
- json_cache_path = os.path.join(CACHE_DIR, f"{urllib.parse.quote(show_title)}.json")
159
- if os.path.exists(json_cache_path):
160
- with open(json_cache_path, 'r') as f:
161
- metadata = json.load(f)
162
- else:
163
- return jsonify({'error': 'Metadata not found'}), 404
164
-
165
- return jsonify({
166
- 'metadata': metadata,
167
- 'seasons': seasons
168
- })
169
-
170
- @app.route('/get_metadata')
171
- def get_metadata():
172
  title = request.args.get('title')
173
  if not title:
174
- return jsonify({'error': 'No title provided'}), 400
175
-
176
- json_cache_path = os.path.join(CACHE_DIR, f"{urllib.parse.quote(title)}.json")
177
-
178
- if os.path.exists(json_cache_path):
179
- with open(json_cache_path, 'r') as f:
180
- data = json.load(f)
181
- return jsonify(data)
182
-
183
- return jsonify({'error': 'Metadata not found'}), 404
184
-
185
- @app.route('/api/cache_film/<path:title>')
186
- def cache_film(title):
187
- title = urllib.parse.unquote(title)
188
- film_file_path = get_film_file_path(title)
189
- if not film_file_path:
190
- return jsonify({'error': 'Film not found'}), 404
191
-
192
- cached_file_path = os.path.join(CACHE_DIR, film_file_path)
193
- if not os.path.exists(cached_file_path):
194
- file_url = f"https://huggingface.co/{REPO}/resolve/main/{film_file_path}"
195
- success = download_and_cache_file(file_url, TOKEN, cached_file_path, proxies=get_system_proxies())
196
- if not success:
197
- return jsonify({'error': 'Failed to cache film'}), 500
198
-
199
- return jsonify({'success': True})
200
-
201
- @app.route('/film/<path:title>')
202
- def film_details(title):
203
- return render_template('film_details_page.html', title=title)
204
-
205
- @app.route('/film_player/<path:title>')
206
- def film_player(title):
207
- title = urllib.parse.unquote(title)
208
- film_file_path = get_film_file_path(title)
209
-
210
- if not film_file_path:
211
- return jsonify({'error': 'Film not found'}), 404
212
-
213
- cached_file_path = os.path.join(CACHE_DIR, film_file_path)
214
- if not os.path.exists(cached_file_path):
215
- return jsonify({'error': 'Film not cached'}), 404
216
-
217
- # Provide the URL to the cached file
218
- cached_url = f"/cached_films/{urllib.parse.quote(title)}"
219
-
220
- return render_template('film_player.html', title=title, cached_url=cached_url)
221
-
222
- @app.route('/cached_films/<path:title>')
223
- def serve_cached_film(title):
224
- title = urllib.parse.unquote(title)
225
- cached_file_path = get_film_file_path(title)
226
- if not cached_file_path:
227
- return jsonify({'error': 'Film not found'}), 404
228
-
229
- return send_from_directory(os.path.dirname(cached_file_path), os.path.basename(cached_file_path))
230
-
231
- @socketio.on('download')
232
- def handle_download(data):
233
- title = data.get('title')
234
- film_file_path = get_film_file_path(title)
235
- if not film_file_path:
236
- emit('download_progress', {'error': 'Film not found'})
237
- return
238
-
239
- cached_file_path = os.path.join(CACHE_DIR, film_file_path)
240
- if os.path.exists(cached_file_path):
241
- emit('download_progress', {'progress': 100, 'status': 'already cached'})
242
- return
243
-
244
- file_url = f"https://huggingface.co/{REPO}/resolve/main/{film_file_path}"
245
- def download_callback(progress):
246
- socketio.emit('download_progress', {'progress': progress})
247
-
248
- success = download_and_cache_file(file_url, TOKEN, cached_file_path, proxies=get_system_proxies(), callback=download_callback)
249
- if not success:
250
- emit('download_progress', {'error': 'Failed to download film'})
251
- else:
252
- emit('download_progress', {'progress': 100, 'status': 'completed'})
253
 
 
254
  if __name__ == "__main__":
255
- socketio.run(app, host='0.0.0.0', port=7860, allow_unsafe_werkzeug=True)
 
1
+ from flask import Flask, jsonify, request, send_from_directory
2
  import os
3
+ import json
4
+ import threading
5
+ from hf_scrapper import download_file, get_system_proxies, get_download_progress
6
  from indexer import indexer
7
  from tvdb import fetch_and_cache_json
 
 
8
  import re
 
 
9
 
10
+ app = Flask(__name__)
11
+
12
+ # Constants and Configuration
13
+ CACHE_DIR = os.getenv("CACHE_DIR")
14
  INDEX_FILE = os.getenv("INDEX_FILE")
15
  TOKEN = os.getenv("TOKEN")
16
+ download_threads = {}
 
17
 
18
  # Ensure CACHE_DIR exists
19
  if not os.path.exists(CACHE_DIR):
20
  os.makedirs(CACHE_DIR)
21
 
22
+ # Index the file structure
23
  indexer()
24
 
25
+ # Load the file structure JSON
26
  if not os.path.exists(INDEX_FILE):
27
  raise FileNotFoundError(f"{INDEX_FILE} not found. Please make sure the file exists.")
28
 
29
  with open(INDEX_FILE, 'r') as f:
30
  file_structure = json.load(f)
31
 
32
+ # Function Definitions
33
+
34
+ def load_json(file_path):
35
+ """Load JSON data from a file."""
36
+ with open(file_path, 'r') as file:
37
+ return json.load(file)
38
+
39
+ def find_movie_path(json_data, title):
40
+ """Find the path of the movie in the JSON data based on the title."""
41
+ for directory in json_data:
42
+ if directory['type'] == 'directory' and directory['path'] == 'films':
43
+ for sub_directory in directory['contents']:
44
+ if sub_directory['type'] == 'directory':
45
+ for item in sub_directory['contents']:
46
+ if item['type'] == 'file' and title.lower() in item['path'].lower():
47
+ return item['path']
48
+ return None
49
+
50
+ def get_film_id(title):
51
+ """Generate a film ID based on the title."""
52
+ return title.replace(" ", "_").lower()
53
+
54
  def prefetch_metadata():
55
+ """Prefetch metadata for all items in the file structure."""
56
  for item in file_structure:
57
  if 'contents' in item:
58
  for sub_item in item['contents']:
 
61
  title = original_title
62
  year = None
63
 
64
+ # Extract year from the title if available
65
  match = re.search(r'\((\d{4})\)', original_title)
66
  if match:
67
  year_str = match.group(1)
 
77
  fetch_and_cache_json(original_title, title, media_type, year)
78
 
79
  def start_prefetching():
80
+ """Start the metadata prefetching in a separate thread."""
81
  prefetch_metadata()
82
 
83
+ # Start prefetching metadata
84
+ thread = threading.Thread(target=start_prefetching)
85
  thread.daemon = True
86
  thread.start()
87
 
88
+ # API Endpoints
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
+ @app.route('/api/movie', methods=['GET'])
91
+ def get_movie():
92
+ """Endpoint to get the movie by title."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  title = request.args.get('title')
94
  if not title:
95
+ return jsonify({"error": "Title parameter is required"}), 400
96
+
97
+ json_data = load_json(INDEX_FILE)
98
+ movie_path = find_movie_path(json_data, title)
99
+
100
+ if not movie_path:
101
+ return jsonify({"error": "Movie not found"}), 404
102
+
103
+ cache_path = os.path.join(CACHE_DIR, movie_path)
104
+ if not os.path.exists(cache_path):
105
+ file_url = f"https://huggingface.co/Unicone-Studio/jellyfin_media/resolve/main/{movie_path}"
106
+ proxies = get_system_proxies()
107
+ film_id = get_film_id(title)
108
+
109
+ # Start the download in a separate thread if not already downloading
110
+ if film_id not in download_threads or not download_threads[film_id].is_alive():
111
+ thread = threading.Thread(target=download_file, args=(file_url, TOKEN, cache_path, proxies, film_id))
112
+ download_threads[film_id] = thread
113
+ thread.start()
114
+
115
+ return jsonify({"status": "Download started", "film_id": film_id})
116
+
117
+ return send_from_directory(os.path.dirname(cache_path), os.path.basename(cache_path))
118
+
119
+ @app.route('/api/progress/<film_id>', methods=['GET'])
120
+ def get_progress(film_id):
121
+ """Endpoint to get the download progress of a movie."""
122
+ progress = get_download_progress(film_id)
123
+ return jsonify({"film_id": film_id, "progress": progress})
124
+
125
+ @app.route('/api/filmid', methods=['GET'])
126
+ def get_film_id_by_title():
127
+ """Endpoint to get the film ID by providing the movie title."""
128
+ title = request.args.get('title')
129
+ if not title:
130
+ return jsonify({"error": "Title parameter is required"}), 400
131
+ film_id = get_film_id(title)
132
+ return jsonify({"film_id": film_id})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
+ # Main entry point
135
  if __name__ == "__main__":
136
+ app.run(debug=True, host="0.0.0.0", port=7860)
hf_scrapper.py CHANGED
@@ -4,13 +4,19 @@ import json
4
  import urllib.request
5
  from requests.exceptions import RequestException
6
  from tqdm import tqdm
7
- from concurrent.futures import ThreadPoolExecutor, as_completed
8
 
9
  CACHE_DIR = os.getenv("CACHE_DIR")
10
  CACHE_JSON_PATH = os.path.join(CACHE_DIR, "cached_films.json")
11
- MAX_WORKERS = 4 # Adjust the number of threads for concurrent downloads
 
12
 
13
  def get_system_proxies():
 
 
 
 
 
 
14
  try:
15
  proxies = urllib.request.getproxies()
16
  print("System proxies:", proxies)
@@ -22,43 +28,70 @@ def get_system_proxies():
22
  print(f"Error getting system proxies: {e}")
23
  return {}
24
 
25
- def download_file_chunk(url, headers, proxies, start, end):
26
- headers['Range'] = f"bytes={start}-{end}"
27
- response = requests.get(url, headers=headers, proxies=proxies, stream=True)
28
- response.raise_for_status()
29
- return response.content
30
 
31
- def download_and_cache_file(file_url, token, cache_path, proxies=None):
 
 
 
 
 
 
 
32
  print(f"Downloading file from URL: {file_url} to {cache_path} with proxies: {proxies}")
33
  headers = {'Authorization': f'Bearer {token}'}
34
  try:
35
- response = requests.head(file_url, headers=headers, proxies=proxies)
36
  response.raise_for_status()
37
-
38
  total_size = int(response.headers.get('content-length', 0))
39
- os.makedirs(os.path.dirname(cache_path), exist_ok=True)
40
-
41
- chunk_size = total_size // MAX_WORKERS
42
- ranges = [(i, min(i + chunk_size, total_size) - 1) for i in range(0, total_size, chunk_size)]
43
-
44
- with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
45
- futures = [executor.submit(download_file_chunk, file_url, headers, proxies, start, end) for start, end in ranges]
46
- with open(cache_path, 'wb') as f, tqdm(total=total_size, unit='B', unit_scale=True, desc=cache_path) as pbar:
47
- for future in as_completed(futures):
48
- chunk = future.result()
49
- f.write(chunk)
50
- pbar.update(len(chunk))
51
 
 
 
 
 
 
 
 
52
  print(f'File cached to {cache_path} successfully.')
53
  update_cache_json(file_url, cache_path)
54
- return True
55
  except RequestException as e:
56
  print(f"Error downloading file: {e}")
57
  except IOError as e:
58
  print(f"Error writing file {cache_path}: {e}")
59
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
  def update_cache_json(file_url, cache_path):
 
 
 
 
 
 
 
62
  cache_data = {}
63
  if os.path.exists(CACHE_JSON_PATH):
64
  with open(CACHE_JSON_PATH, 'r') as json_file:
@@ -69,10 +102,21 @@ def update_cache_json(file_url, cache_path):
69
 
70
  with open(CACHE_JSON_PATH, 'w') as json_file:
71
  json.dump(cache_data, json_file, indent=2)
72
-
73
- print(f'Updated cache JSON: {CACHE_JSON_PATH} with {film_title}: {cache_path}')
74
 
75
  def get_file_structure(repo, token, path="", proxies=None):
 
 
 
 
 
 
 
 
 
 
 
 
76
  api_url = f"https://huggingface.co/api/models/{repo}/tree/main/{path}"
77
  headers = {'Authorization': f'Bearer {token}'}
78
  print(f"Fetching file structure from URL: {api_url} with proxies: {proxies}")
@@ -85,6 +129,13 @@ def get_file_structure(repo, token, path="", proxies=None):
85
  return []
86
 
87
  def write_file_structure_to_json(file_structure, file_path):
 
 
 
 
 
 
 
88
  try:
89
  with open(file_path, 'w') as json_file:
90
  json.dump(file_structure, json_file, indent=2)
@@ -96,5 +147,6 @@ if __name__ == "__main__":
96
  file_url = "https://huggingface.co/Unicone-Studio/jellyfin_media/resolve/main/films/Funky%20Monkey%202004/Funky%20Monkey%20(2004)%20Web-dl%201080p.mp4"
97
  token = os.getenv("TOKEN")
98
  cache_path = os.path.join(CACHE_DIR, "films/Funky Monkey 2004/Funky Monkey (2004) Web-dl 1080p.mp4")
99
- proxy = get_system_proxies()
100
- download_and_cache_file(file_url, token, cache_path, proxies=proxy)
 
 
4
  import urllib.request
5
  from requests.exceptions import RequestException
6
  from tqdm import tqdm
 
7
 
8
  CACHE_DIR = os.getenv("CACHE_DIR")
9
  CACHE_JSON_PATH = os.path.join(CACHE_DIR, "cached_films.json")
10
+
11
+ download_progress = {}
12
 
13
  def get_system_proxies():
14
+ """
15
+ Retrieves the system's HTTP and HTTPS proxies.
16
+
17
+ Returns:
18
+ dict: A dictionary containing the proxies.
19
+ """
20
  try:
21
  proxies = urllib.request.getproxies()
22
  print("System proxies:", proxies)
 
28
  print(f"Error getting system proxies: {e}")
29
  return {}
30
 
31
+ def download_file(file_url, token, cache_path, proxies, film_id, chunk_size=50 * 1024 * 1024): # 50MB chunk size
32
+ """
33
+ Downloads a file from the specified URL and saves it to the cache path.
34
+ Tracks the download progress.
 
35
 
36
+ Args:
37
+ file_url (str): The URL of the file to download.
38
+ token (str): The authorization token for the request.
39
+ cache_path (str): The path to save the downloaded file.
40
+ proxies (dict): Proxies for the request.
41
+ film_id (str): Unique identifier for the film download.
42
+ chunk_size (int): Size of each chunk to download.
43
+ """
44
  print(f"Downloading file from URL: {file_url} to {cache_path} with proxies: {proxies}")
45
  headers = {'Authorization': f'Bearer {token}'}
46
  try:
47
+ response = requests.get(file_url, headers=headers, proxies=proxies, stream=True)
48
  response.raise_for_status()
49
+
50
  total_size = int(response.headers.get('content-length', 0))
51
+ download_progress[film_id] = {"total": total_size, "downloaded": 0}
 
 
 
 
 
 
 
 
 
 
 
52
 
53
+ os.makedirs(os.path.dirname(cache_path), exist_ok=True)
54
+ with open(cache_path, 'wb') as file, tqdm(total=total_size, unit='B', unit_scale=True, desc=cache_path) as pbar:
55
+ for data in response.iter_content(chunk_size=chunk_size):
56
+ file.write(data)
57
+ pbar.update(len(data))
58
+ download_progress[film_id]["downloaded"] += len(data)
59
+
60
  print(f'File cached to {cache_path} successfully.')
61
  update_cache_json(file_url, cache_path)
 
62
  except RequestException as e:
63
  print(f"Error downloading file: {e}")
64
  except IOError as e:
65
  print(f"Error writing file {cache_path}: {e}")
66
+ finally:
67
+ del download_progress[film_id]
68
+
69
+
70
+ def get_download_progress(film_id):
71
+ """
72
+ Gets the download progress for a specific film.
73
+
74
+ Args:
75
+ film_id (str): The unique identifier for the film download.
76
+
77
+ Returns:
78
+ dict: A dictionary containing the total size, downloaded size, and progress percentage.
79
+ """
80
+ if film_id in download_progress:
81
+ total = download_progress[film_id]["total"]
82
+ downloaded = download_progress[film_id]["downloaded"]
83
+ progress = (downloaded / total) * 100
84
+ return {"total": total, "downloaded": downloaded, "progress": progress}
85
+ return {"total": 0, "downloaded": 0, "progress": 0}
86
 
87
  def update_cache_json(file_url, cache_path):
88
+ """
89
+ Updates the cached films JSON with the new file.
90
+
91
+ Args:
92
+ file_url (str): The URL of the downloaded file.
93
+ cache_path (str): The local path where the file is saved.
94
+ """
95
  cache_data = {}
96
  if os.path.exists(CACHE_JSON_PATH):
97
  with open(CACHE_JSON_PATH, 'r') as json_file:
 
102
 
103
  with open(CACHE_JSON_PATH, 'w') as json_file:
104
  json.dump(cache_data, json_file, indent=2)
105
+ print(f'Cache updated with {film_title}.')
 
106
 
107
  def get_file_structure(repo, token, path="", proxies=None):
108
+ """
109
+ Fetches the file structure of a specified Hugging Face repository.
110
+
111
+ Args:
112
+ repo (str): The name of the repository.
113
+ token (str): The authorization token for the request.
114
+ path (str, optional): The specific path in the repository. Defaults to "".
115
+ proxies (dict, optional): The proxies to use for the request. Defaults to None.
116
+
117
+ Returns:
118
+ list: A list of file structure information.
119
+ """
120
  api_url = f"https://huggingface.co/api/models/{repo}/tree/main/{path}"
121
  headers = {'Authorization': f'Bearer {token}'}
122
  print(f"Fetching file structure from URL: {api_url} with proxies: {proxies}")
 
129
  return []
130
 
131
  def write_file_structure_to_json(file_structure, file_path):
132
+ """
133
+ Writes the file structure to a JSON file.
134
+
135
+ Args:
136
+ file_structure (list): The file structure data.
137
+ file_path (str): The path where the JSON file will be saved.
138
+ """
139
  try:
140
  with open(file_path, 'w') as json_file:
141
  json.dump(file_structure, json_file, indent=2)
 
147
  file_url = "https://huggingface.co/Unicone-Studio/jellyfin_media/resolve/main/films/Funky%20Monkey%202004/Funky%20Monkey%20(2004)%20Web-dl%201080p.mp4"
148
  token = os.getenv("TOKEN")
149
  cache_path = os.path.join(CACHE_DIR, "films/Funky Monkey 2004/Funky Monkey (2004) Web-dl 1080p.mp4")
150
+ proxies = get_system_proxies()
151
+ film_id = "funky_monkey_2004" # Unique identifier for the film download
152
+ download_file(file_url, token, cache_path, proxies=proxies, film_id=film_id)
templates/film_details_page.html DELETED
@@ -1,203 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Film Details</title>
7
- <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.min.js" integrity="sha384-13fTj1YYl5Xk8bo8dqPG7ZNmTPukjZ08s5hlIBvbxkQa2ClOsuT9hALo5eUn53I1" crossorigin="anonymous"></script>
8
- <style>
9
- body {
10
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
- background: linear-gradient(to right, #141414, #333);
12
- color: #fff;
13
- margin: 0;
14
- padding: 0;
15
- display: flex;
16
- justify-content: center;
17
- align-items: center;
18
- min-height: 100vh;
19
- }
20
- .container {
21
- width: 90%;
22
- max-width: 1200px;
23
- display: flex;
24
- gap: 20px;
25
- padding: 20px;
26
- background-color: #1e1e1e;
27
- border-radius: 10px;
28
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
29
- }
30
- .poster {
31
- flex: 1;
32
- max-width: 300px;
33
- position: relative;
34
- }
35
- .poster img {
36
- width: 100%;
37
- height: auto;
38
- border-radius: 10px;
39
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.8);
40
- }
41
- .details {
42
- flex: 2;
43
- display: flex;
44
- flex-direction: column;
45
- gap: 20px;
46
- padding: 10px;
47
- }
48
- .details h2 {
49
- margin: 0;
50
- font-size: 36px;
51
- font-weight: bold;
52
- }
53
- .details p {
54
- margin: 5px 0;
55
- font-size: 16px;
56
- line-height: 1.6;
57
- color: #d1d1d1;
58
- }
59
- .genres, .metadata {
60
- display: flex;
61
- flex-wrap: wrap;
62
- gap: 10px;
63
- }
64
- .genres div, .metadata div {
65
- background-color: #333;
66
- padding: 8px 12px;
67
- border-radius: 5px;
68
- font-size: 14px;
69
- }
70
- .metadata div {
71
- background-color: #444;
72
- }
73
- .play-button {
74
- display: inline-block;
75
- padding: 15px 30px;
76
- background: linear-gradient(to right, #ff6f61, #ff3f34);
77
- color: #fff;
78
- text-decoration: none;
79
- border-radius: 50px;
80
- font-size: 18px;
81
- font-weight: bold;
82
- text-align: center;
83
- transition: background 0.3s, transform 0.3s;
84
- }
85
- .play-button:hover {
86
- background: linear-gradient(to right, #ff3f34, #ff6f61);
87
- transform: scale(1.05);
88
- }
89
- .play-button:focus {
90
- outline: none;
91
- box-shadow: 0 0 0 2px rgba(255, 105, 97, 0.5);
92
- }
93
- .error-message {
94
- color: #ff4d4d;
95
- font-size: 18px;
96
- text-align: center;
97
- margin-top: 20px;
98
- }
99
- .metadata-container {
100
- display: flex;
101
- flex-direction: column;
102
- gap: 10px;
103
- }
104
- .metadata-container div {
105
- display: flex;
106
- justify-content: space-between;
107
- padding: 5px 10px;
108
- background-color: #333;
109
- border-radius: 5px;
110
- }
111
- .metadata-container div span {
112
- font-weight: bold;
113
- }
114
- </style>
115
- </head>
116
- <body>
117
- <div class="container">
118
- <div class="poster" id="film-poster">
119
- <img src="https://via.placeholder.com/300x450.png" alt="Poster">
120
- </div>
121
- <div class="details" id="film-details">
122
- <h2 id="film-title">Film Title</h2>
123
- <p id="film-overview">Film overview goes here...</p>
124
- <div class="genres" id="film-genres"></div>
125
- <div class="metadata-container" id="film-metadata"></div>
126
- <a href="#" class="play-button" id="play-button" onclick="playFilm()">Play</a>
127
- </div>
128
- </div>
129
- <script>
130
- async function fetchData(endpoint) {
131
- try {
132
- const response = await fetch(endpoint);
133
- if (!response.ok) throw new Error('Network response was not ok');
134
- return await response.json();
135
- } catch (error) {
136
- console.error('Fetch error:', error);
137
- return null;
138
- }
139
- }
140
-
141
- function createMetadataElement(label, value) {
142
- const div = document.createElement('div');
143
- div.innerHTML = `<span>${label}</span><span>${value}</span>`;
144
- return div;
145
- }
146
-
147
- function createGenreElement(genre) {
148
- const div = document.createElement('div');
149
- div.textContent = genre;
150
- return div;
151
- }
152
-
153
- async function loadFilmDetails(title) {
154
- const data = await fetchData(`/api/film/${encodeURIComponent(title)}`);
155
- if (!data || data.error) {
156
- document.getElementById('film-details').innerHTML = '<p class="error-message">Film not found</p>';
157
- return;
158
- }
159
-
160
- const metadata = data.metadata.data[0];
161
- document.getElementById('film-poster').querySelector('img').src = metadata.image_url || 'https://via.placeholder.com/300x450.png';
162
- document.getElementById('film-title').textContent = metadata.extended_title;
163
- document.getElementById('film-overview').textContent = metadata.overview;
164
-
165
- const genresContainer = document.getElementById('film-genres');
166
- genresContainer.innerHTML = '';
167
- metadata.genres.forEach(genre => genresContainer.appendChild(createGenreElement(genre)));
168
-
169
- const metadataContainer = document.getElementById('film-metadata');
170
- metadataContainer.innerHTML = '';
171
- metadataContainer.appendChild(createMetadataElement('Director', metadata.director));
172
- metadataContainer.appendChild(createMetadataElement('Country', metadata.country));
173
- metadataContainer.appendChild(createMetadataElement('Release Date', metadata.first_air_time));
174
-
175
- const playButton = document.getElementById('play-button');
176
- playButton.href = `#`; // Will be handled by JavaScript
177
- }
178
-
179
- async function playFilm() {
180
- const title = "{{ title }}";
181
- try {
182
- const response = await fetch(`/api/cache_film/${encodeURIComponent(title)}`);
183
- const data = await response.json();
184
- if (data.success) {
185
- window.location.href = `/film_player/${encodeURIComponent(title)}`;
186
- } else {
187
- alert('Error caching film: ' + data.error);
188
- }
189
- } catch (error) {
190
- alert('Error: ' + error.message);
191
- }
192
- }
193
-
194
- const urlParams = new URLSearchParams(window.location.search);
195
- const title = "{{ title }}";
196
- if (title) {
197
- loadFilmDetails(title);
198
- } else {
199
- document.getElementById('film-details').innerHTML = '<p class="error-message">No film title provided</p>';
200
- }
201
- </script>
202
- </body>
203
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/film_player.html DELETED
@@ -1,15 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>{{ title }}</title>
7
- </head>
8
- <body>
9
- <h1>{{ title }}</h1>
10
- <video controls width="100%" height="auto">
11
- <source src="{{ cached_url }}" type="video/mp4">
12
- Your browser does not support the video tag.
13
- </video>
14
- </body>
15
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/films.html DELETED
@@ -1,174 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Films</title>
7
- <style>
8
- body {
9
- font-family: 'Roboto', sans-serif;
10
- background: #0f0f0f;
11
- color: #e0e0e0;
12
- margin: 0;
13
- padding: 0;
14
- overflow-x: hidden;
15
- }
16
- .header {
17
- background: linear-gradient(to right, #ff2c20, #ef8b81);
18
- color: #fff;
19
- padding: 40px 20px;
20
- text-align: center;
21
- border-bottom: 2px solid rgba(255, 255, 255, 0.2);
22
- position: relative;
23
- z-index: 1;
24
- }
25
- .header h1 {
26
- margin: 0;
27
- font-size: 48px;
28
- font-weight: bold;
29
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
30
- }
31
- .content {
32
- padding: 20px;
33
- position: relative;
34
- z-index: 1;
35
- }
36
- .section {
37
- margin-bottom: 60px;
38
- }
39
- .section h2 {
40
- margin-bottom: 20px;
41
- font-size: 36px;
42
- font-weight: bold;
43
- color: #fff;
44
- border-bottom: 2px solid #ff1e1e;
45
- padding-bottom: 10px;
46
- }
47
- .grid {
48
- display: grid;
49
- grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
50
- gap: 20px;
51
- margin-top: 20px;
52
- }
53
- .card {
54
- position: relative;
55
- background: #222;
56
- border-radius: 15px;
57
- overflow: hidden;
58
- text-align: center;
59
- transition: transform 0.3s, box-shadow 0.3s;
60
- cursor: pointer;
61
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.8);
62
- }
63
- .card:hover {
64
- transform: scale(1.05);
65
- box-shadow: 0 8px 16px rgba(0, 0, 0, 0.9);
66
- }
67
- .card img {
68
- width: 100%;
69
- height: 330px;
70
- object-fit: cover;
71
- transition: opacity 0.3s;
72
- }
73
- .card h3 {
74
- margin: 0;
75
- padding: 10px;
76
- font-size: 22px;
77
- font-weight: bold;
78
- background: rgba(0, 0, 0, 0.6);
79
- height: 100%;
80
- }
81
- .card::after {
82
- content: '';
83
- display: block;
84
- position: absolute;
85
- top: 0;
86
- left: 0;
87
- width: 100%;
88
- height: 100%;
89
- background: linear-gradient(to bottom, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.7) 100%);
90
- opacity: 0;
91
- transition: opacity 0.3s;
92
- z-index: 1;
93
- }
94
- .card:hover::after {
95
- opacity: 1;
96
- }
97
- .card p {
98
- margin: 0;
99
- padding: 10px;
100
- font-size: 16px;
101
- color: #ccc;
102
- }
103
- </style>
104
- </head>
105
- <body>
106
- <div class="header">
107
- <h1>All Films</h1>
108
- </div>
109
- <div class="content">
110
- <div class="section" id="films">
111
- <h2>Films</h2>
112
- <div class="grid" id="films-grid"></div>
113
- </div>
114
- </div>
115
- <script>
116
- async function fetchData(endpoint) {
117
- try {
118
- const response = await fetch(endpoint);
119
- if (!response.ok) throw new Error('Network response was not ok');
120
- return await response.json();
121
- } catch (error) {
122
- console.error('Fetch error:', error);
123
- return [];
124
- }
125
- }
126
-
127
- async function fetchMetadata(title) {
128
- try {
129
- const response = await fetch(`/get_metadata?title=${encodeURIComponent(title)}`);
130
- if (response.ok) {
131
- const data = await response.json();
132
- if (data.data && data.data.length > 0) {
133
- const thumbnailUrl = data.data[0].thumbnail;
134
- return thumbnailUrl;
135
- }
136
- }
137
- } catch (error) {
138
- console.error('Metadata fetch error:', error);
139
- }
140
- return null;
141
- }
142
-
143
- function createCard(item) {
144
- const card = document.createElement('div');
145
- card.className = 'card';
146
- const title = item.path.split('/').pop();
147
- card.innerHTML = `
148
- <img src="https://via.placeholder.com/220x330.png" alt="${title}">
149
- <h3>${title}</h3>
150
- `;
151
- // Redirect to the appropriate page on card click
152
- card.onclick = () => {
153
- window.location.href = `/film/${encodeURIComponent(title)}`;
154
- };
155
- fetchMetadata(title).then(thumbnailUrl => {
156
- if (thumbnailUrl !== null) {
157
- card.querySelector('img').src = thumbnailUrl;
158
- }
159
- });
160
- return card;
161
- }
162
-
163
- async function populateGrid(endpoint, gridId) {
164
- const grid = document.getElementById(gridId);
165
- const data = await fetchData(endpoint);
166
- data.forEach(item => {
167
- grid.appendChild(createCard(item));
168
- });
169
- }
170
-
171
- populateGrid('/api/films', 'films-grid');
172
- </script>
173
- </body>
174
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/index.html DELETED
@@ -1,187 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Media Library</title>
7
- <style>
8
- body {
9
- font-family: 'Roboto', sans-serif;
10
- background: #0f0f0f;
11
- color: #e0e0e0;
12
- margin: 0;
13
- padding: 0;
14
- overflow-x: hidden;
15
- }
16
- .header {
17
- background: linear-gradient(to right, #ff2c20, #ef8b81);
18
- color: #fff;
19
- padding: 40px 20px;
20
- text-align: center;
21
- border-bottom: 2px solid rgba(255, 255, 255, 0.2);
22
- position: relative;
23
- z-index: 1;
24
- }
25
- .header h1 {
26
- margin: 0;
27
- font-size: 48px;
28
- font-weight: bold;
29
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
30
- }
31
- .content {
32
- padding: 20px;
33
- position: relative;
34
- z-index: 1;
35
- }
36
- .section {
37
- margin-bottom: 60px;
38
- }
39
- .section h2 {
40
- margin-bottom: 20px;
41
- font-size: 36px;
42
- font-weight: bold;
43
- color: #fff;
44
- border-bottom: 2px solid #ff1e1e;
45
- padding-bottom: 10px;
46
- }
47
- .section h2 a {
48
- color: #f2629f;
49
- text-decoration: none;
50
- font-size: 16px;
51
- }
52
- .grid {
53
- display: grid;
54
- grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
55
- gap: 20px;
56
- margin-top: 20px;
57
- }
58
- .card {
59
- position: relative;
60
- background: #222;
61
- border-radius: 15px;
62
- overflow: hidden;
63
- text-align: center;
64
- transition: transform 0.3s, box-shadow 0.3s;
65
- cursor: pointer;
66
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.8);
67
- }
68
- .card:hover {
69
- transform: scale(1.05);
70
- box-shadow: 0 8px 16px rgba(0, 0, 0, 0.9);
71
- }
72
- .card img {
73
- width: 100%;
74
- height: 330px;
75
- object-fit: cover;
76
- transition: opacity 0.3s;
77
- }
78
- .card h3 {
79
- margin: 0;
80
- padding: 10px;
81
- font-size: 22px;
82
- font-weight: bold;
83
- background: rgba(0, 0, 0, 0.6);
84
- }
85
- .card::after {
86
- content: '';
87
- display: block;
88
- position: absolute;
89
- top: 0;
90
- left: 0;
91
- width: 100%;
92
- height: 100%;
93
- background: linear-gradient(to bottom, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.7) 100%);
94
- opacity: 0;
95
- transition: opacity 0.3s;
96
- z-index: 1;
97
- }
98
- .card:hover::after {
99
- opacity: 1;
100
- }
101
- .card p {
102
- margin: 0;
103
- padding: 10px;
104
- font-size: 16px;
105
- color: #ccc;
106
- }
107
- </style>
108
- </head>
109
- <body>
110
- <div class="header">
111
- <h1>Media Library</h1>
112
- </div>
113
- <div class="content">
114
- <div class="section" id="films">
115
- <h2>Films <a href="/films">View All</a></h2>
116
- <div class="grid" id="films-grid"></div>
117
- </div>
118
- <div class="section" id="tv">
119
- <h2>TV Shows <a href="/tv">View All</a></h2>
120
- <div class="grid" id="tv-grid"></div>
121
- </div>
122
- </div>
123
- <script>
124
- async function fetchData(endpoint) {
125
- try {
126
- const response = await fetch(endpoint);
127
- if (!response.ok) throw new Error('Network response was not ok');
128
- return await response.json();
129
- } catch (error) {
130
- console.error('Fetch error:', error);
131
- return [];
132
- }
133
- }
134
-
135
- async function fetchMetadata(title) {
136
- try {
137
- const response = await fetch(`/get_metadata?title=${encodeURIComponent(title)}`);
138
- if (response.ok) {
139
- const data = await response.json();
140
- if (data.data && data.data.length > 0) {
141
- const thumbnailUrl = data.data[0].thumbnail;
142
- return thumbnailUrl;
143
- }
144
- }
145
- } catch (error) {
146
- console.error('Metadata fetch error:', error);
147
- }
148
- return null;
149
- }
150
-
151
- function createCard(item, mediaType) {
152
- const card = document.createElement('div');
153
- card.className = 'card';
154
- const title = item.path.split('/').pop();
155
- card.innerHTML = `
156
- <img src="https://via.placeholder.com/220x330.png" alt="${title}">
157
- <h3>${title}</h3>
158
- `;
159
- // Redirect to the appropriate page on card click
160
- card.onclick = () => {
161
- if (mediaType === 'movie') {
162
- window.location.href = `/film/${encodeURIComponent(title)}`;
163
- } else if (mediaType === 'series') {
164
- window.location.href = `/tv/${encodeURIComponent(title)}`;
165
- }
166
- };
167
- fetchMetadata(title).then(thumbnailUrl => {
168
- if (thumbnailUrl !== null) {
169
- card.querySelector('img').src = thumbnailUrl;
170
- }
171
- });
172
- return card;
173
- }
174
-
175
- async function populateGrid(endpoint, gridId, mediaType, limit = 5) {
176
- const grid = document.getElementById(gridId);
177
- const data = await fetchData(endpoint);
178
- data.slice(0, limit).forEach(item => {
179
- grid.appendChild(createCard(item, mediaType));
180
- });
181
- }
182
-
183
- populateGrid('/api/films', 'films-grid', 'movie');
184
- populateGrid('/api/tv', 'tv-grid', 'series');
185
- </script>
186
- </body>
187
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/tvshow_details_page.html DELETED
File without changes
templates/tvshow_player.html DELETED
@@ -1,50 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>Media Player</title>
5
- <style>
6
- body {
7
- font-family: Arial, sans-serif;
8
- background-color: #141414;
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>TV Show 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/tvshows.html DELETED
@@ -1,174 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>All TV Shows</title>
7
- <style>
8
- body {
9
- font-family: 'Roboto', sans-serif;
10
- background: #0f0f0f;
11
- color: #e0e0e0;
12
- margin: 0;
13
- padding: 0;
14
- overflow-x: hidden;
15
- }
16
- .header {
17
- background: linear-gradient(to right, #ff2c20, #ef8b81);
18
- color: #fff;
19
- padding: 40px 20px;
20
- text-align: center;
21
- border-bottom: 2px solid rgba(255, 255, 255, 0.2);
22
- position: relative;
23
- z-index: 1;
24
- }
25
- .header h1 {
26
- margin: 0;
27
- font-size: 48px;
28
- font-weight: bold;
29
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
30
- }
31
- .content {
32
- padding: 20px;
33
- position: relative;
34
- z-index: 1;
35
- }
36
- .section {
37
- margin-bottom: 60px;
38
- }
39
- .section h2 {
40
- margin-bottom: 20px;
41
- font-size: 36px;
42
- font-weight: bold;
43
- color: #fff;
44
- border-bottom: 2px solid #ff1e1e;
45
- padding-bottom: 10px;
46
- }
47
- .grid {
48
- display: grid;
49
- grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
50
- gap: 20px;
51
- margin-top: 20px;
52
- }
53
- .card {
54
- position: relative;
55
- background: #222;
56
- border-radius: 15px;
57
- overflow: hidden;
58
- text-align: center;
59
- transition: transform 0.3s, box-shadow 0.3s;
60
- cursor: pointer;
61
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.8);
62
- }
63
- .card:hover {
64
- transform: scale(1.05);
65
- box-shadow: 0 8px 16px rgba(0, 0, 0, 0.9);
66
- }
67
- .card img {
68
- width: 100%;
69
- height: 330px;
70
- object-fit: cover;
71
- transition: opacity 0.3s;
72
- }
73
- .card h3 {
74
- margin: 0;
75
- padding: 10px;
76
- font-size: 22px;
77
- font-weight: bold;
78
- background: rgba(0, 0, 0, 0.6);
79
- height: 100%;
80
- }
81
- .card::after {
82
- content: '';
83
- display: block;
84
- position: absolute;
85
- top: 0;
86
- left: 0;
87
- width: 100%;
88
- height: 100%;
89
- background: linear-gradient(to bottom, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.7) 100%);
90
- opacity: 0;
91
- transition: opacity 0.3s;
92
- z-index: 1;
93
- }
94
- .card:hover::after {
95
- opacity: 1;
96
- }
97
- .card p {
98
- margin: 0;
99
- padding: 10px;
100
- font-size: 16px;
101
- color: #ccc;
102
- }
103
- </style>
104
- </head>
105
- <body>
106
- <div class="header">
107
- <h1>All TV Shows</h1>
108
- </div>
109
- <div class="content">
110
- <div class="section" id="tv">
111
- <h2>TV Shows</h2>
112
- <div class="grid" id="tv-grid"></div>
113
- </div>
114
- </div>
115
- <script>
116
- async function fetchData(endpoint) {
117
- try {
118
- const response = await fetch(endpoint);
119
- if (!response.ok) throw new Error('Network response was not ok');
120
- return await response.json();
121
- } catch (error) {
122
- console.error('Fetch error:', error);
123
- return [];
124
- }
125
- }
126
-
127
- async function fetchMetadata(title) {
128
- try {
129
- const response = await fetch(`/get_metadata?title=${encodeURIComponent(title)}`);
130
- if (response.ok) {
131
- const data = await response.json();
132
- if (data.data && data.data.length > 0) {
133
- const thumbnailUrl = data.data[0].thumbnail;
134
- return thumbnailUrl;
135
- }
136
- }
137
- } catch (error) {
138
- console.error('Metadata fetch error:', error);
139
- }
140
- return null;
141
- }
142
-
143
- function createCard(item) {
144
- const card = document.createElement('div');
145
- card.className = 'card';
146
- const title = item.path.split('/').pop();
147
- card.innerHTML = `
148
- <img src="https://via.placeholder.com/220x330.png" alt="${title}">
149
- <h3>${title}</h3>
150
- `;
151
- // Redirect to the appropriate page on card click
152
- card.onclick = () => {
153
- window.location.href = `/tv/${encodeURIComponent(title)}`;
154
- };
155
- fetchMetadata(title).then(thumbnailUrl => {
156
- if (thumbnailUrl !== null) {
157
- card.querySelector('img').src = thumbnailUrl;
158
- }
159
- });
160
- return card;
161
- }
162
-
163
- async function populateGrid(endpoint, gridId) {
164
- const grid = document.getElementById(gridId);
165
- const data = await fetchData(endpoint);
166
- data.forEach(item => {
167
- grid.appendChild(createCard(item));
168
- });
169
- }
170
-
171
- populateGrid('/api/tv', 'tv-grid');
172
- </script>
173
- </body>
174
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
video.py DELETED
@@ -1,52 +0,0 @@
1
- import os
2
- import logging
3
- import ffmpeg
4
-
5
- # Set up logging
6
- logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
7
-
8
- def ffmpeg_stream(file_url, token, output_dir="tmp/cache/stream", stream_id=None):
9
- # Generate a unique directory for the stream
10
- stream_dir = os.path.join(output_dir, stream_id)
11
-
12
- if not os.path.exists(stream_dir):
13
- os.makedirs(stream_dir)
14
-
15
- # Set the output paths
16
- output_path = os.path.join(stream_dir, 'output.m3u8')
17
- segment_filename = os.path.join(stream_dir, 'segment_%03d.ts')
18
-
19
- # Log the URL and output paths
20
- logging.debug(f"URL: {file_url}")
21
- logging.debug(f"Output Path: {output_path}")
22
- logging.debug(f"Segment Filename: {segment_filename}")
23
-
24
- try:
25
- # Log the command being executed
26
- logging.debug(f"Starting FFmpeg process with URL: {file_url}")
27
-
28
- # Run the FFmpeg command using ffmpeg-python
29
- process = (
30
- ffmpeg
31
- .input(file_url, headers=f"Authorization: Bearer {token}")
32
- .output(output_path,
33
- vcodec='libx264', crf=23, preset='medium',
34
- acodec='aac', audio_bitrate='192k', format='hls',
35
- hls_time=10, hls_list_size=0,
36
- hls_segment_filename=segment_filename)
37
- )
38
-
39
- process.run(capture_stdout=True, capture_stderr=True)
40
-
41
- # Check if the output file was created
42
- if os.path.exists(output_path):
43
- logging.info(f"HLS playlist created at {output_path}")
44
- return output_path
45
- else:
46
- logging.error(f"HLS playlist not created at {output_path}")
47
- return None
48
-
49
- except ffmpeg.Error as e:
50
- error_message = e.stderr.decode('utf8') if e.stderr else str(e)
51
- logging.error(f"Error using FFmpeg to stream file: {error_message}")
52
- return None