Project

General

Profile

RE: DaddyLive, PlutoTV, XUMO, M3U/XMLTV, SamsungTV, Plex,... » channels.py

Vic K, 2023-03-31 21:29

 
1
"""
2
MIT License
3

    
4
Copyright (C) 2023 ROCKY4546
5
https://github.com/rocky4546
6

    
7
This file is part of Cabernet
8

    
9
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
10
and associated documentation files (the "Software"), to deal in the Software without restriction,
11
including without limitation the rights to use, copy, modify, merge, publish, distribute,
12
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
13
is furnished to do so, subject to the following conditions:
14

    
15
The above copyright notice and this permission notice shall be included in all copies or
16
substantial portions of the Software.
17
"""
18

    
19
import html
20
import importlib
21
import importlib.resources
22
import json
23
import re
24

    
25
from lib.plugins.plugin_channels import PluginChannels
26
from lib.common.decorators import handle_json_except
27
from lib.common.decorators import handle_url_except
28
import lib.common.utils as utils
29
from ..lib import daddylive
30
from .. import resources
31

    
32
class Channels(PluginChannels):
33

    
34
    def __init__(self, _instance_obj):
35
        super().__init__(_instance_obj)
36

    
37
        self.search_url = re.compile(b'iframe src=\"(.*?)\" width')
38
        self.search_m3u8 = re.compile(b'source:\'(.*?)\'')
39
        self.search_ch = re.compile(r'div class="grid-item">'
40
                                    + r'<a href=\"(\D+(\d+).php.*?)\" target.*?<strong>(.*?)</strong>')
41
        self.ch_db_list = None
42

    
43
    def get_channels(self):
44
        self.ch_db_list = self.db.get_channels(self.plugin_obj.name, self.instance_key)
45

    
46
        ch_list = self.get_channel_list()
47
        if len(ch_list) == 0:
48
            self.logger.warning('DaddyLive channel list is empty from provider, not updating Cabernet')
49
            return
50
        self.logger.info("{}: Found {} stations on instance {}"
51
                         .format(self.plugin_obj.name, len(ch_list), self.instance_key))
52
        ch_list = sorted(ch_list, key=lambda d: d['name'])
53
        ch_num = 1
54
        for ch in ch_list:
55
            ch['number'] = ch_num
56
            ch_num += 1
57
        return ch_list
58

    
59
    @handle_url_except(timeout=10.0)
60
    @handle_json_except
61
    def get_channel_ref(self, _channel_id):
62
        """
63
        gets the referer required to obtain the ts or stream files from server
64
        """
65
        text = self.get_uri_data(self.plugin_obj.unc_daddylive_base +
66
                                 self.plugin_obj.unc_daddylive_stream.format(_channel_id))
67
        m = re.search(self.search_url, text)
68
        if not m:
69
            # unable to obtain the url, abort
70
            self.logger.info('{}: {} Unable to obtain url, aborting'
71
                             .format(self.plugin_obj.name, _channel_id))
72
            return
73
        return m[1].decode('utf8')
74

    
75
    @handle_url_except(timeout=10.0)
76
    @handle_json_except
77
    def get_channel_uri(self, _channel_id):
78
        json_needs_updating = False
79
        ch_url = self.get_channel_ref(_channel_id)
80
        if not ch_url:
81
            return
82

    
83
        header = {
84
            'User-agent': utils.DEFAULT_USER_AGENT,
85
            'Referer': self.plugin_obj.unc_daddylive_base + self.plugin_obj.unc_daddylive_stream.format(_channel_id)}
86

    
87
        text = self.get_uri_data(ch_url, _header=header)
88
        m = re.search(self.search_m3u8, text)
89
        if not m:
90
            # unable to obtain the url, abort
91
            self.logger.notice('{}: {} Unable to obtain m3u8, aborting'
92
                               .format(self.plugin_obj.name, _channel_id))
93
            return
94
        stream_url = m[1].decode('utf8')
95
        m3u8_uri = self.get_best_stream(stream_url, _channel_id, ch_url)
96
        if not m3u8_uri:
97
            return stream_url
98
        return m3u8_uri
99

    
100
    def get_channel_list(self):
101
        ch_list = []
102
        results = []
103

    
104
        # first get the list of channels to get from the epg plugin
105
        tvg_list = self.get_tvg_reference()
106
        epg_plugins = {u['plugin']: u for u in tvg_list}.keys()
107
        for plugin in epg_plugins:
108
            zones = {u['zone']: u for u in tvg_list if u['plugin'] == plugin}.keys()
109
            for zone in zones:
110
                ch_ids = [n['id'] for n in tvg_list if n['zone'] == zone]
111
                chs = self.plugin_obj.plugins[plugin].plugin_obj \
112
                    .get_channel_list_ext(zone, ch_ids)
113
                if chs is None:
114
                    return
115
                ch_list.extend(chs)
116

    
117
        # get the list of channels from the first URL
118
        uri1 = self.plugin_obj.unc_daddylive_base + self.plugin_obj.unc_daddylive_channels
119
        text1 = self.get_uri_data(uri1).decode()
120
        if text1 is None:
121
            return
122
        text1 = text1.replace('\n', ' ')
123
        match_list1 = re.findall(self.search_ch, text1)
124

    
125
        # get the list of channels from the local file
126
        file_path = './plugins/provider_video_daddylive/resources/list.index'
127
        with open(file_path, 'r') as f:
128
            text2 = f.read()
129
            match_list2 = re.findall(self.search_ch, text2)
130

    
131
        # merge the two lists and remove duplicates
132
        match_list = list(set(match_list1 + match_list2))
133

    
134
        # url, id, name
135
        for m in match_list:
136
            if len(m) != 3:
137
                self.logger.warning(
138
                    'get_channel_list - DaddyLive channel extraction failed. Extraction procedure needs updating')
139
                return None
140
            uid = m[1]
141
            name = html.unescape(m[2])
142
            if name.lower().startswith('the '):
143
                name = name[4:]
144
            group = None
145
            ch = [d for d in tvg_list if d['name'] == name]
146
            if len(ch):
147
                tvg_id = ch[0]['id']
148
                ch = [d for d in ch_list if d['id'] == tvg_id]
149

    
150
                if len(ch):
151
                    ch = ch[0]
152
                    ch_db_data = self.ch_db_list.get(uid)
153
                    if ch_db_data is not None:
154
                        ch['enabled'] = ch_db_data[0]['enabled']
155
                        ch['id'] = ch_db_data[0]['uid']
156
                        ch['name'] = ch_db_data[0]['json']['name']
157
                        ch['HD'] = ch_db_data[0]['json']['HD']
158
                        if ch_db_data[0]['json']['thumbnail'] == ch['thumbnail']:
159
                            thumb = ch_db_data[0]['json']['thumbnail']
160
                            thumb_size = ch_db_data[0]['json']['thumbnail_size']
161
                        else:
162
                            thumb = ch['thumbnail']
163
                            thumb_size = self.get_thumbnail_size(thumb, uid)
164
                        ch['thumbnail'] = thumb
165
                        ch['thumbnail_size'] = thumb_size
166
                        ch['ref_url'] = ch_db_data[0]['json']['ref_url']
167
                        ch['Header'] = ch_db_data[0]['json']['Header']
168
                        ch['use_date_on_m3u8_key'] = False
169
                    else:
170
                        ch['id'] = uid
171
                        ch['name'] = name
172
                        ch['thumbnail_size'] = self.get_thumbnail_size(ch['thumbnail'], uid)
173

    
174
                        ref_url = self.get_channel_ref(uid)
175
                        if not ref_url:
176
                            self.logger.notice('{} BAD CHANNEL found {}:{}'
177
                                               .format(self.plugin_obj.name, uid, name))
178
                            header = None
179
                        else:
180
                            header = {'User-agent': utils.DEFAULT_USER_AGENT,
181
                                      'Referer': ref_url}
182
                        ch['Header'] = header
183
                        ch['ref_url'] = ref_url
184
                        ch['use_date_on_m3u8_key'] = False
185
                        self.logger.debug('{} New Channel Added {}:{}'.format(self.plugin_obj.name, uid, name))
186

    
187
                    group = [n.get('group') for n in tvg_list if n['name'] == ch['name']]
188
                    if len(group):
189
                        group = group[0]
190
                    ch['groups_other'] = group
191
                    results.append(ch)
192
                    ch['found'] = True
193
                    continue
194

    
195
            ch_db_data = self.ch_db_list.get(uid)
196
            if ch_db_data is not None:
197
                enabled = ch_db_data[0]['enabled']
198
                hd = ch_db_data[0]['json']['HD']
199
                thumb = ch_db_data[0]['json']['thumbnail']
200
                thumb_size = ch_db_data[0]['json']['thumbnail_size']
201
                ref_url = self.get_channel_ref(uid)
202
                self.logger.debug('{} Updating Channel {}:{}'.format(self.plugin_obj.name, uid, name))
203
            else:
204
                self.logger.debug('{} New Channel Added {}:{}'.format(self.plugin_obj.name, uid, name))
205
                enabled = True
206
                hd = 0
207
                thumb = None
208
                thumb_size = None
209
                ref_url = self.get_channel_ref(uid)
210

    
211
            if not ref_url:
212
                self.logger.notice('{} BAD CHANNEL found {}:{}'
213
                                   .format(self.plugin_obj.name, uid, name))
214
                header = None
215
            else:
216
                header = {'User-agent': utils.DEFAULT_USER_AGENT,
217
                          'Referer': ref_url}
218

    
219
            channel = {
220
                'id': uid,
221
                'enabled': enabled,
222
                'callsign': uid,
223
                'number': 0,
224
                'name': name,
225
                'HD': hd,
226
                'group_hdtv': None,
227
                'group_sdtv': None,
228
                'groups_other': None,
229
                'thumbnail': thumb,
230
                'thumbnail_size': thumb_size,
231
                'VOD': False,
232
                'Header': header,
233
                'ref_url': ref_url,
234
                'use_date_on_m3u8_key': False,
235
            }
236
            results.append(channel)
237

    
238
        found_tvg_list = [u for u in ch_list if u.get('found') is None]
239
        for ch in found_tvg_list:
240
            self.logger.warning(
241
                '{} Channel {} {} from channel_list.json not found on providers site'.format(self.plugin_obj.name,
242
                                                                                             ch['id'], ch['name']))
243
        found_tvg_list = [u for u in ch_list if u.get('found') is not None]
244
        for ch in found_tvg_list:
245
            del ch['found']
246

    
247
        return results
248

    
249
    def get_tvg_reference(self):
250
        """
251
        Returns a list of channels with zone and channel id info
252
        This is sorted so we can get all channels from each zone together
253
        """
254
        if self.config_obj.data[self.plugin_obj.name.lower()]['epg-plugin'] == 'ALL':
255
            ch_list = json.loads(
256
                importlib.resources.read_text(resources, resource='channel_list.json'))
257
            ch_list = sorted(ch_list, key=lambda d: d['zone'])
258
            return ch_list
259
        else:
260
            return []
(1-1/3)