Bsweb1 commited on
Commit
01ca3fd
·
verified ·
1 Parent(s): 15cb3c5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +107 -250
app.py CHANGED
@@ -1,19 +1,21 @@
1
  from fastapi import FastAPI, Request, Query
2
- from fastapi.responses import JSONResponse, RedirectResponse, HTMLResponse
3
- from fastapi.staticfiles import StaticFiles
4
  from fastapi.middleware.cors import CORSMiddleware
5
- from datetime import datetime, timedelta
6
  import requests
7
  from bs4 import BeautifulSoup
8
  import os
9
  import logging
10
  import time
11
- from typing import Dict, List, Optional
12
 
13
- # App configuration
14
- app = FastAPI(title="StreamFlix Addon", version="1.1.0")
15
 
16
- # Middleware
 
 
 
 
17
  app.add_middleware(
18
  CORSMiddleware,
19
  allow_origins=["*"],
@@ -22,70 +24,38 @@ app.add_middleware(
22
  allow_headers=["*"],
23
  )
24
 
25
- # Mount static files
26
- app.mount("/static", StaticFiles(directory="static"), name="static")
27
-
28
- # Constants
29
- BASE_URL = os.getenv("BASE_URL", "")
30
- TMDB_API_KEY = os.getenv("TMDB_API_KEY", "")
31
  TMDB_API_URL = "https://api.themoviedb.org/3"
32
  USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
33
  REQUEST_TIMEOUT = 15
34
  MAX_RETRIES = 2
35
 
36
- # Catalog settings
37
- CATALOG_PAGE_SIZE = 20
38
- RECENT_RELEASES_LIMIT = 20
39
- RECENT_MOVIES_DAYS = 30
40
- RECENT_TV_DAYS = 14
41
- TMDB_DISCOVER_CACHE_TIME = 3600
42
-
43
- # Configure logging
44
- logging.basicConfig(level=logging.INFO)
45
- logger = logging.getLogger(__name__)
46
-
47
- # Manifest with all features
48
  MANIFEST = {
49
  "id": "community.streamflix.hf",
50
- "version": "1.1.0",
51
  "catalogs": [
52
  {
53
  "type": "movie",
54
  "id": "streamflix_movies",
55
  "name": "StreamFlix - Filmes",
56
- "extra": [
57
- {"name": "search", "isRequired": False},
58
- {"name": "genre", "isRequired": False}
59
- ]
60
  },
61
  {
62
  "type": "series",
63
  "id": "streamflix_series",
64
  "name": "StreamFlix - Séries",
65
- "extra": [
66
- {"name": "search", "isRequired": False},
67
- {"name": "genre", "isRequired": False}
68
- ]
69
- },
70
- {
71
- "type": "movie",
72
- "id": "streamflix_recent_movies",
73
- "name": "Lançamentos Recentes - Filmes",
74
- "extra": []
75
- },
76
- {
77
- "type": "series",
78
- "id": "streamflix_recent_series",
79
- "name": "Lançamentos Recentes - Séries",
80
- "extra": []
81
  }
82
  ],
83
  "resources": ["catalog", "stream", "meta"],
84
  "types": ["movie", "series"],
85
- "name": "StreamFlix Premium",
86
- "description": "O melhor addon para filmes e séries com conteúdo em português!",
87
- "logo": "https://i.imgur.com/8t14k1R.png",
88
- "background": "https://i.imgur.com/y6fryeO.jpeg",
89
  "idPrefixes": ["tt", "tmdb"],
90
  "behaviorHints": {"adult": False}
91
  }
@@ -96,6 +66,7 @@ class VOD:
96
  self.session = requests.Session()
97
  self.session.headers.update({"User-Agent": USER_AGENT})
98
  self.cache = {}
 
99
 
100
  def _request(self, url, method="get", data=None, referer=None, retry=0):
101
  headers = {"User-Agent": USER_AGENT}
@@ -206,6 +177,7 @@ class VOD:
206
  if not options_data or not options_data.get('data') or not options_data['data'].get('options'):
207
  return None
208
 
 
209
  selected_option = None
210
  for option in options_data['data']['options']:
211
  title = option.get('title', '').lower()
@@ -251,6 +223,7 @@ class VOD:
251
  if not player_items:
252
  return None
253
 
 
254
  selected_player = None
255
  for player in player_items:
256
  title = player.get_text(strip=True).lower()
@@ -281,167 +254,70 @@ class VOD:
281
  logger.error(f"Error in movie: {e}", exc_info=True)
282
  return None
283
 
284
- class CatalogManager:
285
- def __init__(self):
286
- self.cache = {}
287
- self.genre_map = {}
288
- self.last_genre_update = 0
289
-
290
- async def initialize_genres(self):
291
- if time.time() - self.last_genre_update < 86400:
292
- return
293
-
294
- try:
295
- movie_url = f"{TMDB_API_URL}/genre/movie/list"
296
- movie_params = {"api_key": TMDB_API_KEY, "language": "pt-BR"}
297
- movie_resp = requests.get(movie_url, params=movie_params)
298
- movie_genres = {g['id']: g['name'] for g in movie_resp.json().get('genres', [])}
299
-
300
- tv_url = f"{TMDB_API_URL}/genre/tv/list"
301
- tv_params = {"api_key": TMDB_API_KEY, "language": "pt-BR"}
302
- tv_resp = requests.get(tv_url, params=tv_params)
303
- tv_genres = {g['id']: g['name'] for g in tv_resp.json().get('genres', [])}
304
-
305
- self.genre_map = {
306
- 'movie': movie_genres,
307
- 'tv': tv_genres
308
- }
309
- self.last_genre_update = time.time()
310
- except Exception as e:
311
- logger.error(f"Erro ao carregar gêneros: {e}")
312
 
313
- async def get_catalog(self, media_type: str, page: int = 1, search: str = None, genre: str = None) -> Dict:
314
- cache_key = f"catalog_{media_type}_{page}_{search}_{genre}"
315
-
316
- if cache_key in self.cache:
317
- return self.cache[cache_key]
318
-
319
- await self.initialize_genres()
320
 
321
- try:
 
 
322
  params = {
323
  "api_key": TMDB_API_KEY,
324
- "language": "pt-BR",
325
  "page": page,
326
- "sort_by": "popularity.desc",
327
- "include_adult": False,
328
- "region": "BR"
329
  }
330
-
331
- if search:
332
- url = f"{TMDB_API_URL}/search/{media_type}"
333
- params["query"] = search
334
- else:
335
- url = f"{TMDB_API_URL}/discover/{media_type}"
336
- if genre and genre.isdigit():
337
- params["with_genres"] = genre
338
-
339
- if media_type == "movie":
340
- params["primary_release_date.lte"] = datetime.now().strftime("%Y-%m-%d")
341
- params["vote_count.gte"] = 100
342
- else:
343
- params["first_air_date.lte"] = datetime.now().strftime("%Y-%m-%d")
344
- params["vote_count.gte"] = 50
345
-
346
- response = requests.get(url, params=params, timeout=10)
347
- response.raise_for_status()
348
- data = response.json()
349
-
350
- metas = []
351
- for item in data.get("results", []):
352
- if not item.get("poster_path"):
353
- continue
354
-
355
- meta = self._build_meta(media_type, item)
356
- metas.append(meta)
357
-
358
- result = {"metas": metas, "hasMore": page < data.get("total_pages", 1)}
359
- self.cache[cache_key] = result
360
- return result
361
-
362
- except Exception as e:
363
- logger.error(f"Erro ao buscar catálogo: {e}")
364
- return {"metas": [], "hasMore": False}
365
-
366
- async def get_recent_releases(self, media_type: str) -> Dict:
367
- cache_key = f"recent_{media_type}"
368
-
369
- if cache_key in self.cache:
370
- return self.cache[cache_key]
371
-
372
- try:
373
- await self.initialize_genres()
374
-
375
  params = {
376
  "api_key": TMDB_API_KEY,
377
- "language": "pt-BR",
378
  "sort_by": "popularity.desc",
379
- "include_adult": False,
380
- "region": "BR",
381
- "vote_count.gte": 50 if media_type == "tv" else 100
382
  }
383
-
384
- today = datetime.now().strftime("%Y-%m-%d")
385
- if media_type == "movie":
386
- start_date = (datetime.now() - timedelta(days=RECENT_MOVIES_DAYS)).strftime("%Y-%m-%d")
387
- params["primary_release_date.gte"] = start_date
388
- params["primary_release_date.lte"] = today
389
- else:
390
- start_date = (datetime.now() - timedelta(days=RECENT_TV_DAYS)).strftime("%Y-%m-%d")
391
- params["first_air_date.gte"] = start_date
392
- params["first_air_date.lte"] = today
393
-
394
- url = f"{TMDB_API_URL}/discover/{media_type}"
395
- response = requests.get(url, params=params, timeout=10)
396
- response.raise_for_status()
397
- data = response.json()
398
-
399
- metas = []
400
- for item in data.get("results", [])[:RECENT_RELEASES_LIMIT]:
401
- if not item.get("poster_path"):
402
- continue
403
-
404
- meta = self._build_meta(media_type, item)
405
- metas.append(meta)
406
-
407
- result = {"metas": metas}
408
- self.cache[cache_key] = result
409
- return result
410
-
411
- except Exception as e:
412
- logger.error(f"Erro ao buscar lançamentos recentes: {e}")
413
- return {"metas": []}
414
 
415
- def _build_meta(self, media_type: str, item: Dict) -> Dict:
416
- genre_ids = item.get("genre_ids", [])
417
- genres = [self.genre_map.get(media_type, {}).get(gid, "") for gid in genre_ids[:3]]
418
- genres = [g for g in genres if g]
419
 
420
- release_date = item.get("release_date") if media_type == "movie" else item.get("first_air_date")
421
- year = release_date[:4] if release_date else "N/A"
 
422
 
423
- return {
424
- "id": f"tmdb:{item['id']}",
425
- "type": media_type,
426
- "name": item.get("title") if media_type == "movie" else item.get("name"),
427
- "genres": genres,
428
- "description": item.get("overview", "Descrição não disponível.")[:300],
429
- "poster": f"https://image.tmdb.org/t/p/w500{item['poster_path']}",
430
- "background": f"https://image.tmdb.org/t/p/original{item['backdrop_path']}" if item.get("backdrop_path") else "",
431
- "releaseInfo": year,
432
- "imdbRating": round(item.get("vote_average", 0), 1),
433
- "popularity": round(item.get("popularity", 0), 1)
434
- }
435
-
436
- # Initialize services
437
- vod_api = VOD()
438
- catalog_manager = CatalogManager()
439
 
440
- # Routes
441
  @app.get("/")
442
- async def home():
443
- with open("static/index.html") as f:
444
- return HTMLResponse(content=f.read(), status_code=200)
445
 
446
  @app.get("/manifest.json")
447
  async def get_manifest():
@@ -452,46 +328,55 @@ async def get_catalog(
452
  type: str,
453
  id: str,
454
  request: Request,
455
- skip: int = Query(0, alias="skip"),
456
- search: str = Query(None, alias="search"),
457
- genre: str = Query(None, alias="genre")
458
  ):
459
- logger.info(f"Catalog request - Type: {type}, ID: {id}, Skip: {skip}, Search: {search}, Genre: {genre}")
460
-
461
- if id in ["streamflix_recent_movies", "streamflix_recent_series"]:
462
- media_type = "movie" if id == "streamflix_recent_movies" else "tv"
463
- recent_releases = await catalog_manager.get_recent_releases(media_type)
464
- return JSONResponse(recent_releases)
465
 
466
  if id not in ["streamflix_movies", "streamflix_series"]:
467
  return JSONResponse({"metas": []})
468
 
 
469
  media_type = "movie" if id == "streamflix_movies" else "tv"
470
- page = (skip // CATALOG_PAGE_SIZE) + 1
 
 
 
471
 
472
- catalog = await catalog_manager.get_catalog(
473
- media_type=media_type,
474
- page=page,
475
- search=search,
476
- genre=genre
477
- )
 
 
 
 
 
 
 
 
 
 
 
478
 
479
- return JSONResponse(catalog)
480
 
481
  @app.get("/meta/{type}/{id}.json")
482
  async def get_meta(type: str, id: str):
483
  try:
484
- tmdb_id = id.split(":")[-1] if ":" in id else id
485
  media_type = "movie" if type == "movie" else "tv"
486
 
487
- if not tmdb_id.isdigit() and type == "movie" and id.startswith("tt"):
488
  imdb_id = id
489
- elif tmdb_id.isdigit():
490
  imdb_id = convert_tmdb_to_imdb(tmdb_id, media_type) if type == "movie" else ""
491
  else:
492
  raise ValueError("Invalid ID format")
493
 
494
- if not tmdb_id.isdigit() and type != "movie":
495
  return JSONResponse({
496
  "meta": {
497
  "id": id,
@@ -506,28 +391,25 @@ async def get_meta(type: str, id: str):
506
  params = {
507
  "api_key": TMDB_API_KEY,
508
  "language": "pt-BR",
509
- "append_to_response": "external_ids,credits"
510
  }
511
 
512
  response = requests.get(url, params=params, timeout=REQUEST_TIMEOUT)
513
  response.raise_for_status()
514
  details = response.json()
515
 
516
- genre_ids = [g["id"] for g in details.get("genres", [])]
517
- genres = [catalog_manager.genre_map.get(media_type, {}).get(gid, "") for gid in genre_ids[:3]]
518
-
519
  meta = {
520
  "id": id,
521
  "type": type,
522
  "name": details.get("title") if media_type == "movie" else details.get("name"),
523
- "genres": [g for g in genres if g],
524
  "description": details.get("overview", "Sem descrição disponível"),
525
  "poster": f"https://image.tmdb.org/t/p/w500{details['poster_path']}" if details.get("poster_path") else MANIFEST["logo"],
526
  "background": f"https://image.tmdb.org/t/p/original{details['backdrop_path']}" if details.get("backdrop_path") else MANIFEST["background"],
527
  "releaseInfo": details.get("release_date", "")[:4] if media_type == "movie" else details.get("first_air_date", "")[:4],
528
  "imdbRating": round(details.get("vote_average", 0), 1),
529
  "director": ", ".join(
530
- [crew["name"] for crew in details.get("credits", {}).get("crew", [])
531
  if crew.get("job") == "Director"
532
  ][:2]) if media_type == "movie" else None
533
  }
@@ -565,7 +447,7 @@ async def get_stream(type: str, id: str):
565
  try:
566
  if type == "movie":
567
  if id.startswith("tmdb:"):
568
- tmdb_id = id.split(":")[-1]
569
  imdb_id = convert_tmdb_to_imdb(tmdb_id, "movie")
570
  if not imdb_id:
571
  logger.warning(f"Failed to convert TMDB to IMDB: {id}")
@@ -612,29 +494,4 @@ async def get_stream(type: str, id: str):
612
  except Exception as e:
613
  logger.error(f"Stream request failed: {e}")
614
 
615
- return JSONResponse({"streams": []})
616
-
617
- @app.get("/genres/{media_type}.json")
618
- async def get_genres(media_type: str):
619
- await catalog_manager.initialize_genres()
620
- genres = catalog_manager.genre_map.get(media_type, {})
621
- return JSONResponse({"genres": genres})
622
-
623
- def convert_tmdb_to_imdb(tmdb_id, media_type="movie"):
624
- cache_key = f"tmdb_to_imdb_{media_type}_{tmdb_id}"
625
- if cache_key in vod_api.cache:
626
- return vod_api.cache[cache_key]
627
-
628
- try:
629
- url = f"{TMDB_API_URL}/{media_type}/{tmdb_id}/external_ids"
630
- params = {"api_key": TMDB_API_KEY}
631
-
632
- response = requests.get(url, params=params, timeout=REQUEST_TIMEOUT)
633
- response.raise_for_status()
634
- data = response.json()
635
- imdb_id = data.get("imdb_id", "")
636
- vod_api.cache[cache_key] = imdb_id
637
- return imdb_id
638
- except Exception as e:
639
- logger.error(f"TMDB conversion failed: {e}")
640
- return ""
 
1
  from fastapi import FastAPI, Request, Query
2
+ from fastapi.responses import JSONResponse, RedirectResponse
 
3
  from fastapi.middleware.cors import CORSMiddleware
 
4
  import requests
5
  from bs4 import BeautifulSoup
6
  import os
7
  import logging
8
  import time
9
+ from typing import Optional
10
 
11
+ # Configuração do app FastAPI
12
+ app = FastAPI(title="StreamFlix Addon", version="1.0.0")
13
 
14
+ # Configuração do logging
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # Middleware CORS
19
  app.add_middleware(
20
  CORSMiddleware,
21
  allow_origins=["*"],
 
24
  allow_headers=["*"],
25
  )
26
 
27
+ # Configurações (usar variáveis de ambiente no Hugging Face)
28
+ BASE_URL = os.getenv("BASE_URL", "https://superflixapi.pw")
29
+ TMDB_API_KEY = os.getenv("TMDB_API_KEY", "84ec49f90ebcffe1e7c61efca1c0a606")
 
 
 
30
  TMDB_API_URL = "https://api.themoviedb.org/3"
31
  USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
32
  REQUEST_TIMEOUT = 15
33
  MAX_RETRIES = 2
34
 
35
+ # Manifesto do Addon (atualizado para Hugging Face)
 
 
 
 
 
 
 
 
 
 
 
36
  MANIFEST = {
37
  "id": "community.streamflix.hf",
38
+ "version": "1.0.0",
39
  "catalogs": [
40
  {
41
  "type": "movie",
42
  "id": "streamflix_movies",
43
  "name": "StreamFlix - Filmes",
44
+ "extra": [{"name": "search", "isRequired": False}]
 
 
 
45
  },
46
  {
47
  "type": "series",
48
  "id": "streamflix_series",
49
  "name": "StreamFlix - Séries",
50
+ "extra": [{"name": "search", "isRequired": False}]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  }
52
  ],
53
  "resources": ["catalog", "stream", "meta"],
54
  "types": ["movie", "series"],
55
+ "name": "StreamFlix (HF)",
56
+ "description": "Addon StreamFlix hospedado no Hugging Face",
57
+ "logo": "https://huggingface.co/spaces/username/streamflix-addon/raw/main/logo.png",
58
+ "background": "https://huggingface.co/spaces/username/streamflix-addon/raw/main/background.jpg",
59
  "idPrefixes": ["tt", "tmdb"],
60
  "behaviorHints": {"adult": False}
61
  }
 
66
  self.session = requests.Session()
67
  self.session.headers.update({"User-Agent": USER_AGENT})
68
  self.cache = {}
69
+ self.cache_timeout = 3600 # 1 hora
70
 
71
  def _request(self, url, method="get", data=None, referer=None, retry=0):
72
  headers = {"User-Agent": USER_AGENT}
 
177
  if not options_data or not options_data.get('data') or not options_data['data'].get('options'):
178
  return None
179
 
180
+ # Find Portuguese option
181
  selected_option = None
182
  for option in options_data['data']['options']:
183
  title = option.get('title', '').lower()
 
223
  if not player_items:
224
  return None
225
 
226
+ # Find Portuguese player
227
  selected_player = None
228
  for player in player_items:
229
  title = player.get_text(strip=True).lower()
 
254
  logger.error(f"Error in movie: {e}", exc_info=True)
255
  return None
256
 
257
+ vod_api = VOD()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
+ def get_tmdb_media(media_type, page=1, search_query=None):
260
+ cache_key = f"tmdb_{media_type}_{page}_{search_query}"
261
+ if cache_key in vod_api.cache:
262
+ return vod_api.cache[cache_key]
 
 
 
263
 
264
+ try:
265
+ if search_query:
266
+ url = f"{TMDB_API_URL}/search/{media_type}"
267
  params = {
268
  "api_key": TMDB_API_KEY,
269
+ "query": search_query,
270
  "page": page,
271
+ "language": "pt-BR"
 
 
272
  }
273
+ else:
274
+ url = f"{TMDB_API_URL}/discover/{media_type}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  params = {
276
  "api_key": TMDB_API_KEY,
 
277
  "sort_by": "popularity.desc",
278
+ "page": page,
279
+ "language": "pt-BR"
 
280
  }
281
+
282
+ response = requests.get(url, params=params, timeout=REQUEST_TIMEOUT)
283
+ response.raise_for_status()
284
+ data = response.json()
285
+ vod_api.cache[cache_key] = data
286
+ return data
287
+ except Exception as e:
288
+ logger.error(f"TMDB request failed: {e}")
289
+ return None
290
+
291
+ def extract_tmdb_id(id_str):
292
+ if id_str.startswith("tmdb:"):
293
+ return id_str.split(":")[1]
294
+ if id_str.isdigit():
295
+ return id_str
296
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
 
298
+ def convert_tmdb_to_imdb(tmdb_id, media_type="movie"):
299
+ cache_key = f"tmdb_to_imdb_{media_type}_{tmdb_id}"
300
+ if cache_key in vod_api.cache:
301
+ return vod_api.cache[cache_key]
302
 
303
+ try:
304
+ url = f"{TMDB_API_URL}/{media_type}/{tmdb_id}/external_ids"
305
+ params = {"api_key": TMDB_API_KEY}
306
 
307
+ response = requests.get(url, params=params, timeout=REQUEST_TIMEOUT)
308
+ response.raise_for_status()
309
+ data = response.json()
310
+ imdb_id = data.get("imdb_id", "")
311
+ vod_api.cache[cache_key] = imdb_id
312
+ return imdb_id
313
+ except Exception as e:
314
+ logger.error(f"TMDB conversion failed: {e}")
315
+ return ""
 
 
 
 
 
 
 
316
 
317
+ # Rotas do FastAPI
318
  @app.get("/")
319
+ async def root():
320
+ return RedirectResponse(url="/manifest.json")
 
321
 
322
  @app.get("/manifest.json")
323
  async def get_manifest():
 
328
  type: str,
329
  id: str,
330
  request: Request,
331
+ skip: int = Query(0),
332
+ search: str = Query(None)
 
333
  ):
334
+ logger.info(f"Catalog request: {type}/{id}?skip={skip}&search={search}")
 
 
 
 
 
335
 
336
  if id not in ["streamflix_movies", "streamflix_series"]:
337
  return JSONResponse({"metas": []})
338
 
339
+ page = (skip // 20) + 1
340
  media_type = "movie" if id == "streamflix_movies" else "tv"
341
+ tmdb_data = get_tmdb_media(media_type, page, search)
342
+
343
+ if not tmdb_data or "results" not in tmdb_data:
344
+ return JSONResponse({"metas": []})
345
 
346
+ metas = []
347
+ for item in tmdb_data["results"]:
348
+ if not item.get("poster_path"):
349
+ continue
350
+
351
+ meta = {
352
+ "id": f"tmdb:{item['id']}",
353
+ "type": media_type,
354
+ "name": item.get("title") if media_type == "movie" else item.get("name"),
355
+ "genres": [genre["name"] for genre in item.get("genres", [])][:3],
356
+ "description": item.get("overview", "Sem descrição disponível")[:300],
357
+ "poster": f"https://image.tmdb.org/t/p/w500{item['poster_path']}",
358
+ "background": f"https://image.tmdb.org/t/p/original{item['backdrop_path']}" if item.get("backdrop_path") else MANIFEST["background"],
359
+ "releaseInfo": item.get("release_date", "")[:4] if media_type == "movie" else item.get("first_air_date", "")[:4],
360
+ "imdbRating": round(item.get("vote_average", 0), 1)
361
+ }
362
+ metas.append(meta)
363
 
364
+ return JSONResponse({"metas": metas})
365
 
366
  @app.get("/meta/{type}/{id}.json")
367
  async def get_meta(type: str, id: str):
368
  try:
369
+ tmdb_id = extract_tmdb_id(id)
370
  media_type = "movie" if type == "movie" else "tv"
371
 
372
+ if not tmdb_id and type == "movie" and id.startswith("tt"):
373
  imdb_id = id
374
+ elif tmdb_id:
375
  imdb_id = convert_tmdb_to_imdb(tmdb_id, media_type) if type == "movie" else ""
376
  else:
377
  raise ValueError("Invalid ID format")
378
 
379
+ if not tmdb_id and type != "movie":
380
  return JSONResponse({
381
  "meta": {
382
  "id": id,
 
391
  params = {
392
  "api_key": TMDB_API_KEY,
393
  "language": "pt-BR",
394
+ "append_to_response": "external_ids"
395
  }
396
 
397
  response = requests.get(url, params=params, timeout=REQUEST_TIMEOUT)
398
  response.raise_for_status()
399
  details = response.json()
400
 
 
 
 
401
  meta = {
402
  "id": id,
403
  "type": type,
404
  "name": details.get("title") if media_type == "movie" else details.get("name"),
405
+ "genres": [genre["name"] for genre in details.get("genres", [])],
406
  "description": details.get("overview", "Sem descrição disponível"),
407
  "poster": f"https://image.tmdb.org/t/p/w500{details['poster_path']}" if details.get("poster_path") else MANIFEST["logo"],
408
  "background": f"https://image.tmdb.org/t/p/original{details['backdrop_path']}" if details.get("backdrop_path") else MANIFEST["background"],
409
  "releaseInfo": details.get("release_date", "")[:4] if media_type == "movie" else details.get("first_air_date", "")[:4],
410
  "imdbRating": round(details.get("vote_average", 0), 1),
411
  "director": ", ".join(
412
+ [crew["name"] for crew in details.get("crew", [])
413
  if crew.get("job") == "Director"
414
  ][:2]) if media_type == "movie" else None
415
  }
 
447
  try:
448
  if type == "movie":
449
  if id.startswith("tmdb:"):
450
+ tmdb_id = extract_tmdb_id(id)
451
  imdb_id = convert_tmdb_to_imdb(tmdb_id, "movie")
452
  if not imdb_id:
453
  logger.warning(f"Failed to convert TMDB to IMDB: {id}")
 
494
  except Exception as e:
495
  logger.error(f"Stream request failed: {e}")
496
 
497
+ return JSONResponse({"streams": []})