ChandimaPrabath commited on
Commit
5c6ae28
·
1 Parent(s): 8c81b5f
app.py CHANGED
@@ -5,7 +5,7 @@ import json
5
  import re
6
  import urllib.parse
7
  from threading import Thread
8
- from hf_scrapper import ffmpeg_stream
9
  from indexer import indexer
10
  from dotenv import load_dotenv
11
  from tvdb import fetch_and_cache_json
@@ -19,7 +19,7 @@ CACHE_DIR = os.getenv("CACHE_DIR")
19
  if not os.path.exists(CACHE_DIR):
20
  os.makedirs(CACHE_DIR)
21
 
22
- indexer()
23
 
24
  if not os.path.exists(INDEX_FILE):
25
  raise FileNotFoundError(f"{INDEX_FILE} not found. Please make sure the file exists.")
@@ -105,21 +105,33 @@ app = Flask(__name__)
105
  def home():
106
  return render_template('index.html')
107
 
108
- @app.route('/player')
109
- def player():
110
  return render_template('player.html')
111
 
 
 
 
 
112
  @app.route('/films')
 
 
 
 
113
  def list_films():
114
  films = [item for item in file_structure if item['path'].startswith('films')]
115
  return jsonify([sub_item for film in films for sub_item in film['contents']])
116
 
117
  @app.route('/tv')
 
 
 
 
118
  def list_tv():
119
  tv_shows = [item for item in file_structure if item['path'].startswith('tv')]
120
  return jsonify([sub_item for show in tv_shows for sub_item in show['contents']])
121
 
122
- @app.route('/film/<path:title>')
123
  def film_page(title):
124
  title = urllib.parse.unquote(title)
125
  film_file_path = get_film_file_path(title)
@@ -138,7 +150,7 @@ def film_page(title):
138
  'file_path': film_file_path
139
  })
140
 
141
- @app.route('/tv/<path:show_title>')
142
  def tv_page(show_title):
143
  show_title = urllib.parse.unquote(show_title)
144
  seasons = get_tv_show_seasons(show_title)
@@ -181,5 +193,9 @@ def stream_video():
181
  file_url = f"https://huggingface.co/{REPO}/resolve/main/{file_path}"
182
  return Response(generate(file_url), mimetype="video/mp4")
183
 
 
 
 
 
184
  if __name__ == '__main__':
185
  app.run(debug=True, host="0.0.0.0", port=7860)
 
5
  import re
6
  import urllib.parse
7
  from threading import Thread
8
+ from video import ffmpeg_stream
9
  from indexer import indexer
10
  from dotenv import load_dotenv
11
  from tvdb import fetch_and_cache_json
 
19
  if not os.path.exists(CACHE_DIR):
20
  os.makedirs(CACHE_DIR)
21
 
22
+ #indexer()
23
 
24
  if not os.path.exists(INDEX_FILE):
25
  raise FileNotFoundError(f"{INDEX_FILE} not found. Please make sure the file exists.")
 
105
  def home():
106
  return render_template('index.html')
107
 
108
+ @app.route('/tvshow_player')
109
+ def tvshow_player():
110
  return render_template('player.html')
111
 
112
+ @app.route('/film_player')
113
+ def film_player():
114
+ return render_template('film_player.html')
115
+
116
  @app.route('/films')
117
+ def films():
118
+ return render_template('films.html')
119
+
120
+ @app.route('/api/films')
121
  def list_films():
122
  films = [item for item in file_structure if item['path'].startswith('films')]
123
  return jsonify([sub_item for film in films for sub_item in film['contents']])
124
 
125
  @app.route('/tv')
126
+ def tv_shows():
127
+ return render_template('tvshows.html')
128
+
129
+ @app.route('/api/tv')
130
  def list_tv():
131
  tv_shows = [item for item in file_structure if item['path'].startswith('tv')]
132
  return jsonify([sub_item for show in tv_shows for sub_item in show['contents']])
133
 
134
+ @app.route('/api/film/<path:title>')
135
  def film_page(title):
136
  title = urllib.parse.unquote(title)
137
  film_file_path = get_film_file_path(title)
 
150
  'file_path': film_file_path
151
  })
152
 
153
+ @app.route('/api/tv/<path:show_title>')
154
  def tv_page(show_title):
155
  show_title = urllib.parse.unquote(show_title)
156
  seasons = get_tv_show_seasons(show_title)
 
193
  file_url = f"https://huggingface.co/{REPO}/resolve/main/{file_path}"
194
  return Response(generate(file_url), mimetype="video/mp4")
195
 
196
+ @app.route('/film/<path:title>')
197
+ def film_details(title):
198
+ return render_template('film_details_page.html', title=title)
199
+
200
  if __name__ == '__main__':
201
  app.run(debug=True, host="0.0.0.0", port=7860)
hf_scrapper.py CHANGED
@@ -18,25 +18,6 @@ def get_system_proxies():
18
  print(f"Error getting system proxies: {e}")
19
  return {}
20
 
21
- def ffmpeg_stream(file_url,token, output="tmp/cache/output.mp4"):
22
- try:
23
- # Set up the FFmpeg command with quality options
24
- ff = FFmpeg(
25
- inputs={
26
- file_url: None
27
- },
28
- outputs={
29
- f'{output}': '-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 192k'
30
- },
31
- global_options= f'-headers "Authorization: Bearer {token}"'
32
- )
33
-
34
- # Run the command
35
- ff.run()
36
-
37
- except Exception as e:
38
- print(f"Error using FFmpeg to stream file: {e}")
39
- return None
40
 
41
  # def download_file(file_url, token, output_path, proxies):
42
  # print(f"Downloading file from URL: {file_url} with proxies: {proxies}")
@@ -72,12 +53,3 @@ def write_file_structure_to_json(file_structure, file_path):
72
  print(f'File structure written to {file_path}')
73
  except IOError as e:
74
  print(f"Error writing file structure to JSON: {e}")
75
-
76
- # Example usage
77
- if __name__ == "__main__":
78
- proxies = get_system_proxies()
79
- token = 'hf token'
80
- file_url = 'file url'
81
-
82
- # Stream the video
83
- output_stream = ffmpeg_stream(file_url, token, output="tmp/cache/video.mp4")
 
18
  print(f"Error getting system proxies: {e}")
19
  return {}
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  # def download_file(file_url, token, output_path, proxies):
23
  # print(f"Downloading file from URL: {file_url} with proxies: {proxies}")
 
53
  print(f'File structure written to {file_path}')
54
  except IOError as e:
55
  print(f"Error writing file structure to JSON: {e}")
 
 
 
 
 
 
 
 
 
templates/film_details_page.html ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ <style>
8
+ body {
9
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10
+ background: linear-gradient(to right, #141414, #333);
11
+ color: #fff;
12
+ margin: 0;
13
+ padding: 0;
14
+ display: flex;
15
+ justify-content: center;
16
+ align-items: center;
17
+ min-height: 100vh;
18
+ }
19
+ .container {
20
+ width: 90%;
21
+ max-width: 1200px;
22
+ display: flex;
23
+ gap: 20px;
24
+ padding: 20px;
25
+ background-color: #1e1e1e;
26
+ border-radius: 10px;
27
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
28
+ }
29
+ .poster {
30
+ flex: 1;
31
+ max-width: 300px;
32
+ position: relative;
33
+ }
34
+ .poster img {
35
+ width: 100%;
36
+ height: auto;
37
+ border-radius: 10px;
38
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.8);
39
+ }
40
+ .details {
41
+ flex: 2;
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: 20px;
45
+ padding: 10px;
46
+ }
47
+ .details h2 {
48
+ margin: 0;
49
+ font-size: 36px;
50
+ font-weight: bold;
51
+ }
52
+ .details p {
53
+ margin: 5px 0;
54
+ font-size: 16px;
55
+ line-height: 1.6;
56
+ color: #d1d1d1;
57
+ }
58
+ .genres, .metadata {
59
+ display: flex;
60
+ flex-wrap: wrap;
61
+ gap: 10px;
62
+ }
63
+ .genres div, .metadata div {
64
+ background-color: #333;
65
+ padding: 8px 12px;
66
+ border-radius: 5px;
67
+ font-size: 14px;
68
+ }
69
+ .metadata div {
70
+ background-color: #444;
71
+ }
72
+ .play-button {
73
+ display: inline-block;
74
+ padding: 15px 30px;
75
+ background: linear-gradient(to right, #ff6f61, #ff3f34);
76
+ color: #fff;
77
+ text-decoration: none;
78
+ border-radius: 50px;
79
+ font-size: 18px;
80
+ font-weight: bold;
81
+ text-align: center;
82
+ transition: background 0.3s, transform 0.3s;
83
+ }
84
+ .play-button:hover {
85
+ background: linear-gradient(to right, #ff3f34, #ff6f61);
86
+ transform: scale(1.05);
87
+ }
88
+ .play-button:focus {
89
+ outline: none;
90
+ box-shadow: 0 0 0 2px rgba(255, 105, 97, 0.5);
91
+ }
92
+ .error-message {
93
+ color: #ff4d4d;
94
+ font-size: 18px;
95
+ text-align: center;
96
+ margin-top: 20px;
97
+ }
98
+ .metadata-container {
99
+ display: flex;
100
+ flex-direction: column;
101
+ gap: 10px;
102
+ }
103
+ .metadata-container div {
104
+ display: flex;
105
+ justify-content: space-between;
106
+ padding: 5px 10px;
107
+ background-color: #333;
108
+ border-radius: 5px;
109
+ }
110
+ .metadata-container div span {
111
+ font-weight: bold;
112
+ }
113
+ </style>
114
+ </head>
115
+ <body>
116
+ <div class="container">
117
+ <div class="poster" id="film-poster">
118
+ <img src="https://via.placeholder.com/300x450.png" alt="Poster">
119
+ </div>
120
+ <div class="details" id="film-details">
121
+ <h2 id="film-title">Film Title</h2>
122
+ <p id="film-overview">Film overview goes here...</p>
123
+ <div class="genres" id="film-genres"></div>
124
+ <div class="metadata-container" id="film-metadata"></div>
125
+ <a href="#" class="play-button" id="play-button">Play</a>
126
+ </div>
127
+ </div>
128
+ <script>
129
+ async function fetchData(endpoint) {
130
+ try {
131
+ const response = await fetch(endpoint);
132
+ if (!response.ok) throw new Error('Network response was not ok');
133
+ return await response.json();
134
+ } catch (error) {
135
+ console.error('Fetch error:', error);
136
+ return null;
137
+ }
138
+ }
139
+
140
+ function createMetadataElement(label, value) {
141
+ const div = document.createElement('div');
142
+ div.innerHTML = `<span>${label}</span><span>${value}</span>`;
143
+ return div;
144
+ }
145
+
146
+ function createGenreElement(genre) {
147
+ const div = document.createElement('div');
148
+ div.textContent = genre;
149
+ return div;
150
+ }
151
+
152
+ async function loadFilmDetails(title) {
153
+ const data = await fetchData(`/api/film/${encodeURIComponent(title)}`);
154
+ if (!data || data.error) {
155
+ document.getElementById('film-details').innerHTML = '<p class="error-message">Film not found</p>';
156
+ return;
157
+ }
158
+
159
+ const metadata = data.metadata.data[0];
160
+ document.getElementById('film-poster').querySelector('img').src = metadata.image_url || 'https://via.placeholder.com/300x450.png';
161
+ document.getElementById('film-title').textContent = metadata.extended_title;
162
+ document.getElementById('film-overview').textContent = metadata.overview;
163
+
164
+ const genresContainer = document.getElementById('film-genres');
165
+ genresContainer.innerHTML = '';
166
+ metadata.genres.forEach(genre => genresContainer.appendChild(createGenreElement(genre)));
167
+
168
+ const metadataContainer = document.getElementById('film-metadata');
169
+ metadataContainer.innerHTML = '';
170
+ metadataContainer.appendChild(createMetadataElement('Director', metadata.director));
171
+ metadataContainer.appendChild(createMetadataElement('Country', metadata.country));
172
+ metadataContainer.appendChild(createMetadataElement('Release Date', metadata.first_air_time));
173
+
174
+ const playButton = document.getElementById('play-button');
175
+ playButton.href = `/stream?path=${encodeURIComponent(data.file_path)}`;
176
+ }
177
+
178
+ const urlParams = new URLSearchParams(window.location.search);
179
+ const title = "{{ title }}";
180
+ if (title) {
181
+ loadFilmDetails(title);
182
+ } else {
183
+ document.getElementById('film-details').innerHTML = '<p class="error-message">No film title provided</p>';
184
+ }
185
+ </script>
186
+ </body>
187
+ </html>
templates/film_player.html CHANGED
@@ -36,7 +36,7 @@
36
  </head>
37
  <body>
38
  <div class="header">
39
- <h1>Media Player</h1>
40
  </div>
41
  <div class="content">
42
  <div class="player-container">
 
36
  </head>
37
  <body>
38
  <div class="header">
39
+ <h1>Movie Player</h1>
40
  </div>
41
  <div class="content">
42
  <div class="player-container">
templates/films.html CHANGED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>All Films</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
+ .section {
26
+ margin-bottom: 40px;
27
+ }
28
+ .section h2 {
29
+ margin-bottom: 20px;
30
+ }
31
+ .grid {
32
+ display: grid;
33
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
34
+ gap: 20px;
35
+ }
36
+ .card {
37
+ background-color: #333;
38
+ border-radius: 10px;
39
+ overflow: hidden;
40
+ text-align: center;
41
+ transition: transform 0.2s;
42
+ cursor: pointer;
43
+ }
44
+ .card:hover {
45
+ transform: scale(1.05);
46
+ }
47
+ .card img {
48
+ width: 100%;
49
+ height: 300px;
50
+ object-fit: cover;
51
+ }
52
+ .card h3 {
53
+ margin: 0;
54
+ padding: 10px;
55
+ font-size: 18px;
56
+ }
57
+ .card p {
58
+ margin: 0;
59
+ padding: 10px;
60
+ }
61
+ </style>
62
+ </head>
63
+ <body>
64
+ <div class="header">
65
+ <h1>All Films</h1>
66
+ </div>
67
+ <div class="content">
68
+ <div class="section" id="films">
69
+ <h2>Films</h2>
70
+ <div class="grid" id="films-grid"></div>
71
+ </div>
72
+ </div>
73
+ <script>
74
+ async function fetchData(endpoint) {
75
+ try {
76
+ const response = await fetch(endpoint);
77
+ if (!response.ok) throw new Error('Network response was not ok');
78
+ return await response.json();
79
+ } catch (error) {
80
+ console.error('Fetch error:', error);
81
+ return [];
82
+ }
83
+ }
84
+
85
+ async function fetchMetadata(title) {
86
+ try {
87
+ const response = await fetch(`/get_metadata?title=${encodeURIComponent(title)}`);
88
+ if (response.ok) {
89
+ const data = await response.json();
90
+ if (data.data && data.data.length > 0) {
91
+ const thumbnailUrl = data.data[0].thumbnail;
92
+ return thumbnailUrl;
93
+ }
94
+ }
95
+ } catch (error) {
96
+ console.error('Metadata fetch error:', error);
97
+ }
98
+ return null;
99
+ }
100
+
101
+ function createCard(item) {
102
+ const card = document.createElement('div');
103
+ card.className = 'card';
104
+ const title = item.path.split('/').pop();
105
+ card.innerHTML = `
106
+ <img src="https://via.placeholder.com/340x500.png" alt="${title}">
107
+ <h3>${title}</h3>
108
+ `;
109
+ // Redirect to the appropriate page on card click
110
+ card.onclick = () => {
111
+ window.location.href = `/film/${encodeURIComponent(title)}`;
112
+ };
113
+ fetchMetadata(title).then(thumbnailUrl => {
114
+ if (thumbnailUrl !== null) {
115
+ card.querySelector('img').src = thumbnailUrl;
116
+ }
117
+ });
118
+ return card;
119
+ }
120
+
121
+ async function populateGrid(endpoint, gridId) {
122
+ const grid = document.getElementById(gridId);
123
+ const data = await fetchData(endpoint);
124
+ data.forEach(item => {
125
+ grid.appendChild(createCard(item));
126
+ });
127
+ }
128
+
129
+ populateGrid('/api/films', 'films-grid');
130
+ </script>
131
+ </body>
132
+ </html>
templates/index.html CHANGED
@@ -1,62 +1,108 @@
1
  <!DOCTYPE html>
2
- <html>
3
  <head>
 
 
4
  <title>Media Library</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
  .section {
26
- margin-bottom: 40px;
27
  }
28
  .section h2 {
29
  margin-bottom: 20px;
 
 
 
 
 
 
 
 
 
 
30
  }
31
  .grid {
32
  display: grid;
33
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
34
  gap: 20px;
 
35
  }
36
  .card {
37
- background-color: #333;
38
- border-radius: 10px;
 
39
  overflow: hidden;
40
  text-align: center;
41
- transition: transform 0.2s;
42
  cursor: pointer;
 
43
  }
44
  .card:hover {
45
  transform: scale(1.05);
 
46
  }
47
  .card img {
48
  width: 100%;
49
- height: 300px;
50
  object-fit: cover;
 
51
  }
52
  .card h3 {
53
  margin: 0;
54
  padding: 10px;
55
- font-size: 18px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
57
  .card p {
58
  margin: 0;
59
  padding: 10px;
 
 
60
  }
61
  </style>
62
  </head>
@@ -66,11 +112,11 @@
66
  </div>
67
  <div class="content">
68
  <div class="section" id="films">
69
- <h2>Films</h2>
70
  <div class="grid" id="films-grid"></div>
71
  </div>
72
  <div class="section" id="tv">
73
- <h2>TV Shows</h2>
74
  <div class="grid" id="tv-grid"></div>
75
  </div>
76
  </div>
@@ -107,7 +153,7 @@
107
  card.className = 'card';
108
  const title = item.path.split('/').pop();
109
  card.innerHTML = `
110
- <img src="https://via.placeholder.com/340x500.png" alt="${title}">
111
  <h3>${title}</h3>
112
  `;
113
  // Redirect to the appropriate page on card click
@@ -126,16 +172,16 @@
126
  return card;
127
  }
128
 
129
- async function populateGrid(endpoint, gridId, mediaType) {
130
  const grid = document.getElementById(gridId);
131
  const data = await fetchData(endpoint);
132
- data.forEach(item => {
133
  grid.appendChild(createCard(item, mediaType));
134
  });
135
  }
136
 
137
- populateGrid('/films', 'films-grid', 'movie');
138
- populateGrid('/tv', 'tv-grid', 'series');
139
  </script>
140
  </body>
141
  </html>
 
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>
 
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>
 
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
 
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/tv.html DELETED
File without changes
templates/tv_player.html DELETED
File without changes
templates/{film_page.html → tvshow_details_page.html} RENAMED
File without changes
templates/tvshow_page.html DELETED
File without changes
templates/tvshow_player.html ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>All TV Shows</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
+ .section {
26
+ margin-bottom: 40px;
27
+ }
28
+ .section h2 {
29
+ margin-bottom: 20px;
30
+ }
31
+ .grid {
32
+ display: grid;
33
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
34
+ gap: 20px;
35
+ }
36
+ .card {
37
+ background-color: #333;
38
+ border-radius: 10px;
39
+ overflow: hidden;
40
+ text-align: center;
41
+ transition: transform 0.2s;
42
+ cursor: pointer;
43
+ }
44
+ .card:hover {
45
+ transform: scale(1.05);
46
+ }
47
+ .card img {
48
+ width: 100%;
49
+ height: 300px;
50
+ object-fit: cover;
51
+ }
52
+ .card h3 {
53
+ margin: 0;
54
+ padding: 10px;
55
+ font-size: 18px;
56
+ }
57
+ .card p {
58
+ margin: 0;
59
+ padding: 10px;
60
+ }
61
+ </style>
62
+ </head>
63
+ <body>
64
+ <div class="header">
65
+ <h1>All TV Shows</h1>
66
+ </div>
67
+ <div class="content">
68
+ <div class="section" id="tv">
69
+ <h2>TV Shows</h2>
70
+ <div class="grid" id="tv-grid"></div>
71
+ </div>
72
+ </div>
73
+ <script>
74
+ async function fetchData(endpoint) {
75
+ try {
76
+ const response = await fetch(endpoint);
77
+ if (!response.ok) throw new Error('Network response was not ok');
78
+ return await response.json();
79
+ } catch (error) {
80
+ console.error('Fetch error:', error);
81
+ return [];
82
+ }
83
+ }
84
+
85
+ async function fetchMetadata(title) {
86
+ try {
87
+ const response = await fetch(`/get_metadata?title=${encodeURIComponent(title)}`);
88
+ if (response.ok) {
89
+ const data = await response.json();
90
+ if (data.data && data.data.length > 0) {
91
+ const thumbnailUrl = data.data[0].thumbnail;
92
+ return thumbnailUrl;
93
+ }
94
+ }
95
+ } catch (error) {
96
+ console.error('Metadata fetch error:', error);
97
+ }
98
+ return null;
99
+ }
100
+
101
+ function createCard(item) {
102
+ const card = document.createElement('div');
103
+ card.className = 'card';
104
+ const title = item.path.split('/').pop();
105
+ card.innerHTML = `
106
+ <img src="https://via.placeholder.com/340x500.png" alt="${title}">
107
+ <h3>${title}</h3>
108
+ `;
109
+ // Redirect to the appropriate page on card click
110
+ card.onclick = () => {
111
+ window.location.href = `/tv/${encodeURIComponent(title)}`;
112
+ };
113
+ fetchMetadata(title).then(thumbnailUrl => {
114
+ if (thumbnailUrl !== null) {
115
+ card.querySelector('img').src = thumbnailUrl;
116
+ }
117
+ });
118
+ return card;
119
+ }
120
+
121
+ async function populateGrid(endpoint, gridId) {
122
+ const grid = document.getElementById(gridId);
123
+ const data = await fetchData(endpoint);
124
+ data.forEach(item => {
125
+ grid.appendChild(createCard(item));
126
+ });
127
+ }
128
+
129
+ populateGrid('/api/tv', 'tv-grid');
130
+ </script>
131
+ </body>
132
+ </html>
video.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ffmpy import FFmpeg
2
+
3
+ def ffmpeg_stream(file_url,token, output="tmp/cache/output.mp4"):
4
+ try:
5
+ # Set up the FFmpeg command with quality options
6
+ ff = FFmpeg(
7
+ inputs={
8
+ file_url: None
9
+ },
10
+ outputs={
11
+ f'{output}': '-c:v libx264 -crf 23 -preset medium -c:a aac -b:a 192k'
12
+ },
13
+ global_options= f'-headers "Authorization: Bearer {token}"'
14
+ )
15
+
16
+ # Run the command
17
+ ff.run()
18
+
19
+ except Exception as e:
20
+ print(f"Error using FFmpeg to stream file: {e}")
21
+ return None