Skip to content

Instantly share code, notes, and snippets.

@claws
Created May 28, 2012 13:52
Show Gist options
  • Save claws/2819315 to your computer and use it in GitHub Desktop.
Save claws/2819315 to your computer and use it in GitHub Desktop.
Create Your Own Weather Report Audio Track Using Current BOM Data
"""
This script retrieves weather forecast and observation
data from the Australian Bureau of Meteorology (BOM)
and converts them into a spoken word weather report
audio file.
This script was written to generate the first track for
my morning alarm clock playlist.
For more information and to download the intro and outro
mp3 jingles see:
http://metaclaws.com/2012/05/28/create_your_own_weather_report_audio_track_using_current_bom_data
"""
from twisted.internet import reactor, defer, utils
from txbom.forecasts import get_forecast, forecastToDict
from txbom.observations import get_observations
import StringIO
import datetime
import logging
import os
import txbom
# Configuration Settings
# The forecast identifier for your location can be found from the BOM web site.
forecast_id = "IDS10034"
# The observations identifier is a url that points to the JSON
# encoded observations for your location. The url link is
# typically found at the bottom of the BOM web page that shows
# observations for your location.
observation_id = "http://www.bom.gov.au/fwo/IDS60901/IDS60901.94675.json"
# Define the words to be spoken by the weather report making use
# of forecast and observation dict keywords for substitutions
# in a string.template.
#
# Valid Observation substitution keywords are:
# obsv_identifier : The observation identifier
# obsv_town : The observation town
# obsv_state : The observation state
# obsv_time_zone : The time zone
# obsv_refreshed : Time observations were last refreshed
# obsv_full_date_time : A worded date time string (e.g. Sunday 27 May 2012)
# obsv_date : The observation date
# obsv_time : The observation time
# obsv_air_temp : The air temperature
# obsv_apparent_temp : The apparent air temperature
# obsv_wind_dir : A worded wind direction,
# obsv_wind_speed_kmh : The wind speed,
# obsv_wind_gust_speed_kmh : The wind gust speed
#
# Valid forecast substitution keywords are:
# fcast_id : The forecast location identifier,
# fcast_town : The forecast location name,
# fcast_state : The forecast location state,
# fcast_date : The forecast date
# fcast_time : The forecast time
# fcast_warnings : A string of any weather warnings
# fcast_uv_alert : The time period for any uv alert
# fcast_uv_index : The uv index value
# fcast_uv_index_name : The uv index name (e.g. Moderate)
# fcast_today : A description of today (e.g. Sunday 27 May 2012)
# fcast_today_content : Forecast for today
# fcast_today_precis : A summary forecast for today
# fcast_today_temperature : Forecast max temp for today
# fcast_tomorrow : Forecast for tomorrow
# fcast_tomorrow_precis : A summary forecast for tomorrow
# fcast_tomorrow_minimum : The min temp tomorrow
# fcast_tomorrow_maximum : The max temp tomorrow
# fcast_five_days : A list of (day_name, temp_min, temp_max, precis) for the next five days
# fcast_raw : The full raw forecast string
#
report_template = """
Current weather conditions for %(obsv_town)s, %(obsv_state)s on %(obsv_date)s at %(obsv_time)s.
The apparent temperature is %(obsv_apparent_temp)s degrees.
The wind is blowing from a %(obsv_wind_dir)s direction at %(obsv_wind_speed_kmh)s kilometers per hour,
gusting to %(obsv_wind_gust_speed_kmh)s kilometers per hour.
Latest forecast for %(fcast_town)s %(fcast_state)s issued on %(fcast_date)s at %(fcast_time)s.
%(fcast_today)s
%(fcast_today_content)s
Today's top temperature is forecast to be %(fcast_today_temperature)s.
This concludes the automated weather report.
"""
# Define a location to save temporary data files such as the
# text version of the weather report and the TTS .aac version
# of the file.
temp_dir = ""
# Define the location of the finished audio file.
#
weather_report_mp3_file = "weather_report.mp3"
# Path to TTS 'say' executable
#
text_to_speech_executable = "/usr/bin/say"
# Path to the ffmpeg executable used to convert weather report
# spoken word to mp3 and to join the intro, weather report and
# outro tracks into a single mp3 file.
#
ffmpeg_exe = "/usr/local/bin/ffmpeg"
# intro/outro mp3 files are added (before and after) to the weather report.
# This is useful if you want a jingle to surround your weather report - such
# as a news bulletin sound effect [i.e. from GarageBand, etc]. This is a
# aid to cueing a sleepy brain by building a memory association that makes
# you pay attention to the audio as you recognise that the weather forecast
# is about to come on.
intro_audio_file = "intro.mp3"
outro_audio_file = "outro.mp3"
# Available voices supported on OS X by the 'say' program
# female:
# Agnes
# Kathy
# Princess
# Vicki
# Victoria
#
# male:
# Alex
# Bruce
# Fred
# Junior
# Ralph
# Albert
#
# novelty:
# Bad News
# Bahh
# Bells
# Boing
# Bubbles
# Cellos
# Deranged
# Good News
# Hysterical
# Pipe Organ
# Trinoids
# Whisper
# Zarvox
text_to_speech_voice = "Alex"
@defer.inlineCallbacks
def generate_weather_report():
"""
Retrieve weather report internet resources,
convert to a compact weather report text file suitable for text-to-speech,
convert text to a spoken weather report via text-to-speech tool,
convert intermediate text-to-speech output to an mp3.
package intro, weather report and outro into a single audio file.
"""
forecast_txt_file = os.path.join(temp_dir, 'forecast.txt')
weather_report_txt_file = os.path.join(temp_dir, 'weather_report.txt')
weather_report_tts_file = os.path.join(temp_dir, 'weather_report.aac')
logging.info("Retrieving forecast and observation data")
observations = yield get_observations(observation_id)
forecast = yield get_forecast(forecast_id)
logging.info("Forecast and observation data retrieval successful")
# save a copy of the retrieved forecast (at the temp location)
# for debugging purposes
fd = open(forecast_txt_file, 'w')
fd.write(forecast)
fd.close()
templateDict = processObservations(observations)
templateDict.update(processForecast(forecast))
weatherReport = report_template % templateDict
logging.debug("Weather Report is:\n%s" % weatherReport)
fd = open(weather_report_txt_file, 'w')
fd.write(weatherReport)
fd.close()
logging.info("Generating weather report")
######################################################################
#
# Pass weather report text file through a TTS engine
# to generate the spoken weather report.
#
command = text_to_speech_executable
command_args = ["-v", "%s" % text_to_speech_voice,
"-o", "%s" % weather_report_tts_file,
"-f", "%s" % weather_report_txt_file]
result = yield utils.getProcessValue(executable=command, args=command_args)
if result:
full_command = "%s %s" % (command, " ".join(command_args))
err_str = "%s exited with status %s. Unable to generate file: %s" % (full_command, result, weather_report_tts_file)
logging.error(err_str)
raise Exception(err_str)
logging.debug("Converted weather report text output to TTS file: %s\n" % weather_report_tts_file)
######################################################################
#
# Convert weather report TTS audio to mp3 and return mp3 filename
#
command = ffmpeg_exe
command_args = ["-y", # forces overwrite
"-i", "%s" % weather_report_tts_file, # input filename
"-ac", "1", # 1 (mono) channel
"-acodec", "libmp3lame", # use lame
"-ar", "44100", # bitrate of 44.1kHz
"-metadata", "title=\"Weather Report\"", # title tag
"-metadata", "artist=\"Weather Service\"", # artist tag
"-metadata", "album=\"Weather Report\"", # album tag
"%s" % weather_report_mp3_file] # output file path
result = yield utils.getProcessValue(executable=command, args=command_args)
if result:
full_command = "%s %s" % (command, " ".join(command_args))
err_str = "%s exited with status %s. Unable to generate file: %s" % (full_command, result, weather_report_mp3_file)
logging.error(err_str)
raise Exception(err_str)
logging.debug("Converted weather report TTS output to mp3 file: %s\n" % weather_report_mp3_file)
######################################################################
#
# Concatenate the intro, weather report and outro tracks into a
# single audio track.
#
# For this to work properly the intro/outro tracks need to have the
# same bitrate as the weather report.
#
# The weather report mp3 file is configured to contain title, artist
# and album mp3 meta data but this gets lost when the files get
# concatenated. The end result is that the mp3 tags will be extracted
# from the first track, the intro. So the mp3 tags on the intro file
# should be setup appropriately for your needs.
#
outFile = StringIO.StringIO()
if os.path.exists(intro_audio_file):
fd = open(intro_audio_file, 'r')
outFile.write(fd.read())
fd.close()
fd = open(weather_report_mp3_file, 'r')
outFile.write(fd.read())
fd.close()
if os.path.exists(outro_audio_file):
fd = open(outro_audio_file, 'r')
outFile.write(fd.read())
fd.close()
fd = open(weather_report_mp3_file, 'w')
fd.write(outFile.getvalue())
fd.close()
# remove intermediate file resources
del outFile
os.remove(weather_report_tts_file)
os.remove(weather_report_txt_file)
logging.info("Finished generating weather report: %s\n" % weather_report_mp3_file)
defer.returnValue(True)
def processObservations(observations):
"""
Process weather observations data into an appropriate
format for use with a spoken word weather report.
"""
currentObservationData = observations.current
observationData = {}
for field in currentObservationData.fields:
observationData[field] = getattr(currentObservationData, field)
# Also add fields from the observation header that can be
# used to help identify the observation location.
for field in observations.header.fields:
observationData[field] = getattr(observations.header, field)
# extract the observation datetime and convert it to a date string
# looking like this: Sunday 27 May 2012
full_datetime = datetime.datetime.strptime(observationData["local_date_time_full"],
"%Y%m%d%H%M%S")
# create a date string looking like:
date_description = "%s %s %s" % (full_datetime.strftime("%A"),
str(int(full_datetime.strftime("%d"))),
full_datetime.strftime("%B %Y"))
# substitute textual wind direction words for acronyms. For example,
# use north west in place of NW.
direction = observationData["wind_dir"]
if direction in txbom.WindDirections:
direction_description = txbom.WindDirections[direction]
else:
direction_description = direction
observationsDict = {"obsv_identifier" : observationData["ID"],
"obsv_town" : observationData["name"],
"obsv_state" : observationData["state"],
"obsv_time_zone" : observationData["time_zone"],
"obsv_refreshed" : observationData["refresh_message"],
"obsv_full_date_time" : observationData["local_date_time_full"],
"obsv_date" : date_description,
"obsv_time" : observationData["local_date_time"].split("/")[1],
"obsv_air_temp" : observationData["air_temp"],
"obsv_apparent_temp" : observationData["apparent_t"],
"obsv_wind_dir" : direction_description,
"obsv_wind_speed_kmh" : observationData["wind_spd_kmh"],
"obsv_wind_gust_speed_kmh" : observationData["gust_kmh"]}
return observationsDict
def processForecast(forecast):
"""
Process a forecast from the BoM into an appropriate
format for use with a spoken word weather report.
"""
forecastDict = forecastToDict(forecast)
return forecastDict
def weatherReportFailure(failure):
logging.error("Error detected:\n%s" % str(failure))
def run():
""" Generate the weather report """
d = generate_weather_report()
d.addCallback(lambda _: reactor.callLater(1.0, reactor.stop))
d.addErrback(weatherReportFailure)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s")
reactor.callWhenRunning(run)
reactor.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment