diff --git a/alternative_epg/tvtv/__init__.py b/alternative_epg/tvtv/__init__.py index fddcfec..563ae82 100644 --- a/alternative_epg/tvtv/__init__.py +++ b/alternative_epg/tvtv/__init__.py @@ -63,9 +63,12 @@ class tvtvEPG(): "name": chan_item["channel"]["name"], "number": channel_number, "id": str(chan_item["channel"]["stationID"]), - "thumbnail": "https://cdn.tvpassport.com/image/station/100x100/%s" % chan_item["channel"]["logoFilename"], + "thumbnail": None, "listing": [], } + if chan_item["channel"]["logoFilename"]: + programguide[channel_number]["thumbnail"] = "https://cdn.tvpassport.com/image/station/100x100/%s" % chan_item["channel"]["logoFilename"] + for listing in chan_item["listings"]: timestamp = self.tvtv_timestamps(listing["listDateTime"], listing["duration"]) @@ -74,7 +77,7 @@ class tvtvEPG(): "time_start": timestamp['time_start'], "time_end": timestamp['time_end'], "duration_minutes": listing["duration"], - "thumbnail": "https://cdn.tvpassport.com/image/show/480x720/%s" % listing["artwork"]["poster"], + "thumbnail": None, "title": listing["showName"], "sub-title": listing["episodeTitle"], "description": listing["description"], @@ -88,19 +91,19 @@ class tvtvEPG(): "id": listing["listingID"], } - if not any(d['id'] == clean_prog_dict['id'] for d in programguide[channel_number]["listing"]): + if listing["artwork"]["poster"]: + listing["artwork"]["poster"] = "https://cdn.tvpassport.com/image/show/480x720/%s" % listing["artwork"]["poster"] + + if not any((d['time_start'] == clean_prog_dict['time_start'] and d['id'] == clean_prog_dict['id']) for d in programguide[channel_number]["listing"]): programguide[channel_number]["listing"].append(clean_prog_dict) return programguide def tvtv_timestamps(self, starttime, duration): - start_time = datetime.datetime.strptime(starttime, '%Y-%m-%d %H:%M:%S') - end_time = start_time + datetime.timedelta(minutes=duration) - start_time = start_time.strftime('%Y%m%d%H%M%S +0000') - end_time = end_time.strftime('%Y%m%d%H%M%S +0000') + start_time = datetime.datetime.strptime(starttime, '%Y-%m-%d %H:%M:%S').timestamp() timestamp = { "time_start": start_time, - "time_end": end_time + "time_end": start_time + (duration * 60) } return timestamp @@ -110,11 +113,11 @@ class tvtvEPG(): stoptime = str(datesdict["stop"]) + "T00%3A00%3A00.000Z" url = "https://www.tvtv.us/tvm/t/tv/v4/lineups/%s/listings/grid?start=%s&end=%s" % (self.lineup_id, starttime, stoptime) self.get_cached_item(str(datesdict["start"]), url) - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "offline_cache", "tvtv") or [] - return [self.fhdhr.db.get_cacheitem_value(x, "offline_cache", "tvtv") for x in cache_list] + cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "tvtv") or [] + return [self.fhdhr.db.get_cacheitem_value(x, "epg_cache", "tvtv") for x in cache_list] def get_cached_item(self, cache_key, url): - cacheitem = self.fhdhr.db.get_cacheitem_value(cache_key, "offline_cache", "tvtv") + cacheitem = self.fhdhr.db.get_cacheitem_value(cache_key, "epg_cache", "tvtv") if cacheitem: self.fhdhr.logger.info('FROM CACHE: ' + str(cache_key)) return cacheitem @@ -127,26 +130,26 @@ class tvtvEPG(): return result = resp.json() - self.fhdhr.db.set_cacheitem_value(cache_key, "offline_cache", result, "tvtv") - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "offline_cache", "tvtv") or [] + self.fhdhr.db.set_cacheitem_value(cache_key, "epg_cache", result, "tvtv") + cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "tvtv") or [] cache_list.append(cache_key) - self.fhdhr.db.set_cacheitem_value("cache_list", "offline_cache", cache_list, "tvtv") + self.fhdhr.db.set_cacheitem_value("cache_list", "epg_cache", cache_list, "tvtv") def remove_stale_cache(self, todaydate): - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "offline_cache", "tvtv") or [] + cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "tvtv") or [] cache_to_kill = [] for cacheitem in cache_list: cachedate = datetime.datetime.strptime(str(cacheitem), "%Y-%m-%d") todaysdate = datetime.datetime.strptime(str(todaydate), "%Y-%m-%d") if cachedate < todaysdate: cache_to_kill.append(cacheitem) - self.fhdhr.db.delete_cacheitem_value(cacheitem, "offline_cache", "tvtv") + self.fhdhr.db.delete_cacheitem_value(cacheitem, "epg_cache", "tvtv") self.fhdhr.logger.info('Removing stale cache: ' + str(cacheitem)) - self.fhdhr.db.set_cacheitem_value("cache_list", "offline_cache", [x for x in cache_list if x not in cache_to_kill], "tvtv") + self.fhdhr.db.set_cacheitem_value("cache_list", "epg_cache", [x for x in cache_list if x not in cache_to_kill], "tvtv") def clear_cache(self): - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "offline_cache", "tvtv") or [] + cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "tvtv") or [] for cacheitem in cache_list: - self.fhdhr.db.delete_cacheitem_value(cacheitem, "offline_cache", "tvtv") + self.fhdhr.db.delete_cacheitem_value(cacheitem, "epg_cache", "tvtv") self.fhdhr.logger.info('Removing cache: ' + str(cacheitem)) - self.fhdhr.db.delete_cacheitem_value("cache_list", "offline_cache", "tvtv") + self.fhdhr.db.delete_cacheitem_value("cache_list", "epg_cache", "tvtv") diff --git a/alternative_epg/zap2it/__init__.py b/alternative_epg/zap2it/__init__.py index d6d0b07..c4e0acd 100644 --- a/alternative_epg/zap2it/__init__.py +++ b/alternative_epg/zap2it/__init__.py @@ -63,9 +63,11 @@ class zap2itEPG(): eventdict = xmldictmaker(event, ["startTime", "endTime", "duration", "rating", "flag"], list_items=["filter", "flag"]) progdict = xmldictmaker(event['program'], ["title", "sub-title", "releaseYear", "episodeTitle", "shortDesc", "season", "episode", "id"]) + timestamp = self.zap2it_timestamps(eventdict['startTime'], eventdict['endTime']) + clean_prog_dict = { - "time_start": self.xmltimestamp_zap(eventdict['startTime']), - "time_end": self.xmltimestamp_zap(eventdict['endTime']), + "time_start": timestamp['time_start'], + "time_end": timestamp['time_end'], "duration_minutes": eventdict['duration'], "thumbnail": str("https://zap2it.tmsimg.com/assets/" + str(eventdict['thumbnail']) + ".jpg"), "title": progdict['title'] or "Unavailable", @@ -78,7 +80,7 @@ class zap2itEPG(): "seasonnumber": progdict['season'], "episodenumber": progdict['episode'], "isnew": False, - "id": str(progdict['id'] or self.xmltimestamp_zap(eventdict['startTime'])), + "id": str(progdict['id'] or "%s_%s" % (cdict["channelId"], timestamp['time_start'])), } for f in eventdict['filter']: @@ -92,16 +94,16 @@ class zap2itEPG(): if 'New' in eventdict['flag'] and 'live' not in eventdict['flag']: clean_prog_dict["isnew"] = True - if not any(d['id'] == clean_prog_dict['id'] for d in programguide[str(cdict["channelNo"])]["listing"]): + if not any((d['time_start'] == clean_prog_dict['time_start'] and d['id'] == clean_prog_dict['id']) for d in programguide[str(cdict["channelNo"])]["listing"]): programguide[str(cdict["channelNo"])]["listing"].append(clean_prog_dict) return programguide - def xmltimestamp_zap(self, inputtime): - xmltime = inputtime.replace('Z', '+00:00') - xmltime = datetime.datetime.fromisoformat(xmltime) - xmltime = xmltime.strftime('%Y%m%d%H%M%S %z') - return xmltime + def zap2it_timestamps(self, starttime, endtime): + timestamp = {} + for time_item, time_value in zip(["time_start", "time_end"], [starttime, endtime]): + timestamp[time_item] = datetime.datetime.fromisoformat(time_value.replace('Z', '+00:00')).timestamp() + return timestamp def get_cached(self, i_times): @@ -129,11 +131,11 @@ class zap2itEPG(): url = 'https://tvlistings.zap2it.com/api/grid?' url += urllib.parse.urlencode(parameters) self.get_cached_item(str(i_time), url) - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "offline_cache", "zap2it") or [] - return [self.fhdhr.db.get_cacheitem_value(x, "offline_cache", "zap2it") for x in cache_list] + cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "zap2it") or [] + return [self.fhdhr.db.get_cacheitem_value(x, "epg_cache", "zap2it") for x in cache_list] def get_cached_item(self, cache_key, url): - cacheitem = self.fhdhr.db.get_cacheitem_value(cache_key, "offline_cache", "zap2it") + cacheitem = self.fhdhr.db.get_cacheitem_value(cache_key, "epg_cache", "zap2it") if cacheitem: self.fhdhr.logger.info('FROM CACHE: ' + str(cache_key)) return cacheitem @@ -146,26 +148,26 @@ class zap2itEPG(): return result = resp.json() - self.fhdhr.db.set_cacheitem_value(cache_key, "offline_cache", result, "zap2it") - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "offline_cache", "zap2it") or [] + self.fhdhr.db.set_cacheitem_value(cache_key, "epg_cache", result, "zap2it") + cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "zap2it") or [] cache_list.append(cache_key) - self.fhdhr.db.set_cacheitem_value("cache_list", "offline_cache", cache_list, "zap2it") + self.fhdhr.db.set_cacheitem_value("cache_list", "epg_cache", cache_list, "zap2it") def remove_stale_cache(self, zap_time): - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "offline_cache", "zap2it") or [] + cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "zap2it") or [] cache_to_kill = [] for cacheitem in cache_list: cachedate = int(cacheitem) if cachedate < zap_time: cache_to_kill.append(cacheitem) - self.fhdhr.db.delete_cacheitem_value(cacheitem, "offline_cache", "zap2it") + self.fhdhr.db.delete_cacheitem_value(cacheitem, "epg_cache", "zap2it") self.fhdhr.logger.info('Removing stale cache: ' + str(cacheitem)) - self.fhdhr.db.set_cacheitem_value("cache_list", "offline_cache", [x for x in cache_list if x not in cache_to_kill], "zap2it") + self.fhdhr.db.set_cacheitem_value("cache_list", "epg_cache", [x for x in cache_list if x not in cache_to_kill], "zap2it") def clear_cache(self): - cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "offline_cache", "zap2it") or [] + cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "epg_cache", "zap2it") or [] for cacheitem in cache_list: - self.fhdhr.db.delete_cacheitem_value(cacheitem, "offline_cache", "zap2it") + self.fhdhr.db.delete_cacheitem_value(cacheitem, "epg_cache", "zap2it") self.fhdhr.logger.info('Removing cache: ' + str(cacheitem)) - self.fhdhr.db.delete_cacheitem_value("cache_list", "offline_cache", "zap2it") + self.fhdhr.db.delete_cacheitem_value("cache_list", "epg_cache", "zap2it") diff --git a/data/internal_config/epg.json b/data/internal_config/epg.json index 9ebc677..a77400c 100644 --- a/data/internal_config/epg.json +++ b/data/internal_config/epg.json @@ -19,6 +19,21 @@ "value": "None,blocks", "config_file": false, "config_web": false + }, + "reverse_days": { + "value": -1, + "config_file": false, + "config_web": false + }, + "forward_days": { + "value": 7, + "config_file": false, + "config_web": false + }, + "block_size": { + "value": 1800, + "config_file": false, + "config_web": false } } } diff --git a/fHDHR/device/channels/chan_ident.py b/fHDHR/device/channels/chan_ident.py index ae94d7d..face34c 100644 --- a/fHDHR/device/channels/chan_ident.py +++ b/fHDHR/device/channels/chan_ident.py @@ -31,7 +31,14 @@ class Channel_IDs(): if cnumber: return cnumber - used_numbers = [existing_channel["number"] for existing_channel in existing_channel_info] + used_numbers = [] + for existing_channel in existing_channel_info: + if existing_channel["subnumber"]: + number = "%s.%s" % (existing_channel["number"], existing_channel["subnumber"]) + else: + number = existing_channel["number"] + used_numbers.append(number) + for i in range(1000, 2000): if str(float(i)) not in used_numbers: break diff --git a/fHDHR/device/epg/__init__.py b/fHDHR/device/epg/__init__.py index 80fe33d..dffcc99 100644 --- a/fHDHR/device/epg/__init__.py +++ b/fHDHR/device/epg/__init__.py @@ -1,7 +1,6 @@ import os import time import datetime -from collections import OrderedDict from .blocks import blocksEPG @@ -29,12 +28,8 @@ class EPG(): self.def_method = self.fhdhr.config.dict["epg"]["def_method"] self.sleeptime = {} - for epg_method in self.epg_methods: - if epg_method in list(self.fhdhr.config.dict.keys()): - if "update_frequency" in list(self.fhdhr.config.dict[epg_method].keys()): - self.sleeptime[epg_method] = self.fhdhr.config.dict[epg_method]["update_frequency"] - if epg_method not in list(self.sleeptime.keys()): - self.sleeptime[epg_method] = self.fhdhr.config.dict["epg"]["update_frequency"] + for epg_method in list(self.epg_handling.keys()): + self.sleeptime[epg_method] = self.fhdhr.config.dict["epg"]["update_frequency"] self.epg_update_url = "%s/api/epg?method=update" % (self.fhdhr.api.base) @@ -60,21 +55,29 @@ class EPG(): self.fhdhr.db.delete_fhdhr_value("epg_dict", method) - def whats_on_now(self, channel_number, method=None): - nowtime = datetime.datetime.utcnow() + def whats_on_now(self, channel_number, method=None, chan_obj=None, chan_dict=None): + nowtime = time.time() epgdict = self.get_epg(method) try: listings = epgdict[channel_number]["listing"] except KeyError: listings = [] for listing in listings: - start_time = datetime.datetime.strptime(listing["time_start"], '%Y%m%d%H%M%S +0000') - end_time = datetime.datetime.strptime(listing["time_end"], '%Y%m%d%H%M%S +0000') - if start_time <= nowtime <= end_time: + for time_item in ["time_start", "time_end"]: + time_value = listing[time_item] + if str(time_value).endswith("+00:00"): + listing[time_item] = datetime.datetime.strptime(time_value, '%Y%m%d%H%M%S +00:00').timestamp() + elif str(time_value).endswith("+0000"): + listing[time_item] = datetime.datetime.strptime(time_value, '%Y%m%d%H%M%S +0000').timestamp() + else: + listing[time_item] = int(time_value) + if int(listing["time_start"]) <= nowtime <= int(listing["time_end"]): epgitem = epgdict[channel_number].copy() epgitem["listing"] = [listing] return epgitem - return None + epgitem = epgdict[channel_number].copy() + epgitem["listing"] = [self.blocks.empty_listing(chan_obj=None, chan_dict=None)] + return epgitem def whats_on_allchans(self, method=None): @@ -84,23 +87,26 @@ class EPG(): method not in self.fhdhr.config.dict["epg"]["valid_epg_methods"]): method = "origin" - channel_guide_list = [] + channel_guide_dict = {} epgdict = self.get_epg(method) - if method in ["blocks", "origin", self.fhdhr.config.dict["main"]["dictpopname"]]: - epgdict = epgdict.copy() - for c in list(epgdict.keys()): + epgdict = epgdict.copy() + for c in list(epgdict.keys()): + if method in ["blocks", "origin", self.fhdhr.config.dict["main"]["dictpopname"]]: chan_obj = self.channels.get_channel_obj("origin_id", epgdict[c]["id"]) - epgdict[chan_obj.number] = epgdict.pop(c) - epgdict[chan_obj.number]["name"] = chan_obj.dict["name"] - epgdict[chan_obj.number]["callsign"] = chan_obj.dict["callsign"] - epgdict[chan_obj.number]["number"] = chan_obj.number - epgdict[chan_obj.number]["id"] = chan_obj.dict["origin_id"] - epgdict[chan_obj.number]["thumbnail"] = chan_obj.thumbnail - for channel_number in list(epgdict.keys()): - whatson = self.whats_on_now(channel_number, method) + channel_number = chan_obj.number + epgdict[channel_number] = epgdict.pop(c) + epgdict[channel_number]["name"] = chan_obj.dict["name"] + epgdict[channel_number]["callsign"] = chan_obj.dict["callsign"] + epgdict[channel_number]["number"] = chan_obj.number + epgdict[channel_number]["id"] = chan_obj.dict["origin_id"] + epgdict[channel_number]["thumbnail"] = chan_obj.thumbnail + else: + chan_obj = None + channel_number = c + whatson = self.whats_on_now(channel_number, method, chan_dict=epgdict, chan_obj=chan_obj) if whatson: - channel_guide_list.append(whatson) - return channel_guide_list + channel_guide_dict[channel_number] = whatson + return channel_guide_dict def get_epg(self, method=None): @@ -176,11 +182,109 @@ class EPG(): else: programguide = self.epg_handling[method].update_epg() - programguide = OrderedDict(sorted(programguide.items())) + # Sort the channels + clean_prog_guide = {} + sorted_chan_list = sorted(list(programguide.keys())) + for cnum in sorted_chan_list: + if cnum not in list(clean_prog_guide.keys()): + clean_prog_guide[cnum] = programguide[cnum].copy() + programguide = clean_prog_guide.copy() - for cnum in programguide: + # sort the channel listings by time stamp + for cnum in list(programguide.keys()): programguide[cnum]["listing"] = sorted(programguide[cnum]["listing"], key=lambda i: i['time_start']) + # Gernate Block periods for between EPG data, if missing + clean_prog_guide = {} + desired_start_time = (datetime.datetime.today() + datetime.timedelta(days=self.fhdhr.config.dict["epg"]["reverse_days"])).timestamp() + desired_end_time = (datetime.datetime.today() + datetime.timedelta(days=self.fhdhr.config.dict["epg"]["forward_days"])).timestamp() + for cnum in list(programguide.keys()): + + if cnum not in list(clean_prog_guide.keys()): + clean_prog_guide[cnum] = programguide[cnum].copy() + clean_prog_guide[cnum]["listing"] = [] + + if method in ["blocks", "origin", self.fhdhr.config.dict["main"]["dictpopname"]]: + chan_obj = self.channels.get_channel_obj("origin_id", programguide[cnum]["id"]) + else: + chan_obj = None + + # Generate Blocks for Channels containing No Lisiings + if not len(programguide[cnum]["listing"]): + timestamps = self.blocks.timestamps_between(desired_start_time, desired_end_time) + clean_prog_dicts = self.blocks.empty_channel_epg(timestamps, chan_dict=programguide[cnum], chan_obj=chan_obj) + clean_prog_guide[cnum]["listing"].extend(clean_prog_dicts) + + else: + + # Clean Timetamps from old xmltv method to timestamps + progindex = 0 + for program_item in programguide[cnum]["listing"]: + for time_item in ["time_start", "time_end"]: + time_value = programguide[cnum]["listing"][progindex][time_item] + if str(time_value).endswith("+00:00"): + programguide[cnum]["listing"][progindex][time_item] = datetime.datetime.strptime(time_value, '%Y%m%d%H%M%S +00:00').timestamp() + elif str(time_value).endswith("+0000"): + programguide[cnum]["listing"][progindex][time_item] = datetime.datetime.strptime(time_value, '%Y%m%d%H%M%S +0000').timestamp() + else: + programguide[cnum]["listing"][progindex][time_item] = int(time_value) + progindex += 1 + + # Generate time before the listing actually starts + first_prog_time = programguide[cnum]["listing"][0]['time_start'] + if desired_start_time < first_prog_time: + timestamps = self.blocks.timestamps_between(desired_start_time, first_prog_time) + clean_prog_dicts = self.blocks.empty_channel_epg(timestamps, chan_dict=programguide[cnum], chan_obj=chan_obj) + clean_prog_guide[cnum]["listing"].extend(clean_prog_dicts) + + # Generate time blocks between events if chunks of time are missing + progindex = 0 + for program_item in programguide[cnum]["listing"]: + try: + nextprog_dict = programguide[cnum]["listing"][progindex + 1] + except IndexError: + nextprog_dict = None + if not nextprog_dict: + clean_prog_guide[cnum]["listing"].append(program_item) + else: + if nextprog_dict['time_start'] > program_item['time_end']: + timestamps = self.blocks.timestamps_between(program_item['time_end'], nextprog_dict['time_start']) + clean_prog_dicts = self.blocks.empty_channel_epg(timestamps, chan_dict=programguide[cnum], chan_obj=chan_obj) + clean_prog_guide[cnum]["listing"].extend(clean_prog_dicts) + else: + clean_prog_guide[cnum]["listing"].append(program_item) + progindex += 1 + + # Generate time after the listing actually ends + end_prog_time = programguide[cnum]["listing"][progindex]['time_end'] + if desired_end_time > end_prog_time: + timestamps = self.blocks.timestamps_between(end_prog_time, desired_end_time) + clean_prog_dicts = self.blocks.empty_channel_epg(timestamps, chan_dict=programguide[cnum], chan_obj=chan_obj) + clean_prog_guide[cnum]["listing"].extend(clean_prog_dicts) + + programguide = clean_prog_guide.copy() + + # if a stock method, generate Blocks EPG for missing channels + if method in ["blocks", "origin", self.fhdhr.config.dict["main"]["dictpopname"]]: + timestamps = self.blocks.timestamps + for fhdhr_id in list(self.channels.list.keys()): + chan_obj = self.channels.list[fhdhr_id] + if str(chan_obj.number) not in list(programguide.keys()): + programguide[str(chan_obj.number)] = chan_obj.epgdict + clean_prog_dicts = self.blocks.empty_channel_epg(timestamps, chan_obj=chan_obj) + programguide[str(chan_obj.number)]["listing"].extend(clean_prog_dicts) + + # Make Thumbnails for missing thumbnails + for cnum in list(programguide.keys()): + if not programguide[cnum]["thumbnail"]: + programguide[cnum]["thumbnail"] = "/api/images?method=generate&type=channel&message=%s" % programguide[cnum]["number"] + programguide[cnum]["listing"] = sorted(programguide[cnum]["listing"], key=lambda i: i['time_start']) + prog_index = 0 + for program_item in programguide[cnum]["listing"]: + if not programguide[cnum]["listing"][prog_index]["thumbnail"]: + programguide[cnum]["listing"][prog_index]["thumbnail"] = programguide[cnum]["thumbnail"] + prog_index += 1 + self.epgdict = programguide self.fhdhr.db.set_fhdhr_value("epg_dict", method, programguide) self.fhdhr.db.set_fhdhr_value("update_time", method, time.time()) @@ -192,7 +296,10 @@ class EPG(): try: while True: for epg_method in self.epg_methods: - if time.time() >= (self.fhdhr.db.get_fhdhr_value("update_time", epg_method) + self.sleeptime[epg_method]): + last_update_time = self.fhdhr.db.get_fhdhr_value("update_time", epg_method) + if not last_update_time: + self.fhdhr.web.session.get(self.epg_update_url) + elif time.time() >= (last_update_time + self.sleeptime[epg_method]): self.fhdhr.web.session.get(self.epg_update_url) time.sleep(360) except KeyboardInterrupt: diff --git a/fHDHR/device/epg/blocks.py b/fHDHR/device/epg/blocks.py index cec6a74..a0f5e93 100644 --- a/fHDHR/device/epg/blocks.py +++ b/fHDHR/device/epg/blocks.py @@ -19,56 +19,99 @@ class blocksEPG(): if str(chan_obj.number) not in list(programguide.keys()): programguide[str(chan_obj.number)] = chan_obj.epgdict - clean_prog_dicts = self.empty_channel_epg(timestamps, chan_obj) + clean_prog_dicts = self.empty_channel_epg(timestamps, chan_obj=chan_obj) for clean_prog_dict in clean_prog_dicts: programguide[str(chan_obj.number)]["listing"].append(clean_prog_dict) return programguide - def get_content_thumbnail(self, content_id): - return "/api/images?method=generate&type=content&message=%s" % content_id - @property def timestamps(self): - timestamps = [] - todaydate = datetime.date.today() - for x in range(0, 6): - xdate = todaydate + datetime.timedelta(days=x) - xtdate = xdate + datetime.timedelta(days=1) + desired_start_time = (datetime.datetime.today() + datetime.timedelta(days=self.fhdhr.config.dict["epg"]["reverse_days"])).timestamp() + desired_end_time = (datetime.datetime.today() + datetime.timedelta(days=self.fhdhr.config.dict["epg"]["forward_days"])).timestamp() + return self.timestamps_between(desired_start_time, desired_end_time) - for hour in range(0, 24): - time_start = datetime.datetime.combine(xdate, datetime.time(hour, 0)) - if hour + 1 < 24: - time_end = datetime.datetime.combine(xdate, datetime.time(hour + 1, 0)) - else: - time_end = datetime.datetime.combine(xtdate, datetime.time(0, 0)) - timestampdict = { - "time_start": str(time_start.strftime('%Y%m%d%H%M%S')) + " +0000", - "time_end": str(time_end.strftime('%Y%m%d%H%M%S')) + " +0000", - } - timestamps.append(timestampdict) + def timestamps_between(self, starttime, endtime): + timestamps = [] + desired_blocksize = self.fhdhr.config.dict["epg"]["block_size"] + current_time = starttime + while (current_time + desired_blocksize) <= endtime: + timestampdict = { + "time_start": current_time, + "time_end": current_time + desired_blocksize, + } + timestamps.append(timestampdict) + current_time += desired_blocksize + if current_time < endtime: + timestampdict = { + "time_start": current_time, + "time_end": endtime + } + timestamps.append(timestampdict) return timestamps - def empty_channel_epg(self, timestamps, chan_obj): + def single_channel_epg(self, timestampdict, chan_obj=None, chan_dict=None): + + if chan_obj: + content_id = "%s_%s" % (chan_obj.dict["origin_id"], timestampdict['time_start']) + elif chan_dict: + content_id = "%s_%s" % (chan_dict["id"], timestampdict['time_start']) + + clean_prog_dict = { + "time_start": timestampdict['time_start'], + "time_end": timestampdict['time_end'], + "duration_minutes": (timestampdict['time_end'] - timestampdict['time_start']) / 60, + "title": "Unavailable", + "sub-title": "Unavailable", + "description": "Unavailable", + "rating": "N/A", + "episodetitle": None, + "releaseyear": None, + "genres": [], + "seasonnumber": None, + "episodenumber": None, + "isnew": False, + "id": content_id, + } + if chan_obj: + clean_prog_dict["thumbnail"] = chan_obj.thumbnail + elif chan_dict: + clean_prog_dict["thumbnail"] = chan_dict["thumbnail"] + if not clean_prog_dict["thumbnail"]: + clean_prog_dict["thumbnail"] = "/api/images?method=generate&type=content&message=%s" % content_id + + return clean_prog_dict + + def empty_channel_epg(self, timestamps, chan_obj=None, chan_dict=None): clean_prog_dicts = [] - for timestamp in timestamps: - content_id = "%s_%s" % (chan_obj.dict["origin_id"], str(timestamp['time_start']).split(" ")[0]) - clean_prog_dict = { - "time_start": timestamp['time_start'], - "time_end": timestamp['time_end'], - "duration_minutes": 60, - "thumbnail": chan_obj.dict["thumbnail"] or self.get_content_thumbnail(content_id), - "title": "Unavailable", - "sub-title": "Unavailable", - "description": "Unavailable", - "rating": "N/A", - "episodetitle": None, - "releaseyear": None, - "genres": [], - "seasonnumber": None, - "episodenumber": None, - "isnew": False, - "id": content_id, - } + for timestampdict in timestamps: + clean_prog_dict = self.single_channel_epg(timestampdict, chan_obj=chan_obj, chan_dict=chan_dict) clean_prog_dicts.append(clean_prog_dict) return clean_prog_dicts + + def empty_listing(self, chan_obj=None, chan_dict=None): + clean_prog_dict = { + "time_start": None, + "time_end": None, + "duration_minutes": None, + "title": "Unavailable", + "sub-title": "Unavailable", + "description": "Unavailable", + "rating": "N/A", + "episodetitle": None, + "releaseyear": None, + "genres": [], + "seasonnumber": None, + "episodenumber": None, + "isnew": False, + "id": "Unavailable", + } + + if chan_obj: + clean_prog_dict["thumbnail"] = chan_obj.thumbnail + elif chan_dict: + clean_prog_dict["thumbnail"] = chan_dict["thumbnail"] + if not clean_prog_dict["thumbnail"]: + clean_prog_dict["thumbnail"] = "/api/images?method=generate&type=content&message=Unavailable" + + return clean_prog_dict diff --git a/fHDHR_web/api/xmltv.py b/fHDHR_web/api/xmltv.py index bcb8e97..bdb42f7 100644 --- a/fHDHR_web/api/xmltv.py +++ b/fHDHR_web/api/xmltv.py @@ -2,6 +2,7 @@ from flask import Response, request, redirect import xml.etree.ElementTree from io import BytesIO import urllib.parse +import datetime from fHDHR.tools import sub_el @@ -90,6 +91,18 @@ class xmlTV(): """This method is called when creation of a full xmltv is not possible""" return self.xmltv_file(self.xmltv_headers()) + def timestamp_to_datetime(self, time_start, time_end): + xmltvtimetamps = {} + + for time_item, time_value in zip(["time_start", "time_end"], [time_start, time_end]): + + if str(time_value).endswith(tuple(["+0000", "+00:00"])): + xmltvtimetamps[time_item] = str(time_value) + else: + xmltvtimetamps[time_item] = str(datetime.datetime.fromtimestamp(time_value)) + " +0000" + + return xmltvtimetamps + def create_xmltv(self, base_url, epgdict, source): if not epgdict: return self.xmltv_empty() @@ -129,9 +142,11 @@ class xmlTV(): for program in channel_listing: + xmltvtimetamps = self.timestamp_to_datetime(program['time_start'], program['time_end']) + prog_out = sub_el(out, 'programme', - start=program['time_start'], - stop=program['time_end'], + start=xmltvtimetamps['time_start'], + stop=xmltvtimetamps['time_end'], channel=str(channelnum)) sub_el(prog_out, 'title', lang='en', text=program['title']) diff --git a/fHDHR_web/pages/channels_editor.py b/fHDHR_web/pages/channels_editor.py index bd455ed..2628843 100644 --- a/fHDHR_web/pages/channels_editor.py +++ b/fHDHR_web/pages/channels_editor.py @@ -24,4 +24,6 @@ class Channels_Editor_HTML(): channelslist.append(channel_dict) + channelslist = sorted(channelslist, key=lambda i: i['number']) + return render_template('channels_editor.html', request=request, fhdhr=self.fhdhr, channelslist=channelslist) diff --git a/fHDHR_web/pages/channels_html.py b/fHDHR_web/pages/channels_html.py index 2478418..ef82b72 100644 --- a/fHDHR_web/pages/channels_html.py +++ b/fHDHR_web/pages/channels_html.py @@ -31,4 +31,6 @@ class Channels_HTML(): if channel_dict["enabled"]: channels_dict["Enabled"] += 1 + channelslist = sorted(channelslist, key=lambda i: i['number']) + return render_template('channels.html', request=request, fhdhr=self.fhdhr, channelslist=channelslist, channels_dict=channels_dict, list=list) diff --git a/fHDHR_web/pages/guide_html.py b/fHDHR_web/pages/guide_html.py index 12d9408..d863a2d 100644 --- a/fHDHR_web/pages/guide_html.py +++ b/fHDHR_web/pages/guide_html.py @@ -14,9 +14,28 @@ class Guide_HTML(): def __call__(self, *args): return self.get(*args) + def number_sort(self, chan_dict): + for channel in list(chan_dict.keys()): + number = chan_dict[channel]["number"] + chan_dict[channel]["number"] = number.split(".")[0] + try: + chan_dict[channel]["subnumber"] = number.split(".")[1] + except IndexError: + chan_dict[channel]["subnumber"] = None + sorted_chan_list = sorted(list(chan_dict.keys()), key=lambda i: (int(chan_dict[i]['number']), int(chan_dict[i]['subnumber'] or 0))) + sorted_chan_dict = {} + for cnum in sorted_chan_list: + if cnum not in list(sorted_chan_dict.keys()): + sorted_chan_dict[cnum] = chan_dict[cnum].copy() + if sorted_chan_dict[cnum]["subnumber"]: + sorted_chan_dict[cnum]["number"] = "%s.%s" % (sorted_chan_dict[cnum]["number"], sorted_chan_dict[cnum]["subnumber"]) + del sorted_chan_dict[cnum]["subnumber"] + chan_dict = sorted_chan_dict.copy() + return chan_dict + def get(self, *args): - nowtime = datetime.datetime.utcnow() + nowtime = datetime.datetime.utcnow().timestamp() chan_guide_list = [] @@ -25,21 +44,38 @@ class Guide_HTML(): if source not in epg_methods: source = self.fhdhr.device.epg.def_method - for channel in self.fhdhr.device.epg.whats_on_allchans(source): - end_time = datetime.datetime.strptime(channel["listing"][0]["time_end"], '%Y%m%d%H%M%S +0000') - remaining_time = humanized_time(int((end_time - nowtime).total_seconds())) + whatson = self.fhdhr.device.epg.whats_on_allchans(source) + + # Sort the channels + sorted_chan_guide = self.number_sort(whatson) + + for channel in list(sorted_chan_guide.keys()): + if sorted_chan_guide[channel]["listing"][0]["time_end"]: + remaining_time = humanized_time(sorted_chan_guide[channel]["listing"][0]["time_end"] - nowtime) + else: + remaining_time = "N/A" chan_dict = { - "name": channel["name"], - "number": channel["number"], - "chan_thumbnail": channel["thumbnail"], - "listing_title": channel["listing"][0]["title"], - "listing_thumbnail": channel["listing"][0]["thumbnail"], - "listing_description": channel["listing"][0]["description"], - "remaining_time": str(remaining_time) + "name": sorted_chan_guide[channel]["name"], + "number": sorted_chan_guide[channel]["number"], + "chan_thumbnail": sorted_chan_guide[channel]["thumbnail"], + "listing_title": sorted_chan_guide[channel]["listing"][0]["title"], + "listing_thumbnail": sorted_chan_guide[channel]["listing"][0]["thumbnail"], + "listing_description": sorted_chan_guide[channel]["listing"][0]["description"], + "listing_remaining_time": str(remaining_time) } + + for time_item in ["time_start", "time_end"]: + + if not sorted_chan_guide[channel]["listing"][0][time_item]: + chan_dict["listing_%s" % time_item] = "N/A" + elif str(sorted_chan_guide[channel]["listing"][0][time_item]).endswith(tuple(["+0000", "+00:00"])): + chan_dict["listing_%s" % time_item] = str(sorted_chan_guide[channel]["listing"][0][time_item]) + else: + chan_dict["listing_%s" % time_item] = str(datetime.datetime.fromtimestamp(sorted_chan_guide[channel]["listing"][0][time_item])) + if source in ["blocks", "origin", self.fhdhr.config.dict["main"]["dictpopname"]]: - chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", channel["id"]) + chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", sorted_chan_guide[channel]["id"]) chan_dict["name"] = chan_obj.dict["name"] chan_dict["number"] = chan_obj.number @@ -47,6 +83,13 @@ class Guide_HTML(): chan_dict["enabled"] = chan_obj.dict["enabled"] chan_dict["play_url"] = chan_obj.play_url + chan_dict["listing_thumbnail"] = chan_dict["listing_thumbnail"] or chan_obj.thumbnail + else: + if not chan_dict["listing_thumbnail"]: + chan_dict["listing_thumbnail"] = chan_dict["chan_thumbnail"] + if not chan_dict["listing_thumbnail"]: + chan_dict["listing_thumbnail"] = "/api/images?method=generate&type=channel&message=%s" % chan_dict["number"] + chan_guide_list.append(chan_dict) return render_template('guide.html', request=request, fhdhr=self.fhdhr, chan_guide_list=chan_guide_list, epg_methods=epg_methods, source=source) diff --git a/fHDHR_web/templates/guide.html b/fHDHR_web/templates/guide.html index 64c0d77..b0ec93c 100644 --- a/fHDHR_web/templates/guide.html +++ b/fHDHR_web/templates/guide.html @@ -21,6 +21,8 @@ Content Title Content Thumbnail Content Description + Start Time (UTC) + End Time (UTC) Content Remaining Time @@ -39,7 +41,9 @@ {{ chan_dict["listing_title"] }} {{ chan_dict[ {{ chan_dict["listing_description"] }} - {{ chan_dict["remaining_time"] }} + {{ chan_dict["listing_time_start"] }} + {{ chan_dict["listing_time_end"] }} + {{ chan_dict["listing_remaining_time"] }} {% endfor %} diff --git a/origin/origin_epg.py b/origin/origin_epg.py index 5e286a0..32a16cb 100644 --- a/origin/origin_epg.py +++ b/origin/origin_epg.py @@ -1,4 +1,3 @@ -import datetime import xmltodict import fHDHR.tools @@ -19,11 +18,6 @@ class OriginEPG(): )) return item_thumb_url - def xmltimestamp_nextpvr(self, epochtime): - xmltime = datetime.datetime.fromtimestamp(int(epochtime)/1000) - xmltime = str(xmltime.strftime('%Y%m%d%H%M%S')) + " +0000" - return xmltime - def duration_nextpvr_minutes(self, starttime, endtime): return ((int(endtime) - int(starttime))/1000/60) @@ -33,9 +27,9 @@ class OriginEPG(): for fhdhr_id in list(fhdhr_channels.list.keys()): chan_obj = fhdhr_channels.list[fhdhr_id] - if str(chan_obj.dict['number']) not in list(programguide.keys()): + if str(chan_obj.number) not in list(programguide.keys()): - programguide[str(chan_obj.dict["number"])] = chan_obj.epgdict + programguide[str(chan_obj.number)] = chan_obj.epgdict epg_url = ('%s%s:%s/service?method=channel.listings&channel_id=%s' % ("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://", @@ -53,8 +47,8 @@ class OriginEPG(): progdict = fHDHR.tools.xmldictmaker(program_item, ["start", "end", "title", "name", "subtitle", "rating", "description", "season", "episode", "id", "episodeTitle"]) clean_prog_dict = { - "time_start": self.xmltimestamp_nextpvr(progdict["start"]), - "time_end": self.xmltimestamp_nextpvr(progdict["end"]), + "time_start": (int(progdict["start"]) / 1000), + "time_end": (int(progdict["end"]) / 1000), "duration_minutes": self.duration_nextpvr_minutes(progdict["start"], progdict["end"]), "thumbnail": self.get_content_thumbnail(progdict['id']), "title": progdict['name'] or "Unavailable", @@ -67,7 +61,7 @@ class OriginEPG(): "seasonnumber": progdict['season'], "episodenumber": progdict['episode'], "isnew": False, - "id": str(progdict['id'] or self.xmltimestamp_nextpvr(progdict["start"])), + "id": str(progdict['id'] or "%s_%s" % (chan_obj.dict['origin_id'], progdict["start"])), } if 'genre' in list(progdict.keys()): @@ -80,7 +74,7 @@ class OriginEPG(): # TODO isNEW - if not any(d['id'] == clean_prog_dict['id'] for d in programguide[str(chan_obj.dict["number"])]["listing"]): - programguide[str(chan_obj.dict["number"])]["listing"].append(clean_prog_dict) + if not any((d['time_start'] == clean_prog_dict['time_start'] and d['id'] == clean_prog_dict['id']) for d in programguide[chan_obj.number]["listing"]): + programguide[str(chan_obj.number)]["listing"].append(clean_prog_dict) return programguide