Project

General

Profile

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

Vic K, 2023-04-20 19:23

 
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

    
96
        if self.config_obj.data[self.config_section]['player-stream_type'] == 'm3u8redirect':
97
            self.logger.warning('Stream Type of m3u8redirect not available with this plugin')
98
            return stream_url
99

    
100
        m3u8_uri = self.get_best_stream(stream_url, _channel_id, ch_url)
101
        if not m3u8_uri:
102
            return stream_url
103
        return m3u8_uri
104

    
105
    def get_channel_list(self):
106
        ch_list = []
107
        results = []
108

    
109
        # first get the list of channels to get from the epg plugin
110
        tvg_list = self.get_tvg_reference()
111
        epg_plugins = {u['plugin']: u for u in tvg_list}.keys()
112
        for plugin in epg_plugins:
113
            zones = {u['zone']: u for u in tvg_list if u['plugin'] == plugin}.keys()
114
            for zone in zones:
115
                ch_ids = [n['id'] for n in tvg_list if n['zone'] == zone]
116
                chs = self.plugin_obj.plugins[plugin].plugin_obj \
117
                    .get_channel_list_ext(zone, ch_ids)
118
                if chs is None:
119
                    return
120
                ch_list.extend(chs)
121
                
122
        # get the list of channels from the first URL
123
        uri1 = self.plugin_obj.unc_daddylive_base + self.plugin_obj.unc_daddylive_channels
124
        text1 = self.get_uri_data(uri1).decode()
125
        if text1 is None:
126
            return
127
        text1 = text1.replace('\n', ' ')
128
        match_list1 = re.findall(self.search_ch, text1)
129

    
130
        # get the list of channels from the local file
131
        file_path = './plugins_ext/provider_video_daddylive/resources/list.index'
132
        with open(file_path, 'r') as f:
133
            text2 = f.read()
134
            match_list2 = re.findall(self.search_ch, text2)
135

    
136
        # merge the two lists and remove duplicates
137
        match_list = list(set(match_list1 + match_list2))
138
        # url, id, name
139
        for m in match_list:
140
            if len(m) != 3:
141
                self.logger.warning(
142
                    'get_channel_list - DaddyLive channel extraction failed. Extraction procedure needs updating')
143
                return None
144
            uid = m[1]
145
            name = html.unescape(m[2])
146
            if name.lower().startswith('the '):
147
                name = name[4:]
148
            group = None
149
            ch = [d for d in tvg_list if d['name'] == name]
150
            if len(ch):
151
                tvg_id = ch[0]['id']
152
                ch = [d for d in ch_list if d['id'] == tvg_id]
153

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

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

    
191
                    group = [n.get('group') for n in tvg_list if n['name'] == ch['name']]
192
                    if len(group):
193
                        group = group[0]
194
                    ch['groups_other'] = group
195
                    results.append(ch)
196
                    ch['found'] = True
197
                    continue
198

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

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

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

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

    
251
        return results
252

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