Skip to content

Commit

Permalink
[weather.ozweather] 2.1.2
Browse files Browse the repository at this point in the history
  • Loading branch information
bossanova808 committed Oct 16, 2024
1 parent afe0a48 commit 81aaad4
Show file tree
Hide file tree
Showing 14 changed files with 251 additions and 434 deletions.
10 changes: 6 additions & 4 deletions weather.ozweather/addon.xml
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="weather.ozweather" name="Oz Weather" version="2.1.1" provider-name="Bossanova808">
<addon id="weather.ozweather" name="Oz Weather" version="2.1.2" provider-name="Bossanova808">
<requires>
<import addon="xbmc.python" version="3.0.0"/>
<import addon="script.module.requests" version="2.22.0+matrix.1"/>
<import addon="script.module.beautifulsoup4" version="4.8.2+matrix.1"/>
<import addon="script.module.pytz" version="3.0+matrix.2"/>
<import addon="script.module.bossanova808" version="1.0.0"/>
</requires>
<extension point="xbmc.python.weather" library="default.py"/>
<extension point="xbmc.addon.metadata">
<summary lang="en_GB">Weather forecasting and radar images for Australia using Bureau of Meteorology data</summary>
<description lang="en_GB">Weather forecasting and radar images for Australia using Bureau of Meteorology data. For full features (animated radars &amp; ABC weather videos) - make sure you install the replacement skin files - see information at the addon wiki (https://kodi.wiki/index.php?title=Add-on:Oz_Weather).</description>
<disclaimer lang="en_GB">Use of this addon implies you agree to the following terms and conditions - http://reg.bom.gov.au/other/copyright.shtml and http://reg.bom.gov.au/other/disclaimer.shtml</disclaimer>
<disclaimer lang="en_GB">Use of this addon implies you agree to the following terms and conditions - https://reg.bom.gov.au/other/copyright.shtml and https://reg.bom.gov.au/other/disclaimer.shtml</disclaimer>
<platform>all</platform>
<language>en</language>
<license>GPL-3.0-only</license>
Expand All @@ -22,8 +23,9 @@
<icon>icon.png</icon>
<fanart>fanart.jpg</fanart>
</assets>
<news>2.1.1
- Add Latitude and Longitude to Window data for skins that display 'season' using (arguably dubious) logic
<news>v2.1.2
- Remove old common code, instead use new script.module.bossanova808
- Fix for occasional error with missing national radar background
</news>
</extension>
</addon>
4 changes: 4 additions & 0 deletions weather.ozweather/changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
v2.1.2
- Remove old common code, instead use new script.module.bossanova808
- Fix for occasional error with missing national radar background

2.1.1
- Add Latitude and Longitude to Window data for skins that display 'season' using (arguably dubious) logic

Expand Down
5 changes: 3 additions & 2 deletions weather.ozweather/default.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-

import sys
from bossanova808 import exception_logger
from resources.lib import ozweather

# This is kept as minimal as possible per @enen92's advice that:
# Reason for this is that entry point files are the only python files that are not compiled to bytecode (pyc).
# Hence, you'll see a performance gain if you store your codebase in a "module"

if __name__ == "__main__":
ozweather.run(sys.argv)

with exception_logger.log_exception():
ozweather.run(sys.argv)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 11 additions & 35 deletions weather.ozweather/resources/lib/abc/abc_video.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
# -*- coding: utf-8 -*-
import requests
import re
import sys
import xbmc
import json
from bs4 import BeautifulSoup

# Small hack to allow for unit testing - see common.py for explanation
# Allow for unit testing this file
# This brings this addon's resources, and bossanova808 module stuff into scope
# (when running this module outside Kodi)
if not xbmc.getUserAgent():
sys.path.insert(0, '../../..')
sys.path.insert(0, '../../../../script.module.bossanova808/resources/lib')

from resources.lib.store import Store
from resources.lib.common import *
from bossanova808.utilities import *


def scrape_and_play_abc_weather_video():
Expand All @@ -22,7 +23,7 @@ def scrape_and_play_abc_weather_video():
# Construct an offscreen list item with metadata...
item = xbmcgui.ListItem(path=url)
item.setProperty('mimetype', 'video/mpeg')
item.setInfo('Video', { 'title' : 'ABC Weather In 90 Seconds'})
item.setInfo('Video', {'title': 'ABC Weather In 90 Seconds'})
item.setArt({'thumb': f'{CWD}/resources/weather-in-90-seconds.png'})
# ...and then play it, fullscreen
xbmc.Player().play(url, item, False)
Expand All @@ -31,16 +32,12 @@ def scrape_and_play_abc_weather_video():

# See bottom of this file for notes on matching the video links (& Store.py for the regex)
def get_abc_weather_video_link():

try:
r = requests.get(Store.ABC_URL)

bs = BeautifulSoup(r.text, "html.parser")
json_string = bs.find("script", {'type': 'application/json',"id": "__NEXT_DATA__"})

json_string = bs.find("script", {'type': 'application/json', "id": "__NEXT_DATA__"})
json_object = json.loads(json_string.string)

# log(json_object)
# Logger.debug(json_object)
# Put the json blob into: https://jsonhero.io/j/JU0I9LB4AlLU
# Gives a path to the needed video as:
# $.props.pageProps.channelpage.components.0.component.props.list.3.player.config.sources.1.file
Expand All @@ -51,32 +48,11 @@ def get_abc_weather_video_link():
return sorted(urls, key=lambda x: x['bitrate'], reverse=True)[0]['file']

except Exception as inst:
log("Couldn't get ABC video URL from scraped page: " + str(inst))
Logger.error("Couldn't get ABC video URL from scraped page: " + str(inst))
return ""


# UNIT TESTING
if __name__ == "__main__":
log("\nTesting scraping of ABC Weather Video - here's the 'Best' link:\n")
log(get_abc_weather_video_link())


# > 2023_05 - CURRENT ABC VIDEO URL NOTES
# view the source on: https://www.abc.net.au/news/weather
# search for 'mp4'
# Regex in Store.py used to match the URL format
# Multiple matches will be found - first is a definition/download link (.mpg)
# 2nd is the highest quality stream (720p) - the one we want.
# https://mediacore-live-production.akamaized.net/video/01/im/Z/0m.mp4

# < 2023_05 - LEGACY INFO
# note date and quality level variables...
# view source on https://www.abc.net.au/news/newschannel/weather-in-90-seconds/ and find mp4 to see this list,
# the end of the URL can change regularly
# {'url': 'https://abcmedia.akamaized.net/news/news24/wins/201403/WINs_Weather1_0703_1000k.mp4', 'contentType': 'video/mp4', 'codec': 'AVC', 'bitrate': '928', 'width': '1024', 'height': '576', 'filesize': '11657344'}
# {'url': 'https://abcmedia.akamaized.net/news/news24/wins/201403/WINs_Weather1_0703_256k.mp4', 'contentType': 'video/mp4', 'codec': 'AVC', 'bitrate': '170', 'width': '320', 'height': '180', 'filesize': '2472086'}
# {'url': 'https://abcmedia.akamaized.net/news/news24/wins/201403/WINs_Weather1_0703_512k.mp4', 'contentType': 'video/mp4', 'codec': 'AVC', 'bitrate': '400', 'width': '512', 'height': '288', 'filesize': '5328218'}
# {'url': 'https://abcmedia.akamaized.net/news/news24/wins/201403/WINs_Weather1_0703_trw.mp4', 'contentType': 'video/mp4', 'codec': 'AVC', 'bitrate': '1780', 'width': '1280', 'height': '720', 'filesize': '21599356'}
# Other URLs - should match any of these
# https://abcmedia.akamaized.net/news/news24/wins/201409/WINm_Update1_0909_VSB03WF2_512k.mp4&
# https://abcmedia.akamaized.net/news/news24/wins/201409/WINs_Weather2_0209_trw.mp4
Logger.info("\nTesting scraping of ABC Weather Video - here's the 'Best' link:\n")
Logger.info(get_abc_weather_video_link())
58 changes: 30 additions & 28 deletions weather.ozweather/resources/lib/bom/bom_forecast.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
import math
import xbmc

# Small hack to allow for unit testing - see common.py for explanation
# Allow for unit testing this file
# This brings this addon's resources, and bossanova808 module stuff into scope
# (when running this module outside Kodi)
if not xbmc.getUserAgent():
sys.path.insert(0, '../../..')
sys.path.insert(0, '../../../../script.module.bossanova808/resources/lib')

from resources.lib.store import Store
from resources.lib.common import *
from bossanova808.utilities import *
from bossanova808.logger import Logger

"""
(See bottom of this file for BOM API output examples!)
Expand Down Expand Up @@ -137,37 +141,35 @@ def bom_forecast(geohash):
try:
r = requests.get(bom_api_area_information_url)
area_information = r.json()["data"]
log(area_information)
Logger.debug(area_information)
if area_information:
location_timezone_text = area_information['timezone']
log(f"Location timezone from BOM is {location_timezone_text}")
Logger.debug(f"Location timezone from BOM is {location_timezone_text}")
location_timezone = pytz.timezone(location_timezone_text)
# For any date comparisons - this is the localised now...
now = datetime.datetime.now(location_timezone)

except:
log(f'Error retrieving area information from {bom_api_area_information_url}')
Logger.error(f'Error retrieving area information from {bom_api_area_information_url}')

# Get CURRENT OBSERVATIONS
try:
r = requests.get(bom_api_current_observations_url)
current_observations = r.json()["data"]
weather_data['ObservationsUpdated'] = utc_str_to_local_str(r.json()["metadata"]["issue_time"], time_zone=location_timezone)
weather_data['ObservationsStation'] = r.json()["data"]['station']['name']
log(current_observations)

Logger.debug(current_observations)
except:
log(f'Error retrieving current observations from {bom_api_current_observations_url}')
Logger.error(f'Error retrieving current observations from {bom_api_current_observations_url}')
return False

# Get WARNINGS
try:
r = requests.get(bom_api_warnings_url)
warnings = r.json()["data"]
log(warnings)
Logger.debug(warnings)

except:
log(f'Error retrieving warnings from {bom_api_warnings_url}')
Logger.error(f'Error retrieving warnings from {bom_api_warnings_url}')

# Get 7-DAY FORECAST
try:
Expand All @@ -176,10 +178,10 @@ def bom_forecast(geohash):
weather_data['ForecastUpdated'] = utc_str_to_local_str(r.json()["metadata"]["issue_time"], time_zone=location_timezone)
weather_data['ForecastRegion'] = r.json()["metadata"]["forecast_region"].title()
weather_data['ForecastType'] = r.json()["metadata"]["forecast_type"].title()
log(forecast_seven_days)
Logger.debug(forecast_seven_days)

except:
log(f'Error retrieving seven day forecast from {bom_api_forecast_seven_days_url}')
Logger.error(f'Error retrieving seven day forecast from {bom_api_forecast_seven_days_url}')
return False

# FUTURE?
Expand Down Expand Up @@ -234,15 +236,15 @@ def bom_forecast(geohash):
# AT = Ta + 0.33•ρ − 0.70•ws − 4.00
else:
try:
log("Feels Like not provided by BOM. Attempting to calculate feels like...but will likely fail...")
Logger.warning("Feels Like not provided by BOM. Attempting to calculate feels like...but will likely fail...")
water_vapour_pressure = current_observations['humidity'] * 6.105 * math.exp((17.27 * current_observations['temp'])/(237.7 + current_observations['temp']))
calculated_feels_like = current_observations['temp'] + (0.33 * water_vapour_pressure) - (0.70 * current_observations['wind']['speed_kilometre']) - 4.00
weather_data['Current.FeelsLike'] = calculated_feels_like
weather_data['Current.Ozw_FeelsLike'] = calculated_feels_like
log(f"Success! Using calculated feels like of {calculated_feels_like}")
Logger.info(f"Success! Using calculated feels like of {calculated_feels_like}")
# Not provided, could not calculate it, so set it to the current temp (avoids Kodi showing a random '0' value!)
except:
log("Feels like not provided, could not calculate - setting to current temperature to avoid kodi displaying random 0s.")
Logger.error("Feels like not provided, and also could not calculate - setting to current temperature to avoid kodi displaying random 0s.")
weather_data['Current.FeelsLike'] = str(round(current_observations['temp']))

# WARNINGS
Expand All @@ -251,7 +253,7 @@ def bom_forecast(geohash):
for i, warning in enumerate(warnings):
# Warnings body...only major warnings as we don't need every little message about sheep grazing etc...
if warning['warning_group_type'] == 'major':
# Don't really care when it was issue, if it hasn't expired, it's current, so show it..
# Don't really care when it was issue, if it hasn't expired, it's current, so show it...
# warning_issued = utc_str_to_local_str(warning['issue_time'], local_format='%d/%m %I:%M%p', time_zone=location_timezone)
# Time signature on the expiry is different for some reason?!
# Remove the completely unnecessary fractions of a second...
Expand Down Expand Up @@ -318,7 +320,7 @@ def bom_forecast(geohash):
descriptor_from_short_text = forecast_seven_days[i]['short_text'].lower()
descriptor_from_short_text = descriptor_from_short_text.replace(' ', '_').replace('.', '').strip()
if descriptor_from_short_text in Store.WEATHER_CODES:
log("Using short text as icon descriptor as this is often more reliable than the actual icon_descriptor")
Logger.debug("Using short text as icon descriptor as this is often more reliable than the actual icon_descriptor")
icon_descriptor = descriptor_from_short_text
# Now we have some sort of icon descriptor, try and get the actual icon_code
try:
Expand All @@ -327,11 +329,11 @@ def bom_forecast(geohash):
else:
icon_code = Store.WEATHER_CODES[icon_descriptor]
except KeyError:
log(f'Could not find icon code for BOM icon_descriptor: {forecast_seven_days[i]["icon_descriptor"]} and from short text {descriptor_from_short_text}')
Logger.error(f'Could not find icon code for BOM icon_descriptor: {forecast_seven_days[i]["icon_descriptor"]} and from short text {descriptor_from_short_text}')
# Pop the missing icon descriptor into the outlook to make it easier for people to report in the forum thread
set_key(weather_data, i, "Outlook", f"[{forecast_seven_days[i]['icon_descriptor']}] {forecast_seven_days[i]['short_text']}")

log(f"Icon descriptor is: {icon_descriptor}, icon code is {icon_code}")
Logger.debug(f"Icon descriptor is: {icon_descriptor}, icon code is {icon_code}")
set_keys(weather_data, i, ["OutlookIcon", "ConditionIcon"], f'{icon_code}.png')
set_keys(weather_data, i, ["FanartCode"], icon_code)

Expand All @@ -346,20 +348,20 @@ def bom_forecast(geohash):
temp_max = forecast_seven_days[i]['temp_max']
if i == 0 and not temp_max:
if forecast_seven_days[i]['now']['now_label'] == "Tomorrow's Max":
log("Using now->temp_now as now->now_label is Tomorrow's Max")
Logger.debug("Using now->temp_now as now->now_label is Tomorrow's Max")
temp_max = forecast_seven_days[i]['now']['temp_now']
elif forecast_seven_days[i]['now']['later_label'] == "Tomorrow's Max":
log("Using now->temp_later as now->later_label is Tomorrow's Max")
Logger.debug("Using now->temp_later as now->later_label is Tomorrow's Max")
temp_max = forecast_seven_days[i]['now']['temp_later']
set_keys(weather_data, i, ["HighTemp", "HighTemperature"], temp_max)

temp_min = forecast_seven_days[i]['temp_min']
if i == 0 and not temp_min:
if forecast_seven_days[i]['now']['now_label'] == 'Overnight Min':
log("Using now->temp_now as now->now_label is Overnight Min")
Logger.debug("Using now->temp_now as now->now_label is Overnight Min")
temp_min = forecast_seven_days[i]['now']['temp_now']
elif forecast_seven_days[i]['now']['later_label'] == 'Overnight Min':
log("Using now->temp_later as now->later_label is Overnight Min")
Logger.debug("Using now->temp_later as now->later_label is Overnight Min")
temp_min = forecast_seven_days[i]['now']['temp_later']
set_keys(weather_data, i, ["LowTemp", "LowTemperature"], temp_min)

Expand Down Expand Up @@ -411,19 +413,19 @@ def bom_forecast(geohash):


###########################################################
# MAIN (only for unit testing outside of Kodi)
# MAIN (only for unit testing outside Kodi)

if __name__ == "__main__":

geohashes_to_test = ['r1r11df', 'r1f94ew']
for geohash in geohashes_to_test:
log(f'Getting weather data from BOM for geohash "{geohash}"')
Logger.info(f'Getting weather data from BOM for geohash "{geohash}"')
test_data = bom_forecast(geohash)

for key in sorted(test_data):
if test_data[key] == "?" or test_data[key] == "na":
log("**** MISSING: ")
log(f'{key}: "{test_data[key]}"')
Logger.info("**** MISSING: ")
Logger.info(f'{key}: "{test_data[key]}"')

"""
BOM API
Expand Down
Loading

0 comments on commit 81aaad4

Please sign in to comment.