Get the content object and its M3U relation
(content_type, content_id, preferred_m3u_account_id=None, preferred_stream_id=None)
| 32 | |
| 33 | |
| 34 | def _get_content_and_relation(content_type, content_id, preferred_m3u_account_id=None, preferred_stream_id=None): |
| 35 | """Get the content object and its M3U relation""" |
| 36 | try: |
| 37 | logger.info(f"[CONTENT-LOOKUP] Looking up {content_type} with UUID {content_id}") |
| 38 | if preferred_m3u_account_id: |
| 39 | logger.info(f"[CONTENT-LOOKUP] Preferred M3U account ID: {preferred_m3u_account_id}") |
| 40 | if preferred_stream_id: |
| 41 | logger.info(f"[CONTENT-LOOKUP] Preferred stream ID: {preferred_stream_id}") |
| 42 | |
| 43 | if content_type == 'movie': |
| 44 | content_obj = Movie.objects.filter(uuid=content_id).first() |
| 45 | if content_obj is None and preferred_stream_id: |
| 46 | # UUIDs are regenerated when process_movie_batch |
| 47 | # (apps/vod/tasks.py) creates duplicate vod_movie records |
| 48 | # during refresh — see #961 / #973. stream_id is stable |
| 49 | # (unique per (m3u_account, stream_id)) so it's a safe |
| 50 | # fallback for previously-cached external player URLs. |
| 51 | # Strictest-match first: prefer the requested account, then |
| 52 | # any active account by priority (matches the existing |
| 53 | # relation-selection ordering below). |
| 54 | rel = None |
| 55 | if preferred_m3u_account_id: |
| 56 | rel = ( |
| 57 | M3UMovieRelation.objects |
| 58 | .filter(stream_id=preferred_stream_id, |
| 59 | m3u_account_id=preferred_m3u_account_id, |
| 60 | m3u_account__is_active=True) |
| 61 | .select_related('movie', 'm3u_account') |
| 62 | .first() |
| 63 | ) |
| 64 | if rel is None: |
| 65 | rel = ( |
| 66 | M3UMovieRelation.objects |
| 67 | .filter(stream_id=preferred_stream_id, |
| 68 | m3u_account__is_active=True) |
| 69 | .select_related('movie', 'm3u_account') |
| 70 | .order_by('-m3u_account__priority', 'id') |
| 71 | .first() |
| 72 | ) |
| 73 | if rel is not None: |
| 74 | content_obj = rel.movie |
| 75 | logger.warning( |
| 76 | f"[STREAMID-FALLBACK] Movie UUID {content_id} not " |
| 77 | f"found; resolved via stream_id " |
| 78 | f"{preferred_stream_id} -> movie uuid " |
| 79 | f"{content_obj.uuid} (provider: " |
| 80 | f"{rel.m3u_account.name})" |
| 81 | ) |
| 82 | if content_obj is None: |
| 83 | raise Http404( |
| 84 | f"Movie not found by uuid {content_id} " |
| 85 | f"or stream_id {preferred_stream_id}" |
| 86 | ) |
| 87 | logger.info(f"[CONTENT-FOUND] Movie: {content_obj.name} (ID: {content_obj.id})") |
| 88 | |
| 89 | # Filter by preferred stream ID first (most specific) |
| 90 | relations_query = content_obj.m3u_relations.filter(m3u_account__is_active=True) |
| 91 | if preferred_stream_id: |