ChandimaPrabath commited on
Commit
bd51aea
·
1 Parent(s): e47fc7c

0.0.2 V Beta

Browse files
Files changed (5) hide show
  1. Dockerfile +0 -38
  2. LoadBalancer.py +265 -12
  3. app.py +60 -229
  4. old.LoadBalancer.py +73 -0
  5. old.app.py +418 -0
Dockerfile DELETED
@@ -1,38 +0,0 @@
1
- # Base image
2
- FROM python:3.9
3
-
4
- # Install additional packages and setup
5
- RUN apt-get update && apt-get install -y \
6
- fakeroot \
7
- git \
8
- git-lfs \
9
- ffmpeg \
10
- libsm6 \
11
- libxext6 \
12
- cmake \
13
- rsync \
14
- libgl1-mesa-glx \
15
- && rm -rf /var/lib/apt/lists/* \
16
- && mv /usr/bin/apt-get /usr/bin/.apt-get \
17
- && echo '#!/usr/bin/env sh\nfakeroot /usr/bin/.apt-get $@' > /usr/bin/apt-get \
18
- && chmod +x /usr/bin/apt-get \
19
- && useradd -m -u 1000 user \
20
- && git lfs install
21
-
22
- # Set the working directory
23
- WORKDIR /home/user/app
24
-
25
- # Copy requirements file and install dependencies
26
- COPY requirements.txt /tmp/requirements.txt
27
- RUN pip install --no-cache-dir -r /tmp/requirements.txt
28
-
29
- # Copy the application code
30
- COPY --chown=1000:1000 ./ /home/user/app
31
-
32
- USER user
33
-
34
- # Expose the port the app runs on
35
- EXPOSE 7860
36
-
37
- # Set the entry point to run the Flask application
38
- ENTRYPOINT ["python", "app.py"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
LoadBalancer.py CHANGED
@@ -1,17 +1,62 @@
 
 
 
 
 
 
 
 
 
1
  import time
2
  import logging
3
  from threading import Thread, Event, Timer
4
  from api import InstancesAPI
5
 
 
 
 
 
 
6
  class LoadBalancer:
7
- def __init__(self, polling_interval=10, max_retries=3, initial_delay=1):
8
- self.version = "0.0.1 V Beta"
9
  self.instances = []
10
  self.polling_interval = polling_interval
11
  self.max_retries = max_retries
12
  self.initial_delay = initial_delay
13
  self.stop_event = Event()
14
  self.instances_api = InstancesAPI(self.instances)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  def register_instance(self, instance_url):
17
  if instance_url not in self.instances:
@@ -57,17 +102,225 @@ class LoadBalancer:
57
  logging.info("Stopping polling.")
58
  self.stop_event.set()
59
 
60
- if __name__ == "__main__":
61
- logging.basicConfig(level=logging.INFO)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
- load_balancer = LoadBalancer()
 
64
 
65
- # Example registration (in real use, handle this via an API endpoint)
66
- load_balancer.register_instance("http://localhost:5000")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
- # Start polling in a separate thread
69
- polling_thread = Thread(target=load_balancer.start_polling)
70
- polling_thread.start()
 
 
 
 
 
 
71
 
72
- # Stop polling after some time for demonstration purposes
73
- Timer(300, load_balancer.stop_polling).start() # Stop after 5 minutes
 
 
1
+ import os
2
+ import requests
3
+ import json
4
+ import urllib.request
5
+ from tqdm import tqdm
6
+ from indexer import indexer
7
+ import re
8
+ from tvdb import fetch_and_cache_json
9
+ from threading import Event
10
  import time
11
  import logging
12
  from threading import Thread, Event, Timer
13
  from api import InstancesAPI
14
 
15
+
16
+ CACHE_DIR = os.getenv("CACHE_DIR")
17
+
18
+ download_progress = {}
19
+
20
  class LoadBalancer:
21
+ def __init__(self, cache_dir, index_file, token, repo, polling_interval=10, max_retries=3, initial_delay=1):
22
+ self.version = "0.0.2 V Beta"
23
  self.instances = []
24
  self.polling_interval = polling_interval
25
  self.max_retries = max_retries
26
  self.initial_delay = initial_delay
27
  self.stop_event = Event()
28
  self.instances_api = InstancesAPI(self.instances)
29
+ self.CACHE_DIR = cache_dir
30
+ self.INDEX_FILE = index_file
31
+ self.TOKEN = token
32
+ self.REPO = repo
33
+ self.FILM_STORE_JSON_PATH = os.path.join(cache_dir, "film_store.json")
34
+ self.TV_STORE_JSON_PATH = os.path.join(cache_dir, "tv_store.json")
35
+ self.file_structure = None
36
+
37
+ # Ensure CACHE_DIR exists
38
+ if not os.path.exists(self.CACHE_DIR):
39
+ os.makedirs(self.CACHE_DIR)
40
+
41
+ for path in [self.FILM_STORE_JSON_PATH, self.TV_STORE_JSON_PATH]:
42
+ if not os.path.exists(path):
43
+ with open(path, 'w') as json_file:
44
+ json.dump({}, json_file)
45
+
46
+ # Index the file structure
47
+ indexer()
48
+
49
+ # Load the file structure JSON
50
+ if not os.path.exists(self.INDEX_FILE):
51
+ raise FileNotFoundError(f"{self.INDEX_FILE} not found. Please make sure the file exists.")
52
+
53
+ with open(self.INDEX_FILE, 'r') as f:
54
+ self.file_structure = json.load(f)
55
+
56
+ prefetch_thread = Thread(target=self.start_prefetching)
57
+ prefetch_thread.daemon = True
58
+ prefetch_thread.start()
59
+
60
 
61
  def register_instance(self, instance_url):
62
  if instance_url not in self.instances:
 
102
  logging.info("Stopping polling.")
103
  self.stop_event.set()
104
 
105
+ ######################################################################
106
+
107
+ @staticmethod
108
+ def read_json(file_path):
109
+ if os.path.exists(file_path):
110
+ with open(file_path, 'r') as json_file:
111
+ return json.load(json_file)
112
+ return {}
113
+
114
+ @staticmethod
115
+ def get_system_proxies():
116
+ """
117
+ Retrieves the system's HTTP and HTTPS proxies.
118
+
119
+ Returns:
120
+ dict: A dictionary containing the proxies.
121
+ """
122
+ try:
123
+ proxies = urllib.request.getproxies()
124
+ print("System proxies:", proxies)
125
+ return {
126
+ "http": proxies.get("http"),
127
+ "https": proxies.get("http")
128
+ }
129
+ except Exception as e:
130
+ print(f"Error getting system proxies: {e}")
131
+ return {}
132
+
133
+ @staticmethod
134
+ def download_film(title):
135
+ """
136
+ Downloads a film to a specific instance
137
+
138
+ Args:
139
+ title (str): The title of the film.
140
+ """
141
+ pass
142
+
143
+ @staticmethod
144
+ def update_film_store_json(title, cache_path):
145
+ """
146
+ Updates the film store JSON with the new file.
147
+
148
+ Args:
149
+ title (str): The title of the film.
150
+ cache_path (str): The local path where the file is saved.
151
+ """
152
+ FILM_STORE_JSON_PATH = os.path.join(CACHE_DIR, "film_store.json")
153
+
154
+ film_store_data = {}
155
+ if os.path.exists(FILM_STORE_JSON_PATH):
156
+ with open(FILM_STORE_JSON_PATH, 'r') as json_file:
157
+ film_store_data = json.load(json_file)
158
+
159
+ film_store_data[title] = cache_path
160
+
161
+ with open(FILM_STORE_JSON_PATH, 'w') as json_file:
162
+ json.dump(film_store_data, json_file, indent=2)
163
+ print(f'Film store updated with {title}.')
164
+
165
+ @staticmethod
166
+ def download_episode(title, season, episode):
167
+ """
168
+ Downloads a episode to a specific instance
169
+ """
170
+
171
+ @staticmethod
172
+ def update_tv_store_json(title, cache_path):
173
+ """
174
+ Updates the TV store JSON with the new file, organizing by title, season, and episode.
175
+
176
+ Args:
177
+ title (str): The title of the TV show.
178
+ cache_path (str): The local path where the file is saved.
179
+ """
180
+ TV_STORE_JSON_PATH = os.path.join(CACHE_DIR, "tv_store.json")
181
+
182
+ tv_store_data = {}
183
+ if os.path.exists(TV_STORE_JSON_PATH):
184
+ with open(TV_STORE_JSON_PATH, 'r') as json_file:
185
+ tv_store_data = json.load(json_file)
186
+
187
+ # Extract season and episode information from the cache_path
188
+ season_part = os.path.basename(os.path.dirname(cache_path)) # Extracts 'Season 1'
189
+ episode_part = os.path.basename(cache_path) # Extracts 'Grand Blue Dreaming - S01E01 - Deep Blue HDTV-720p.mp4'
190
+
191
+ # Create the structure if not already present
192
+ if title not in tv_store_data:
193
+ tv_store_data[title] = {}
194
+
195
+ if season_part not in tv_store_data[title]:
196
+ tv_store_data[title][season_part] = {}
197
+
198
+ # Assuming episode_part is unique for each episode within a season
199
+ tv_store_data[title][season_part][episode_part] = cache_path
200
+
201
+ with open(TV_STORE_JSON_PATH, 'w') as json_file:
202
+ json.dump(tv_store_data, json_file, indent=2)
203
+
204
+ print(f'TV store updated with {title}, {season_part}, {episode_part}.')
205
+
206
+ @staticmethod
207
+ def is_valid_url(url):
208
+ # Simple URL validation (could be more complex if needed)
209
+ regex = re.compile(
210
+ r'^(?:http|ftp)s?://' # http:// or https://
211
+ r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
212
+ r'localhost|' # localhost...
213
+ r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
214
+ r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
215
+ r'(?::\d+)?' # optional port
216
+ r'(?:/?|[/?]\S+)$', re.IGNORECASE)
217
+ return re.match(regex, url) is not None
218
+
219
+
220
+ def load_json(self, file_path):
221
+ """Load JSON data from a file."""
222
+ with open(file_path, 'r') as file:
223
+ return json.load(file)
224
+
225
+ def find_movie_path(self, title):
226
+ """Find the path of the movie in the JSON data based on the title."""
227
+ for directory in self.file_structure:
228
+ if directory['type'] == 'directory' and directory['path'] == 'films':
229
+ for sub_directory in directory['contents']:
230
+ if sub_directory['type'] == 'directory':
231
+ for item in sub_directory['contents']:
232
+ if item['type'] == 'file' and title.lower() in item['path'].lower():
233
+ return item['path']
234
+ return None
235
+
236
+ def find_tv_path(self, title):
237
+ """Find the path of the TV show in the JSON data based on the title."""
238
+ for directory in self.file_structure:
239
+ if directory['type'] == 'directory' and directory['path'] == 'tv':
240
+ for sub_directory in directory['contents']:
241
+ if sub_directory['type'] == 'directory' and title.lower() in sub_directory['path'].lower():
242
+ return sub_directory['path']
243
+ return None
244
+
245
+ def get_tv_structure(self, title):
246
+ """Find the path of the TV show in the JSON data based on the title."""
247
+ for directory in self.file_structure:
248
+ if directory['type'] == 'directory' and directory['path'] == 'tv':
249
+ for sub_directory in directory['contents']:
250
+ if sub_directory['type'] == 'directory' and title.lower() in sub_directory['path'].lower():
251
+ return sub_directory
252
+ return None
253
+
254
+ def get_film_id(self, title):
255
+ """Generate a film ID based on the title."""
256
+ return title.replace(" ", "_").lower()
257
+
258
+ def prefetch_metadata(self):
259
+ """Prefetch metadata for all items in the file structure."""
260
+ for item in self.file_structure:
261
+ if 'contents' in item:
262
+ for sub_item in item['contents']:
263
+ original_title = sub_item['path'].split('/')[-1]
264
+ media_type = 'series' if item['path'].startswith('tv') else 'movie'
265
+ title = original_title
266
+ year = None
267
+
268
+ # Extract year from the title if available
269
+ match = re.search(r'\((\d{4})\)', original_title)
270
+ if match:
271
+ year_str = match.group(1)
272
+ if year_str.isdigit() and len(year_str) == 4:
273
+ title = original_title[:match.start()].strip()
274
+ year = int(year_str)
275
+ else:
276
+ parts = original_title.rsplit(' ', 1)
277
+ if len(parts) > 1 and parts[-1].isdigit() and len(parts[-1]) == 4:
278
+ title = parts[0].strip()
279
+ year = int(parts[-1])
280
+
281
+ fetch_and_cache_json(original_title, title, media_type, year)
282
+
283
+ def bytes_to_human_readable(self, num, suffix="B"):
284
+ for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
285
+ if abs(num) < 1024.0:
286
+ return f"{num:3.1f} {unit}{suffix}"
287
+ num /= 1024.0
288
+ return f"{num:.1f} Y{suffix}"
289
 
290
+ def encode_episodeid(self, title, season, episode):
291
+ return f"{title}_{season}_{episode}"
292
 
293
+ def get_all_tv_shows(self):
294
+ """Get all TV shows from the indexed cache structure JSON file."""
295
+ tv_shows = {}
296
+ for directory in self.file_structure:
297
+ if directory['type'] == 'directory' and directory['path'] == 'tv':
298
+ for sub_directory in directory['contents']:
299
+ if sub_directory['type'] == 'directory':
300
+ show_title = sub_directory['path'].split('/')[-1]
301
+ tv_shows[show_title] = []
302
+ for season_directory in sub_directory['contents']:
303
+ if season_directory['type'] == 'directory':
304
+ season = season_directory['path'].split('/')[-1]
305
+ for episode in season_directory['contents']:
306
+ if episode['type'] == 'file':
307
+ tv_shows[show_title].append({
308
+ "season": season,
309
+ "episode": episode['path'].split('/')[-1],
310
+ "path": episode['path']
311
+ })
312
+ return tv_shows
313
 
314
+ def get_all_films(self):
315
+ """Get all films from the indexed cache structure JSON file."""
316
+ films = []
317
+ for directory in self.file_structure:
318
+ if directory['type'] == 'directory' and directory['path'] == 'films':
319
+ for sub_directory in directory['contents']:
320
+ if sub_directory['type'] == 'directory':
321
+ films.append(sub_directory['path'])
322
+ return films
323
 
324
+ def start_prefetching(self):
325
+ """Start the metadata prefetching in a separate thread."""
326
+ self.prefetch_metadata()
app.py CHANGED
@@ -2,191 +2,33 @@ from flask import Flask, jsonify, request, send_from_directory
2
  from flask_cors import CORS
3
  import os
4
  import json
5
- import threading
6
  import urllib.parse
7
- from hf_scrapper import download_film, download_episode, get_system_proxies, get_download_progress
8
- from indexer import indexer
9
- from tvdb import fetch_and_cache_json
10
- import re
11
- import logging
12
  from LoadBalancer import LoadBalancer
 
13
 
14
- logging.basicConfig(level=logging.INFO)
15
- load_balancer = LoadBalancer()
16
-
17
- # Start polling in a separate thread
18
- polling_thread = threading.Thread(target=load_balancer.start_polling)
19
- polling_thread.start()
20
 
21
  app = Flask(__name__)
22
  CORS(app)
23
 
 
24
  # Constants and Configuration
25
  CACHE_DIR = os.getenv("CACHE_DIR")
26
  INDEX_FILE = os.getenv("INDEX_FILE")
27
  TOKEN = os.getenv("TOKEN")
28
- FILM_STORE_JSON_PATH = os.path.join(CACHE_DIR, "film_store.json")
29
- TV_STORE_JSON_PATH = os.path.join(CACHE_DIR, "tv_store.json")
30
- INSTANCE_REGISTER_JSON_PATH = os.path.join(CACHE_DIR, "instance_register.json")
31
  REPO = os.getenv("REPO")
32
- download_threads = {}
33
-
34
- # Ensure CACHE_DIR exists
35
- if not os.path.exists(CACHE_DIR):
36
- os.makedirs(CACHE_DIR)
37
-
38
- for path in [FILM_STORE_JSON_PATH, TV_STORE_JSON_PATH]:
39
- if not os.path.exists(path):
40
- with open(path, 'w') as json_file:
41
- json.dump({}, json_file)
42
-
43
- # Index the file structure
44
- indexer()
45
-
46
- # Load the file structure JSON
47
- if not os.path.exists(INDEX_FILE):
48
- raise FileNotFoundError(f"{INDEX_FILE} not found. Please make sure the file exists.")
49
 
50
- with open(INDEX_FILE, 'r') as f:
51
- file_structure = json.load(f)
52
-
53
- # Function Definitions
54
- def load_json(file_path):
55
- """Load JSON data from a file."""
56
- with open(file_path, 'r') as file:
57
- return json.load(file)
58
-
59
- def find_movie_path(json_data, title):
60
- """Find the path of the movie in the JSON data based on the title."""
61
- for directory in json_data:
62
- if directory['type'] == 'directory' and directory['path'] == 'films':
63
- for sub_directory in directory['contents']:
64
- if sub_directory['type'] == 'directory':
65
- for item in sub_directory['contents']:
66
- if item['type'] == 'file' and title.lower() in item['path'].lower():
67
- return item['path']
68
- return None
69
-
70
- def find_tv_path(json_data, title):
71
- """Find the path of the TV show in the JSON data based on the title."""
72
- for directory in json_data:
73
- if directory['type'] == 'directory' and directory['path'] == 'tv':
74
- for sub_directory in directory['contents']:
75
- if sub_directory['type'] == 'directory' and title.lower() in sub_directory['path'].lower():
76
- return sub_directory['path']
77
- return None
78
-
79
- def get_tv_structure(json_data,title):
80
- """Find the path of the TV show in the JSON data based on the title."""
81
- for directory in json_data:
82
- if directory['type'] == 'directory' and directory['path'] == 'tv':
83
- for sub_directory in directory['contents']:
84
- if sub_directory['type'] == 'directory' and title.lower() in sub_directory['path'].lower():
85
- return sub_directory
86
- return None
87
-
88
- def get_film_id(title):
89
- """Generate a film ID based on the title."""
90
- return title.replace(" ", "_").lower()
91
-
92
- def prefetch_metadata():
93
- """Prefetch metadata for all items in the file structure."""
94
- for item in file_structure:
95
- if 'contents' in item:
96
- for sub_item in item['contents']:
97
- original_title = sub_item['path'].split('/')[-1]
98
- media_type = 'series' if item['path'].startswith('tv') else 'movie'
99
- title = original_title
100
- year = None
101
-
102
- # Extract year from the title if available
103
- match = re.search(r'\((\d{4})\)', original_title)
104
- if match:
105
- year_str = match.group(1)
106
- if year_str.isdigit() and len(year_str) == 4:
107
- title = original_title[:match.start()].strip()
108
- year = int(year_str)
109
- else:
110
- parts = original_title.rsplit(' ', 1)
111
- if len(parts) > 1 and parts[-1].isdigit() and len(parts[-1]) == 4:
112
- title = parts[0].strip()
113
- year = int(parts[-1])
114
-
115
- fetch_and_cache_json(original_title, title, media_type, year)
116
-
117
- def bytes_to_human_readable(num, suffix="B"):
118
- for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
119
- if abs(num) < 1024.0:
120
- return f"{num:3.1f} {unit}{suffix}"
121
- num /= 1024.0
122
- return f"{num:.1f} Y{suffix}"
123
-
124
- def encode_episodeid(title,season,episode):
125
- return f"{title}_{season}_{episode}"
126
-
127
- def is_valid_url(url):
128
- # Simple URL validation (could be more complex if needed)
129
- regex = re.compile(
130
- r'^(?:http|ftp)s?://' # http:// or https://
131
- r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
132
- r'localhost|' # localhost...
133
- r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
134
- r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
135
- r'(?::\d+)?' # optional port
136
- r'(?:/?|[/?]\S+)$', re.IGNORECASE)
137
- return re.match(regex, url) is not None
138
-
139
- def get_all_tv_shows(indexed_cache):
140
- """Get all TV shows from the indexed cache structure JSON file."""
141
- tv_shows = {}
142
- for directory in indexed_cache:
143
- if directory['type'] == 'directory' and directory['path'] == 'tv':
144
- for sub_directory in directory['contents']:
145
- if sub_directory['type'] == 'directory':
146
- show_title = sub_directory['path'].split('/')[-1]
147
- tv_shows[show_title] = []
148
- for season_directory in sub_directory['contents']:
149
- if season_directory['type'] == 'directory':
150
- season = season_directory['path'].split('/')[-1]
151
- for episode in season_directory['contents']:
152
- if episode['type'] == 'file':
153
- tv_shows[show_title].append({
154
- "season": season,
155
- "episode": episode['path'].split('/')[-1],
156
- "path": episode['path']
157
- })
158
- return tv_shows
159
-
160
- def get_all_films(indexed_cache):
161
- """Get all films from the indexed cache structure JSON file."""
162
- films = []
163
- for directory in indexed_cache:
164
- if directory['type'] == 'directory' and directory['path'] == 'films':
165
- for sub_directory in directory['contents']:
166
- if sub_directory['type'] == 'directory':
167
- films.append(sub_directory['path'])
168
- return films
169
-
170
- def start_prefetching():
171
- """Start the metadata prefetching in a separate thread."""
172
- prefetch_metadata()
173
-
174
- # Start prefetching metadata
175
- thread = threading.Thread(target=start_prefetching)
176
- thread.daemon = True
177
- thread.start()
178
 
179
  # API Endpoints
180
-
181
- @app.route('/api/film', methods=['GET'])
182
- def get_movie_api():
183
  """Endpoint to get the movie by title."""
184
- title = request.args.get('title')
185
  if not title:
186
  return jsonify({"error": "Title parameter is required"}), 400
187
 
188
  # Load the film store JSON
189
- with open(FILM_STORE_JSON_PATH, 'r') as json_file:
190
  film_store_data = json.load(json_file)
191
 
192
  # Check if the film is already cached
@@ -195,36 +37,32 @@ def get_movie_api():
195
  if os.path.exists(cache_path):
196
  return send_from_directory(os.path.dirname(cache_path), os.path.basename(cache_path))
197
 
198
- movie_path = find_movie_path(file_structure, title)
199
 
200
  if not movie_path:
201
  return jsonify({"error": "Movie not found"}), 404
202
 
203
  cache_path = os.path.join(CACHE_DIR, movie_path)
204
  file_url = f"https://huggingface.co/{REPO}/resolve/main/{movie_path}"
205
- proxies = get_system_proxies()
206
- film_id = get_film_id(title)
207
 
208
  # Start the download in a separate thread if not already downloading
209
- if film_id not in download_threads or not download_threads[film_id].is_alive():
210
- thread = threading.Thread(target=download_film, args=(file_url, TOKEN, cache_path, proxies, film_id, title))
211
- download_threads[film_id] = thread
212
  thread.start()
213
 
214
  return jsonify({"status": "Download started", "film_id": film_id})
215
 
216
- @app.route('/api/tv', methods=['GET'])
217
- def get_tv_show_api():
218
  """Endpoint to get the TV show by title, season, and episode."""
219
- title = request.args.get('title')
220
- season = request.args.get('season')
221
- episode = request.args.get('episode')
222
-
223
  if not title or not season or not episode:
224
  return jsonify({"error": "Title, season, and episode parameters are required"}), 400
225
 
226
  # Load the TV store JSON
227
- with open(TV_STORE_JSON_PATH, 'r') as json_file:
228
  tv_store_data = json.load(json_file)
229
 
230
  # Check if the episode is already cached
@@ -232,16 +70,17 @@ def get_tv_show_api():
232
  for ep in tv_store_data[title][season]:
233
  if episode in ep:
234
  cache_path = tv_store_data[title][season][ep]
 
235
  if os.path.exists(cache_path):
236
  return send_from_directory(os.path.dirname(cache_path), os.path.basename(cache_path))
237
 
238
- tv_path = find_tv_path(file_structure, title)
239
 
240
  if not tv_path:
241
  return jsonify({"error": "TV show not found"}), 404
242
 
243
  episode_path = None
244
- for directory in file_structure:
245
  if directory['type'] == 'directory' and directory['path'] == 'tv':
246
  for sub_directory in directory['contents']:
247
  if sub_directory['type'] == 'directory' and title.lower() in sub_directory['path'].lower():
@@ -257,24 +96,39 @@ def get_tv_show_api():
257
 
258
  cache_path = os.path.join(CACHE_DIR, episode_path)
259
  file_url = f"https://huggingface.co/{REPO}/resolve/main/{episode_path}"
260
- proxies = get_system_proxies()
261
- episode_id = encode_episodeid(title,season,episode)
262
 
263
  # Start the download in a separate thread if not already downloading
264
- if episode_id not in download_threads or not download_threads[episode_id].is_alive():
265
- thread = threading.Thread(target=download_episode, args=(file_url, TOKEN, cache_path, proxies, episode_id, title))
266
- download_threads[episode_id] = thread
267
  thread.start()
268
 
269
  return jsonify({"status": "Download started", "episode_id": episode_id})
270
 
271
-
272
  @app.route('/api/progress/<id>', methods=['GET'])
273
  def get_progress_api(id):
274
  """Endpoint to get the download progress of a movie or TV show episode."""
275
- progress = get_download_progress(id)
276
  return jsonify({"id": id, "progress": progress})
277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  @app.route('/api/cache/size', methods=['GET'])
279
  def get_cache_size_api():
280
  total_size = 0
@@ -282,7 +136,7 @@ def get_cache_size_api():
282
  for f in filenames:
283
  fp = os.path.join(dirpath, f)
284
  total_size += os.path.getsize(fp)
285
- readable_size = bytes_to_human_readable(total_size)
286
  return jsonify({"cache_size": readable_size})
287
 
288
  @app.route('/api/cache/clear', methods=['POST'])
@@ -296,8 +150,8 @@ def clear_cache_api():
296
  @app.route('/api/tv/store', methods=['GET'])
297
  def get_tv_store_api():
298
  """Endpoint to get the TV store JSON."""
299
- if os.path.exists(TV_STORE_JSON_PATH):
300
- with open(TV_STORE_JSON_PATH, 'r') as json_file:
301
  tv_store_data = json.load(json_file)
302
  return jsonify(tv_store_data)
303
  return jsonify({}), 404
@@ -305,39 +159,15 @@ def get_tv_store_api():
305
  @app.route('/api/film/store', methods=['GET'])
306
  def get_film_store_api():
307
  """Endpoint to get the film store JSON."""
308
- if os.path.exists(FILM_STORE_JSON_PATH):
309
- with open(FILM_STORE_JSON_PATH, 'r') as json_file:
310
  tv_store_data = json.load(json_file)
311
  return jsonify(tv_store_data)
312
  return jsonify({}), 404
313
 
314
- #################################################
315
- # No change needed
316
-
317
- @app.route('/api/filmid', methods=['GET'])
318
- def get_film_id_by_title_api():
319
- """Endpoint to get the film ID by providing the movie title."""
320
- title = request.args.get('title')
321
- if not title:
322
- return jsonify({"error": "Title parameter is required"}), 400
323
- film_id = get_film_id(title)
324
- return jsonify({"film_id": film_id})
325
-
326
- @app.route('/api/episodeid', methods=['GET'])
327
- def get_episode_id_api():
328
- """Endpoint to get the episode ID by providing the TV show title, season, and episode."""
329
- title = request.args.get('title')
330
- season = request.args.get('season')
331
- episode = request.args.get('episode')
332
- if not title or not season or not episode:
333
- return jsonify({"error": "Title, season, and episode parameters are required"}), 400
334
- episode_id = encode_episodeid(title,season,episode)
335
- return jsonify({"episode_id": episode_id})
336
-
337
- @app.route('/api/film/metadata', methods=['GET'])
338
- def get_film_metadata_api():
339
  """Endpoint to get the film metadata by title."""
340
- title = request.args.get('title')
341
  if not title:
342
  return jsonify({'error': 'No title provided'}), 400
343
 
@@ -350,10 +180,9 @@ def get_film_metadata_api():
350
 
351
  return jsonify({'error': 'Metadata not found'}), 404
352
 
353
- @app.route('/api/tv/metadata', methods=['GET'])
354
- def get_tv_metadata_api():
355
  """Endpoint to get the TV show metadata by title."""
356
- title = request.args.get('title')
357
  if not title:
358
  return jsonify({'error': 'No title provided'}), 400
359
 
@@ -364,7 +193,7 @@ def get_tv_metadata_api():
364
  data = json.load(f)
365
 
366
  # Add the file structure to the metadata
367
- tv_structure_data = get_tv_structure(file_structure, title)
368
  if tv_structure_data:
369
  data['file_structure'] = tv_structure_data
370
 
@@ -375,11 +204,12 @@ def get_tv_metadata_api():
375
 
376
  @app.route("/api/film/all")
377
  def get_all_films_api():
378
- return get_all_films(file_structure)
379
 
380
  @app.route("/api/tv/all")
381
  def get_all_tvshows_api():
382
- return get_all_tv_shows(file_structure)
 
383
 
384
  #############################################################
385
  # unique api's
@@ -391,7 +221,7 @@ def register_instance():
391
  return jsonify({"error": "No URL provided"}), 400
392
 
393
  url = data["url"]
394
- if not is_valid_url(url):
395
  return jsonify({"error": "Invalid URL"}), 400
396
 
397
  # Register the instance
@@ -403,16 +233,17 @@ def register_instance():
403
  except Exception as e:
404
  logging.error(f"Error registering instance: {e}")
405
  return jsonify({"error": "Failed to register instance"}), 500
 
 
 
 
 
406
  #############################################################
407
  # Routes
408
  @app.route('/')
409
  def index():
410
  return f"Load Balancer is Running {load_balancer.version}"
411
 
412
- @app.route('/api/instances',methods=["GET"])
413
- def get_instances():
414
- return load_balancer.instances
415
-
416
  # Main entry point
417
  if __name__ == "__main__":
418
  app.run(debug=True, host="0.0.0.0", port=7860)
 
2
  from flask_cors import CORS
3
  import os
4
  import json
5
+ from threading import Thread
6
  import urllib.parse
 
 
 
 
 
7
  from LoadBalancer import LoadBalancer
8
+ import logging
9
 
 
 
 
 
 
 
10
 
11
  app = Flask(__name__)
12
  CORS(app)
13
 
14
+ logging.basicConfig(level=logging.INFO)
15
  # Constants and Configuration
16
  CACHE_DIR = os.getenv("CACHE_DIR")
17
  INDEX_FILE = os.getenv("INDEX_FILE")
18
  TOKEN = os.getenv("TOKEN")
 
 
 
19
  REPO = os.getenv("REPO")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
+ load_balancer = LoadBalancer(cache_dir=CACHE_DIR, index_file=INDEX_FILE, token=TOKEN, repo=REPO)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  # API Endpoints
24
+ @app.route('/api/film/<title>', methods=['GET'])
25
+ def get_movie_api(title):
 
26
  """Endpoint to get the movie by title."""
 
27
  if not title:
28
  return jsonify({"error": "Title parameter is required"}), 400
29
 
30
  # Load the film store JSON
31
+ with open(load_balancer.FILM_STORE_JSON_PATH, 'r') as json_file:
32
  film_store_data = json.load(json_file)
33
 
34
  # Check if the film is already cached
 
37
  if os.path.exists(cache_path):
38
  return send_from_directory(os.path.dirname(cache_path), os.path.basename(cache_path))
39
 
40
+ movie_path = load_balancer.find_movie_path(title)
41
 
42
  if not movie_path:
43
  return jsonify({"error": "Movie not found"}), 404
44
 
45
  cache_path = os.path.join(CACHE_DIR, movie_path)
46
  file_url = f"https://huggingface.co/{REPO}/resolve/main/{movie_path}"
47
+ proxies = load_balancer.get_system_proxies()
48
+ film_id = load_balancer.get_film_id(title)
49
 
50
  # Start the download in a separate thread if not already downloading
51
+ if film_id not in load_balancer.download_threads or not load_balancer.download_threads[film_id].is_alive():
52
+ thread = Thread(target=load_balancer.download_film, args=(file_url, TOKEN, cache_path, proxies, film_id, title))
53
+ load_balancer.download_threads[film_id] = thread
54
  thread.start()
55
 
56
  return jsonify({"status": "Download started", "film_id": film_id})
57
 
58
+ @app.route('/api/tv/<title>/<season>/<episode>', methods=['GET'])
59
+ def get_tv_show_api(title, season, episode):
60
  """Endpoint to get the TV show by title, season, and episode."""
 
 
 
 
61
  if not title or not season or not episode:
62
  return jsonify({"error": "Title, season, and episode parameters are required"}), 400
63
 
64
  # Load the TV store JSON
65
+ with open(load_balancer.TV_STORE_JSON_PATH, 'r') as json_file:
66
  tv_store_data = json.load(json_file)
67
 
68
  # Check if the episode is already cached
 
70
  for ep in tv_store_data[title][season]:
71
  if episode in ep:
72
  cache_path = tv_store_data[title][season][ep]
73
+ print(cache_path)
74
  if os.path.exists(cache_path):
75
  return send_from_directory(os.path.dirname(cache_path), os.path.basename(cache_path))
76
 
77
+ tv_path = load_balancer.find_tv_path(title)
78
 
79
  if not tv_path:
80
  return jsonify({"error": "TV show not found"}), 404
81
 
82
  episode_path = None
83
+ for directory in load_balancer.file_structure:
84
  if directory['type'] == 'directory' and directory['path'] == 'tv':
85
  for sub_directory in directory['contents']:
86
  if sub_directory['type'] == 'directory' and title.lower() in sub_directory['path'].lower():
 
96
 
97
  cache_path = os.path.join(CACHE_DIR, episode_path)
98
  file_url = f"https://huggingface.co/{REPO}/resolve/main/{episode_path}"
99
+ proxies = load_balancer.get_system_proxies()
100
+ episode_id = load_balancer.encode_episodeid(title, season, episode)
101
 
102
  # Start the download in a separate thread if not already downloading
103
+ if episode_id not in load_balancer.download_threads or not load_balancer.download_threads[episode_id].is_alive():
104
+ thread = Thread(target=load_balancer.download_episode, args=(file_url, TOKEN, cache_path, proxies, episode_id, title))
105
+ load_balancer.download_threads[episode_id] = thread
106
  thread.start()
107
 
108
  return jsonify({"status": "Download started", "episode_id": episode_id})
109
 
 
110
  @app.route('/api/progress/<id>', methods=['GET'])
111
  def get_progress_api(id):
112
  """Endpoint to get the download progress of a movie or TV show episode."""
113
+ progress = load_balancer.get_download_progress(id)
114
  return jsonify({"id": id, "progress": progress})
115
 
116
+ @app.route('/api/filmid/<title>', methods=['GET'])
117
+ def get_film_id_by_title_api(title):
118
+ """Endpoint to get the film ID by providing the movie title."""
119
+ if not title:
120
+ return jsonify({"error": "Title parameter is required"}), 400
121
+ film_id = load_balancer.get_film_id(title)
122
+ return jsonify({"film_id": film_id})
123
+
124
+ @app.route('/api/episodeid/<title>/<season>/<episode>', methods=['GET'])
125
+ def get_episode_id_api(title,season,episode):
126
+ """Endpoint to get the episode ID by providing the TV show title, season, and episode."""
127
+ if not title or not season or not episode:
128
+ return jsonify({"error": "Title, season, and episode parameters are required"}), 400
129
+ episode_id = load_balancer.encode_episodeid(title,season,episode)
130
+ return jsonify({"episode_id": episode_id})
131
+
132
  @app.route('/api/cache/size', methods=['GET'])
133
  def get_cache_size_api():
134
  total_size = 0
 
136
  for f in filenames:
137
  fp = os.path.join(dirpath, f)
138
  total_size += os.path.getsize(fp)
139
+ readable_size = load_balancer.bytes_to_human_readable(total_size)
140
  return jsonify({"cache_size": readable_size})
141
 
142
  @app.route('/api/cache/clear', methods=['POST'])
 
150
  @app.route('/api/tv/store', methods=['GET'])
151
  def get_tv_store_api():
152
  """Endpoint to get the TV store JSON."""
153
+ if os.path.exists(load_balancer.TV_STORE_JSON_PATH):
154
+ with open(load_balancer.TV_STORE_JSON_PATH, 'r') as json_file:
155
  tv_store_data = json.load(json_file)
156
  return jsonify(tv_store_data)
157
  return jsonify({}), 404
 
159
  @app.route('/api/film/store', methods=['GET'])
160
  def get_film_store_api():
161
  """Endpoint to get the film store JSON."""
162
+ if os.path.exists(load_balancer.FILM_STORE_JSON_PATH):
163
+ with open(load_balancer.FILM_STORE_JSON_PATH, 'r') as json_file:
164
  tv_store_data = json.load(json_file)
165
  return jsonify(tv_store_data)
166
  return jsonify({}), 404
167
 
168
+ @app.route('/api/film/metadata/<title>', methods=['GET'])
169
+ def get_film_metadata_api(title):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  """Endpoint to get the film metadata by title."""
 
171
  if not title:
172
  return jsonify({'error': 'No title provided'}), 400
173
 
 
180
 
181
  return jsonify({'error': 'Metadata not found'}), 404
182
 
183
+ @app.route('/api/tv/metadata/<title>', methods=['GET'])
184
+ def get_tv_metadata_api(title):
185
  """Endpoint to get the TV show metadata by title."""
 
186
  if not title:
187
  return jsonify({'error': 'No title provided'}), 400
188
 
 
193
  data = json.load(f)
194
 
195
  # Add the file structure to the metadata
196
+ tv_structure_data = load_balancer.get_tv_structure(title)
197
  if tv_structure_data:
198
  data['file_structure'] = tv_structure_data
199
 
 
204
 
205
  @app.route("/api/film/all")
206
  def get_all_films_api():
207
+ return load_balancer.get_all_films()
208
 
209
  @app.route("/api/tv/all")
210
  def get_all_tvshows_api():
211
+ return load_balancer.get_all_tv_shows()
212
+
213
 
214
  #############################################################
215
  # unique api's
 
221
  return jsonify({"error": "No URL provided"}), 400
222
 
223
  url = data["url"]
224
+ if not load_balancer.is_valid_url(url):
225
  return jsonify({"error": "Invalid URL"}), 400
226
 
227
  # Register the instance
 
233
  except Exception as e:
234
  logging.error(f"Error registering instance: {e}")
235
  return jsonify({"error": "Failed to register instance"}), 500
236
+
237
+ @app.route('/api/instances',methods=["GET"])
238
+ def get_instances():
239
+ return load_balancer.instances
240
+
241
  #############################################################
242
  # Routes
243
  @app.route('/')
244
  def index():
245
  return f"Load Balancer is Running {load_balancer.version}"
246
 
 
 
 
 
247
  # Main entry point
248
  if __name__ == "__main__":
249
  app.run(debug=True, host="0.0.0.0", port=7860)
old.LoadBalancer.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ import logging
3
+ from threading import Thread, Event, Timer
4
+ from api import InstancesAPI
5
+
6
+ class LoadBalancer:
7
+ def __init__(self, polling_interval=10, max_retries=3, initial_delay=1):
8
+ self.version = "0.0.1 V Beta"
9
+ self.instances = []
10
+ self.polling_interval = polling_interval
11
+ self.max_retries = max_retries
12
+ self.initial_delay = initial_delay
13
+ self.stop_event = Event()
14
+ self.instances_api = InstancesAPI(self.instances)
15
+
16
+ def register_instance(self, instance_url):
17
+ if instance_url not in self.instances:
18
+ self.instances.append(instance_url)
19
+ logging.info(f"Registered instance {instance_url}")
20
+ else:
21
+ logging.info(f"Instance {instance_url} is already registered.")
22
+
23
+ def remove_instance(self, instance_url):
24
+ if instance_url in self.instances:
25
+ self.instances.remove(instance_url)
26
+ logging.info(f"Removed instance {instance_url}")
27
+ else:
28
+ logging.info(f"Instance {instance_url} not found for removal.")
29
+
30
+ def get_reports(self):
31
+ reports = self.instances_api.fetch_reports()
32
+ for instance_url in self.instances[:]: # Copy list to avoid modification during iteration
33
+ if instance_url in reports:
34
+ report = reports[instance_url]
35
+ logging.info(f"Report from {instance_url}: {report}")
36
+ self.process_report(instance_url, report)
37
+ else:
38
+ logging.error(f"Failed to get report from {instance_url}. Removing instance.")
39
+ self.remove_instance(instance_url)
40
+
41
+ def process_report(self, instance_url, report):
42
+ # Process the report (film_store, tv_store, cache_size) here
43
+ logging.info(f"Processing report from {instance_url}")
44
+ # Example: Print the film_store and tv_store
45
+ logging.info(f"Film Store: {report.get('film_store')}")
46
+ logging.info(f"TV Store: {report.get('tv_store')}")
47
+ logging.info(f"Cache Size: {report.get('cache_size')}")
48
+
49
+ def start_polling(self):
50
+ logging.info("Starting polling.")
51
+ while not self.stop_event.is_set():
52
+ self.get_reports()
53
+ time.sleep(self.polling_interval)
54
+ logging.info("Polling stopped.")
55
+
56
+ def stop_polling(self):
57
+ logging.info("Stopping polling.")
58
+ self.stop_event.set()
59
+
60
+ if __name__ == "__main__":
61
+ logging.basicConfig(level=logging.INFO)
62
+
63
+ load_balancer = LoadBalancer()
64
+
65
+ # Example registration (in real use, handle this via an API endpoint)
66
+ load_balancer.register_instance("http://localhost:5000")
67
+
68
+ # Start polling in a separate thread
69
+ polling_thread = Thread(target=load_balancer.start_polling)
70
+ polling_thread.start()
71
+
72
+ # Stop polling after some time for demonstration purposes
73
+ Timer(300, load_balancer.stop_polling).start() # Stop after 5 minutes
old.app.py ADDED
@@ -0,0 +1,418 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, jsonify, request, send_from_directory
2
+ from flask_cors import CORS
3
+ import os
4
+ import json
5
+ import threading
6
+ import urllib.parse
7
+ from hf_scrapper import download_film, download_episode, get_system_proxies, get_download_progress
8
+ from indexer import indexer
9
+ from tvdb import fetch_and_cache_json
10
+ import re
11
+ import logging
12
+ from LoadBalancer import LoadBalancer
13
+
14
+ logging.basicConfig(level=logging.INFO)
15
+ load_balancer = LoadBalancer()
16
+
17
+ # Start polling in a separate thread
18
+ polling_thread = threading.Thread(target=load_balancer.start_polling)
19
+ polling_thread.start()
20
+
21
+ app = Flask(__name__)
22
+ CORS(app)
23
+
24
+ # Constants and Configuration
25
+ CACHE_DIR = os.getenv("CACHE_DIR")
26
+ INDEX_FILE = os.getenv("INDEX_FILE")
27
+ TOKEN = os.getenv("TOKEN")
28
+ FILM_STORE_JSON_PATH = os.path.join(CACHE_DIR, "film_store.json")
29
+ TV_STORE_JSON_PATH = os.path.join(CACHE_DIR, "tv_store.json")
30
+ INSTANCE_REGISTER_JSON_PATH = os.path.join(CACHE_DIR, "instance_register.json")
31
+ REPO = os.getenv("REPO")
32
+ download_threads = {}
33
+
34
+ # Ensure CACHE_DIR exists
35
+ if not os.path.exists(CACHE_DIR):
36
+ os.makedirs(CACHE_DIR)
37
+
38
+ for path in [FILM_STORE_JSON_PATH, TV_STORE_JSON_PATH]:
39
+ if not os.path.exists(path):
40
+ with open(path, 'w') as json_file:
41
+ json.dump({}, json_file)
42
+
43
+ # Index the file structure
44
+ indexer()
45
+
46
+ # Load the file structure JSON
47
+ if not os.path.exists(INDEX_FILE):
48
+ raise FileNotFoundError(f"{INDEX_FILE} not found. Please make sure the file exists.")
49
+
50
+ with open(INDEX_FILE, 'r') as f:
51
+ file_structure = json.load(f)
52
+
53
+ # Function Definitions
54
+ def load_json(file_path):
55
+ """Load JSON data from a file."""
56
+ with open(file_path, 'r') as file:
57
+ return json.load(file)
58
+
59
+ def find_movie_path(json_data, title):
60
+ """Find the path of the movie in the JSON data based on the title."""
61
+ for directory in json_data:
62
+ if directory['type'] == 'directory' and directory['path'] == 'films':
63
+ for sub_directory in directory['contents']:
64
+ if sub_directory['type'] == 'directory':
65
+ for item in sub_directory['contents']:
66
+ if item['type'] == 'file' and title.lower() in item['path'].lower():
67
+ return item['path']
68
+ return None
69
+
70
+ def find_tv_path(json_data, title):
71
+ """Find the path of the TV show in the JSON data based on the title."""
72
+ for directory in json_data:
73
+ if directory['type'] == 'directory' and directory['path'] == 'tv':
74
+ for sub_directory in directory['contents']:
75
+ if sub_directory['type'] == 'directory' and title.lower() in sub_directory['path'].lower():
76
+ return sub_directory['path']
77
+ return None
78
+
79
+ def get_tv_structure(json_data,title):
80
+ """Find the path of the TV show in the JSON data based on the title."""
81
+ for directory in json_data:
82
+ if directory['type'] == 'directory' and directory['path'] == 'tv':
83
+ for sub_directory in directory['contents']:
84
+ if sub_directory['type'] == 'directory' and title.lower() in sub_directory['path'].lower():
85
+ return sub_directory
86
+ return None
87
+
88
+ def get_film_id(title):
89
+ """Generate a film ID based on the title."""
90
+ return title.replace(" ", "_").lower()
91
+
92
+ def prefetch_metadata():
93
+ """Prefetch metadata for all items in the file structure."""
94
+ for item in file_structure:
95
+ if 'contents' in item:
96
+ for sub_item in item['contents']:
97
+ original_title = sub_item['path'].split('/')[-1]
98
+ media_type = 'series' if item['path'].startswith('tv') else 'movie'
99
+ title = original_title
100
+ year = None
101
+
102
+ # Extract year from the title if available
103
+ match = re.search(r'\((\d{4})\)', original_title)
104
+ if match:
105
+ year_str = match.group(1)
106
+ if year_str.isdigit() and len(year_str) == 4:
107
+ title = original_title[:match.start()].strip()
108
+ year = int(year_str)
109
+ else:
110
+ parts = original_title.rsplit(' ', 1)
111
+ if len(parts) > 1 and parts[-1].isdigit() and len(parts[-1]) == 4:
112
+ title = parts[0].strip()
113
+ year = int(parts[-1])
114
+
115
+ fetch_and_cache_json(original_title, title, media_type, year)
116
+
117
+ def bytes_to_human_readable(num, suffix="B"):
118
+ for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
119
+ if abs(num) < 1024.0:
120
+ return f"{num:3.1f} {unit}{suffix}"
121
+ num /= 1024.0
122
+ return f"{num:.1f} Y{suffix}"
123
+
124
+ def encode_episodeid(title,season,episode):
125
+ return f"{title}_{season}_{episode}"
126
+
127
+ def is_valid_url(url):
128
+ # Simple URL validation (could be more complex if needed)
129
+ regex = re.compile(
130
+ r'^(?:http|ftp)s?://' # http:// or https://
131
+ r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
132
+ r'localhost|' # localhost...
133
+ r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
134
+ r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
135
+ r'(?::\d+)?' # optional port
136
+ r'(?:/?|[/?]\S+)$', re.IGNORECASE)
137
+ return re.match(regex, url) is not None
138
+
139
+ def get_all_tv_shows(indexed_cache):
140
+ """Get all TV shows from the indexed cache structure JSON file."""
141
+ tv_shows = {}
142
+ for directory in indexed_cache:
143
+ if directory['type'] == 'directory' and directory['path'] == 'tv':
144
+ for sub_directory in directory['contents']:
145
+ if sub_directory['type'] == 'directory':
146
+ show_title = sub_directory['path'].split('/')[-1]
147
+ tv_shows[show_title] = []
148
+ for season_directory in sub_directory['contents']:
149
+ if season_directory['type'] == 'directory':
150
+ season = season_directory['path'].split('/')[-1]
151
+ for episode in season_directory['contents']:
152
+ if episode['type'] == 'file':
153
+ tv_shows[show_title].append({
154
+ "season": season,
155
+ "episode": episode['path'].split('/')[-1],
156
+ "path": episode['path']
157
+ })
158
+ return tv_shows
159
+
160
+ def get_all_films(indexed_cache):
161
+ """Get all films from the indexed cache structure JSON file."""
162
+ films = []
163
+ for directory in indexed_cache:
164
+ if directory['type'] == 'directory' and directory['path'] == 'films':
165
+ for sub_directory in directory['contents']:
166
+ if sub_directory['type'] == 'directory':
167
+ films.append(sub_directory['path'])
168
+ return films
169
+
170
+ def start_prefetching():
171
+ """Start the metadata prefetching in a separate thread."""
172
+ prefetch_metadata()
173
+
174
+ # Start prefetching metadata
175
+ thread = threading.Thread(target=start_prefetching)
176
+ thread.daemon = True
177
+ thread.start()
178
+
179
+ # API Endpoints
180
+
181
+ @app.route('/api/film', methods=['GET'])
182
+ def get_movie_api():
183
+ """Endpoint to get the movie by title."""
184
+ title = request.args.get('title')
185
+ if not title:
186
+ return jsonify({"error": "Title parameter is required"}), 400
187
+
188
+ # Load the film store JSON
189
+ with open(FILM_STORE_JSON_PATH, 'r') as json_file:
190
+ film_store_data = json.load(json_file)
191
+
192
+ # Check if the film is already cached
193
+ if title in film_store_data:
194
+ cache_path = film_store_data[title]
195
+ if os.path.exists(cache_path):
196
+ return send_from_directory(os.path.dirname(cache_path), os.path.basename(cache_path))
197
+
198
+ movie_path = find_movie_path(file_structure, title)
199
+
200
+ if not movie_path:
201
+ return jsonify({"error": "Movie not found"}), 404
202
+
203
+ cache_path = os.path.join(CACHE_DIR, movie_path)
204
+ file_url = f"https://huggingface.co/{REPO}/resolve/main/{movie_path}"
205
+ proxies = get_system_proxies()
206
+ film_id = get_film_id(title)
207
+
208
+ # Start the download in a separate thread if not already downloading
209
+ if film_id not in download_threads or not download_threads[film_id].is_alive():
210
+ thread = threading.Thread(target=download_film, args=(file_url, TOKEN, cache_path, proxies, film_id, title))
211
+ download_threads[film_id] = thread
212
+ thread.start()
213
+
214
+ return jsonify({"status": "Download started", "film_id": film_id})
215
+
216
+ @app.route('/api/tv', methods=['GET'])
217
+ def get_tv_show_api():
218
+ """Endpoint to get the TV show by title, season, and episode."""
219
+ title = request.args.get('title')
220
+ season = request.args.get('season')
221
+ episode = request.args.get('episode')
222
+
223
+ if not title or not season or not episode:
224
+ return jsonify({"error": "Title, season, and episode parameters are required"}), 400
225
+
226
+ # Load the TV store JSON
227
+ with open(TV_STORE_JSON_PATH, 'r') as json_file:
228
+ tv_store_data = json.load(json_file)
229
+
230
+ # Check if the episode is already cached
231
+ if title in tv_store_data and season in tv_store_data[title]:
232
+ for ep in tv_store_data[title][season]:
233
+ if episode in ep:
234
+ cache_path = tv_store_data[title][season][ep]
235
+ if os.path.exists(cache_path):
236
+ return send_from_directory(os.path.dirname(cache_path), os.path.basename(cache_path))
237
+
238
+ tv_path = find_tv_path(file_structure, title)
239
+
240
+ if not tv_path:
241
+ return jsonify({"error": "TV show not found"}), 404
242
+
243
+ episode_path = None
244
+ for directory in file_structure:
245
+ if directory['type'] == 'directory' and directory['path'] == 'tv':
246
+ for sub_directory in directory['contents']:
247
+ if sub_directory['type'] == 'directory' and title.lower() in sub_directory['path'].lower():
248
+ for season_dir in sub_directory['contents']:
249
+ if season_dir['type'] == 'directory' and season in season_dir['path']:
250
+ for episode_file in season_dir['contents']:
251
+ if episode_file['type'] == 'file' and episode in episode_file['path']:
252
+ episode_path = episode_file['path']
253
+ break
254
+
255
+ if not episode_path:
256
+ return jsonify({"error": "Episode not found"}), 404
257
+
258
+ cache_path = os.path.join(CACHE_DIR, episode_path)
259
+ file_url = f"https://huggingface.co/{REPO}/resolve/main/{episode_path}"
260
+ proxies = get_system_proxies()
261
+ episode_id = encode_episodeid(title,season,episode)
262
+
263
+ # Start the download in a separate thread if not already downloading
264
+ if episode_id not in download_threads or not download_threads[episode_id].is_alive():
265
+ thread = threading.Thread(target=download_episode, args=(file_url, TOKEN, cache_path, proxies, episode_id, title))
266
+ download_threads[episode_id] = thread
267
+ thread.start()
268
+
269
+ return jsonify({"status": "Download started", "episode_id": episode_id})
270
+
271
+
272
+ @app.route('/api/progress/<id>', methods=['GET'])
273
+ def get_progress_api(id):
274
+ """Endpoint to get the download progress of a movie or TV show episode."""
275
+ progress = get_download_progress(id)
276
+ return jsonify({"id": id, "progress": progress})
277
+
278
+ @app.route('/api/cache/size', methods=['GET'])
279
+ def get_cache_size_api():
280
+ total_size = 0
281
+ for dirpath, dirnames, filenames in os.walk(CACHE_DIR):
282
+ for f in filenames:
283
+ fp = os.path.join(dirpath, f)
284
+ total_size += os.path.getsize(fp)
285
+ readable_size = bytes_to_human_readable(total_size)
286
+ return jsonify({"cache_size": readable_size})
287
+
288
+ @app.route('/api/cache/clear', methods=['POST'])
289
+ def clear_cache_api():
290
+ for dirpath, dirnames, filenames in os.walk(CACHE_DIR):
291
+ for f in filenames:
292
+ fp = os.path.join(dirpath, f)
293
+ os.remove(fp)
294
+ return jsonify({"status": "Cache cleared"})
295
+
296
+ @app.route('/api/tv/store', methods=['GET'])
297
+ def get_tv_store_api():
298
+ """Endpoint to get the TV store JSON."""
299
+ if os.path.exists(TV_STORE_JSON_PATH):
300
+ with open(TV_STORE_JSON_PATH, 'r') as json_file:
301
+ tv_store_data = json.load(json_file)
302
+ return jsonify(tv_store_data)
303
+ return jsonify({}), 404
304
+
305
+ @app.route('/api/film/store', methods=['GET'])
306
+ def get_film_store_api():
307
+ """Endpoint to get the film store JSON."""
308
+ if os.path.exists(FILM_STORE_JSON_PATH):
309
+ with open(FILM_STORE_JSON_PATH, 'r') as json_file:
310
+ tv_store_data = json.load(json_file)
311
+ return jsonify(tv_store_data)
312
+ return jsonify({}), 404
313
+
314
+ #################################################
315
+ # No change needed
316
+
317
+ @app.route('/api/filmid', methods=['GET'])
318
+ def get_film_id_by_title_api():
319
+ """Endpoint to get the film ID by providing the movie title."""
320
+ title = request.args.get('title')
321
+ if not title:
322
+ return jsonify({"error": "Title parameter is required"}), 400
323
+ film_id = get_film_id(title)
324
+ return jsonify({"film_id": film_id})
325
+
326
+ @app.route('/api/episodeid', methods=['GET'])
327
+ def get_episode_id_api():
328
+ """Endpoint to get the episode ID by providing the TV show title, season, and episode."""
329
+ title = request.args.get('title')
330
+ season = request.args.get('season')
331
+ episode = request.args.get('episode')
332
+ if not title or not season or not episode:
333
+ return jsonify({"error": "Title, season, and episode parameters are required"}), 400
334
+ episode_id = encode_episodeid(title,season,episode)
335
+ return jsonify({"episode_id": episode_id})
336
+
337
+ @app.route('/api/film/metadata', methods=['GET'])
338
+ def get_film_metadata_api():
339
+ """Endpoint to get the film metadata by title."""
340
+ title = request.args.get('title')
341
+ if not title:
342
+ return jsonify({'error': 'No title provided'}), 400
343
+
344
+ json_cache_path = os.path.join(CACHE_DIR, f"{urllib.parse.quote(title)}.json")
345
+
346
+ if os.path.exists(json_cache_path):
347
+ with open(json_cache_path, 'r') as f:
348
+ data = json.load(f)
349
+ return jsonify(data)
350
+
351
+ return jsonify({'error': 'Metadata not found'}), 404
352
+
353
+ @app.route('/api/tv/metadata', methods=['GET'])
354
+ def get_tv_metadata_api():
355
+ """Endpoint to get the TV show metadata by title."""
356
+ title = request.args.get('title')
357
+ if not title:
358
+ return jsonify({'error': 'No title provided'}), 400
359
+
360
+ json_cache_path = os.path.join(CACHE_DIR, f"{urllib.parse.quote(title)}.json")
361
+
362
+ if os.path.exists(json_cache_path):
363
+ with open(json_cache_path, 'r') as f:
364
+ data = json.load(f)
365
+
366
+ # Add the file structure to the metadata
367
+ tv_structure_data = get_tv_structure(file_structure, title)
368
+ if tv_structure_data:
369
+ data['file_structure'] = tv_structure_data
370
+
371
+ return jsonify(data)
372
+
373
+ return jsonify({'error': 'Metadata not found'}), 404
374
+
375
+
376
+ @app.route("/api/film/all")
377
+ def get_all_films_api():
378
+ return get_all_films(file_structure)
379
+
380
+ @app.route("/api/tv/all")
381
+ def get_all_tvshows_api():
382
+ return get_all_tv_shows(file_structure)
383
+
384
+ #############################################################
385
+ # unique api's
386
+ @app.route('/api/register', methods=['POST'])
387
+ def register_instance():
388
+ try:
389
+ data = request.json
390
+ if not data or "url" not in data:
391
+ return jsonify({"error": "No URL provided"}), 400
392
+
393
+ url = data["url"]
394
+ if not is_valid_url(url):
395
+ return jsonify({"error": "Invalid URL"}), 400
396
+
397
+ # Register the instance
398
+ load_balancer.register_instance(url)
399
+ logging.info(f"Instance registered: {url}")
400
+
401
+ return jsonify({"message": f"Instance {url} registered successfully"}), 200
402
+
403
+ except Exception as e:
404
+ logging.error(f"Error registering instance: {e}")
405
+ return jsonify({"error": "Failed to register instance"}), 500
406
+ #############################################################
407
+ # Routes
408
+ @app.route('/')
409
+ def index():
410
+ return f"Load Balancer is Running {load_balancer.version}"
411
+
412
+ @app.route('/api/instances',methods=["GET"])
413
+ def get_instances():
414
+ return load_balancer.instances
415
+
416
+ # Main entry point
417
+ if __name__ == "__main__":
418
+ app.run(debug=True, host="0.0.0.0", port=7860)