Compare commits

..

26 Commits

Author SHA1 Message Date
7aed6e5707 Small adjustment 2025-10-31 13:47:28 +01:00
0746ecd3d1 Small adjustment to RSS feed 2025-10-31 11:50:20 +01:00
44d5e206ca Changed the default news feed RSS url 2025-10-31 11:46:27 +01:00
fa9534609c feat: Implement dynamic RSS ticker animation speed 2025-10-31 11:43:52 +01:00
7b9095f849 fix: Further adjust RSS ticker animation speed to 240s 2025-10-31 11:33:25 +01:00
7b1f0d2be2 fix: Further adjust RSS ticker animation speed 2025-10-31 11:27:57 +01:00
17bdc73457 fix: Adjust RSS ticker animation speed 2025-10-31 11:24:39 +01:00
02b53b01a1 fix: Correctly parse XML response from CORS proxy in RSS ticker 2025-10-31 11:18:42 +01:00
f41bc910bc fix: Add timeout to proxy requests and handle missing Content-Type 2025-10-31 11:12:55 +01:00
d2020e57ed feat: Add reset button and improve error handling for RSSticker 2025-10-31 11:06:15 +01:00
084401bb3a feat: Add RSS Ticker Overlay 2025-10-31 10:51:37 +01:00
0f56d23d44 feat: Add location storage and hide input to weather overlay 2025-10-31 10:26:37 +01:00
1f02269e75 docs: Clarify location selection instructions for weather overlay 2025-10-31 10:18:47 +01:00
ce93b8a092 feat: Add location selection to weather overlay 2025-10-31 10:15:20 +01:00
e03d50ed5b revert: Revert port change to 8000 2025-10-31 10:12:39 +01:00
9498c2dcc2 fix: Change port to 8080 to avoid address in use error 2025-10-31 10:10:13 +01:00
ade17801a0 fix: Allow address reuse in server.py 2025-10-31 10:09:08 +01:00
e9d358d1e5 refactor: Rename overlay files for clarity 2025-10-31 10:06:50 +01:00
45ae372b8c feat: Suppress server logs and add server status to overlay 2025-10-31 10:05:32 +01:00
8ec5a8e49e fix: Handle OPTIONS requests in server.py 2025-10-31 10:01:56 +01:00
03469097c8 fix: Fix indentation error in server.py 2025-10-31 10:00:35 +01:00
f889c4dc68 fix: Handle KeyboardInterrupt gracefully in server.py 2025-10-31 09:59:48 +01:00
9c6c619071 docs: Add detailed Python environment setup instructions 2025-10-31 09:58:55 +01:00
70ab2550ad docs: Add example image to system_monitor_overlay README 2025-10-31 09:56:13 +01:00
a1bb4a1010 docs: Add README.md for system_monitor_overlay 2025-10-31 09:53:51 +01:00
a48a06ed04 feat: Add heading-less system monitor overlay 2025-10-31 09:50:35 +01:00
12 changed files with 546 additions and 90 deletions

View File

@@ -81,12 +81,18 @@
<h3>Weather Overlay</h3> <h3>Weather Overlay</h3>
<ul> <ul>
<li><a href="/weather_overlay/index.html">Weather Overlay</a></li> <li><a href="/weather_overlay/weather.html">Weather Overlay</a></li>
</ul> </ul>
<h3>System Monitor Overlay</h3> <h3>System Monitor Overlay</h3>
<ul> <ul>
<li><a href="/system_monitor_overlay/index.html">System Monitor Overlay</a></li> <li><a href="/system_monitor_overlay/system_monitor.html">System Monitor Overlay</a></li>
<li><a href="/system_monitor_overlay/system_monitor_no_heading.html">System Monitor Overlay (No Heading)</a></li>
</ul>
<h3>RSS Ticker Overlay</h3>
<ul>
<li><a href="/rss_ticker_overlay/rss_ticker.html">RSS Ticker Overlay</a></li>
</ul> </ul>
<p>For detailed usage instructions and to understand how to deploy these overlays, please refer to the <a href="https://gitea.ramforth.net/ramforth/Overlays/src/branch/main/azuracast/README.md">Azuracast README.md</a> within the Gitea repository.</p> <p>For detailed usage instructions and to understand how to deploy these overlays, please refer to the <a href="https://gitea.ramforth.net/ramforth/Overlays/src/branch/main/azuracast/README.md">Azuracast README.md</a> within the Gitea repository.</p>

View File

@@ -0,0 +1,22 @@
# RSS Ticker Overlay
This overlay displays headlines from an RSS feed as a horizontally scrolling ticker. It's designed to be used as a browser source in streaming software like OBS Studio.
## Installation
1. **Add to OBS Studio:**
* Add a new "Browser" source to your scene.
* Set the URL to the `rss_ticker.html` file in this directory.
* Set the width and height to your desired dimensions.
2. **Customize RSS Feed:**
To set your RSS feed URL, you need to open the `rss_ticker.html` file in a regular web browser (like Firefox or Chrome) first.
1. Open the `rss_ticker.html` file in your browser.
2. Enter the full URL of the RSS feed in the input field and click the "Update RSS" button.
3. The ticker will now display headlines from your chosen RSS feed. This URL will be saved in your browser's local storage, so it will be remembered the next time you open the overlay.
4. You can now add the `rss_ticker.html` file as a browser source in OBS Studio. It will use the RSS feed you just set.
**Note:** To reset the RSS feed URL, you can open the browser's developer tools (usually by pressing F12), go to the "Application" or "Storage" tab, find "Local Storage", and delete the `rss_feed_url` key.

View File

@@ -0,0 +1,24 @@
from flask import Flask, request, Response
import requests
app = Flask(__name__)
@app.route('/proxy')
def proxy():
url = request.args.get('url')
if not url:
return "Missing URL parameter", 400
try:
response = requests.get(url, timeout=10)
content_type = response.headers.get('Content-Type', 'text/plain')
headers = {
'Access-Control-Allow-Origin': '*',
'Content-Type': content_type
}
return Response(response.content, response.status_code, headers)
except requests.exceptions.RequestException as e:
return str(e), 500
if __name__ == '__main__':
app.run(port=8001)

View File

@@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RSS Ticker Overlay</title>
<style>
body {
font-family: sans-serif;
color: white;
background-color: rgba(0, 0, 0, 0.5);
padding: 10px;
border-radius: 5px;
overflow: hidden; /* Hide scrollbar */
}
#ticker-container {
white-space: nowrap;
overflow: hidden;
box-sizing: border-box;
width: 100%;
}
#ticker-content {
display: inline-block;
padding-left: 100%; /* Start off-screen */
}
@keyframes ticker-scroll {
0% { transform: translate3d(0, 0, 0); }
100% { transform: translate3d(-100%, 0, 0); }
}
#rss-input-container {
background-color: rgba(0, 0, 0, 0.7);
border: 1px solid white;
padding: 10px;
margin-top: 20px;
z-index: 100;
}
#rss-url-input {
padding: 5px;
border: 1px solid #ccc;
border-radius: 5px;
width: 70%;
}
#update-rss-button {
padding: 5px 10px;
border: 1px solid #ccc;
border-radius: 5px;
background-color: #4CAF50;
color: white;
cursor: pointer;
}
</style>
</head>
<body>
<div id="ticker-container">
<span id="ticker-content">Loading RSS feed...</span>
</div>
<div id="rss-input-container">
<input type="text" id="rss-url-input" placeholder="Enter RSS Feed URL">
<button id="update-rss-button">Update RSS</button>
<button id="reset-rss-button">Reset</button>
</div>
<script>
const defaultRssUrl = "https://thecafeterium.com/feed/"; // Default theCafeterium news feed
function fetchRssFeed(rssUrl) {
console.log('Fetching RSS feed:', rssUrl);
const corsProxy = "http://localhost:8001/proxy?url=";
const url = `${corsProxy}${encodeURIComponent(rssUrl)}`;
fetch(url)
.then(response => {
console.log('Proxy response:', response);
if (response.ok) return response.text();
throw new Error('Network response was not ok.');
})
.then(xmlText => {
console.log('Raw XML from proxy:', xmlText);
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlText, "text/xml");
console.log('Parsed XML Doc:', xmlDoc);
const items = xmlDoc.querySelectorAll("item");
let headlines = [];
items.forEach(item => {
const titleElement = item.querySelector("title");
if (titleElement) {
headlines.push(titleElement.textContent);
}
});
if (headlines.length > 0) {
const tickerContent = document.getElementById('ticker-content');
tickerContent.textContent = headlines.join(" | ");
// Calculate dynamic animation duration
const pixelsPerSecond = 50; // Adjust this value to change the perceived speed
const contentWidth = tickerContent.scrollWidth;
const animationDuration = contentWidth / pixelsPerSecond;
// Apply and restart animation
tickerContent.style.animationDuration = `${animationDuration}s`;
tickerContent.style.animation = 'none'; // Reset animation
void tickerContent.offsetWidth; // Trigger reflow
tickerContent.style.animation = `ticker-scroll ${animationDuration}s linear infinite`;
} else {
document.getElementById('ticker-content').textContent = "No headlines found in feed.";
}
})
.catch(error => {
console.error('Error fetching RSS feed:', error);
document.getElementById('ticker-content').textContent = "Error loading RSS feed.";
});
}
function saveRssUrl(rssUrl) {
localStorage.setItem('rss_feed_url', rssUrl);
}
function loadRssUrl() {
return localStorage.getItem('rss_feed_url');
}
document.getElementById('update-rss-button').addEventListener('click', () => {
const rssUrl = document.getElementById('rss-url-input').value;
if (rssUrl) {
saveRssUrl(rssUrl);
fetchRssFeed(rssUrl);
document.getElementById('rss-input-container').style.display = 'none';
}
});
document.getElementById('reset-rss-button').addEventListener('click', () => {
localStorage.removeItem('rss_feed_url');
document.getElementById('rss-input-container').style.display = 'block';
document.getElementById('rss-url-input').value = '';
document.getElementById('ticker-content').textContent = "Loading RSS feed...";
});
// Initial load
const storedRssUrl = loadRssUrl();
if (storedRssUrl) {
fetchRssFeed(storedRssUrl);
document.getElementById('rss-input-container').style.display = 'none';
} else {
fetchRssFeed(defaultRssUrl);
}
</script>
</body>
</html>

View File

@@ -0,0 +1,68 @@
# System Monitor Overlay
This overlay displays real-time system information like CPU and memory usage. It's designed to be used as a browser source in streaming software like OBS Studio.
## Example Output
![Example Output](obs_example.png)
## Setting up the Python Environment
Before you can run the server, it's recommended to create a virtual environment to keep the dependencies for this project isolated from your system's Python installation.
1. **Create a virtual environment:**
```bash
python3 -m venv venv
```
2. **Activate the virtual environment:**
* On Linux/macOS (bash/zsh):
```bash
source venv/bin/activate
```
* On Linux/macOS (fish):
```bash
source venv/bin/activate.fish
```
* On Windows:
```bash
.\venv\Scripts\activate
```
You will know the virtual environment is active when you see `(venv)` at the beginning of your command prompt.
## Installation
Once you have activated the virtual environment, you can install the required library and run the server.
1. **Install the required library:**
```bash
pip install psutil
```
2. **Run the server:**
```bash
python server.py
```
The server will start on port 8000.
3. **Add to OBS Studio:**
* Add a new "Browser" source to your scene.
* Set the URL to the `system_monitor.html` file in this directory.
* Set the width and height to your desired dimensions.
## Overlays
* `system_monitor.html`: The default overlay with a heading.
* `system_monitor_no_heading.html`: An alternative overlay without the heading.

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -6,6 +6,13 @@ import json
PORT = 8000 PORT = 8000
class SystemInfoHandler(http.server.SimpleHTTPRequestHandler): class SystemInfoHandler(http.server.SimpleHTTPRequestHandler):
def do_OPTIONS(self):
self.send_response(200, "ok")
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS')
self.send_header("Access-Control-Allow-Headers", "X-Requested-With, Content-type")
self.end_headers()
def do_GET(self): def do_GET(self):
if self.path == '/stats': if self.path == '/stats':
self.send_response(200) self.send_response(200)
@@ -20,6 +27,16 @@ class SystemInfoHandler(http.server.SimpleHTTPRequestHandler):
else: else:
super().do_GET() super().do_GET()
with socketserver.TCPServer(('', PORT), SystemInfoHandler) as httpd: class QuietSystemInfoHandler(SystemInfoHandler):
def log_message(self, format, *args):
if self.path != '/stats':
super().log_message(format, *args)
socketserver.TCPServer.allow_reuse_address = True
with socketserver.TCPServer(('', PORT), QuietSystemInfoHandler) as httpd:
print("serving at port", PORT) print("serving at port", PORT)
httpd.serve_forever() try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nShutting down server.")
httpd.shutdown()

View File

@@ -12,17 +12,27 @@
padding: 20px; padding: 20px;
border-radius: 10px; border-radius: 10px;
} }
.stat {
font-size: 1.2em;
margin-bottom: 10px;
}
.descriptor {
font-weight: 300; /* Light */
}
.value {
font-weight: 700; /* Bold */
}
</style> </style>
</head> </head>
<body> <body>
<h1>System Monitor</h1> <h1>System Monitor</h1>
<div> <div class="stat">
<h2>CPU Usage</h2> <span class="descriptor">CPU Usage:</span>
<p id="cpu-usage"></p> <span class="value" id="cpu-usage"></span>
</div> </div>
<div> <div class="stat">
<h2>Memory Usage</h2> <span class="descriptor">Memory Usage:</span>
<p id="memory-usage"></p> <span class="value" id="memory-usage"></span>
</div> </div>
<script> <script>
@@ -33,7 +43,11 @@
document.getElementById('cpu-usage').textContent = `${data.cpu_percent}%`; document.getElementById('cpu-usage').textContent = `${data.cpu_percent}%`;
document.getElementById('memory-usage').textContent = `${data.memory_percent}%`; document.getElementById('memory-usage').textContent = `${data.memory_percent}%`;
}) })
.catch(error => console.error('Error fetching system stats:', error)); .catch(error => {
console.error('Error fetching system stats:', error);
document.getElementById('cpu-usage').textContent = "server not running";
document.getElementById('memory-usage').textContent = "server not running";
});
} }
// Fetch stats every 2 seconds // Fetch stats every 2 seconds

View File

@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Monitor (No Heading)</title>
<style>
body {
font-family: sans-serif;
color: white;
background-color: rgba(0, 0, 0, 0.5);
padding: 20px;
border-radius: 10px;
}
.stat {
font-size: 1.2em;
margin-bottom: 10px;
}
.descriptor {
font-weight: 300; /* Light */
}
.value {
font-weight: 700; /* Bold */
}
</style>
</head>
<body>
<div class="stat">
<span class="descriptor">CPU Usage:</span>
<span class="value" id="cpu-usage"></span>
</div>
<div class="stat">
<span class="descriptor">Memory Usage:</span>
<span class="value" id="memory-usage"></span>
</div>
<script>
function fetchStats() {
fetch('http://localhost:8000/stats')
.then(response => response.json())
.then(data => {
document.getElementById('cpu-usage').textContent = `${data.cpu_percent}%`;
document.getElementById('memory-usage').textContent = `${data.memory_percent}%`;
})
.catch(error => {
console.error('Error fetching system stats:', error);
document.getElementById('cpu-usage').textContent = "server not running";
document.getElementById('memory-usage').textContent = "server not running";
});
}
// Fetch stats every 2 seconds
setInterval(fetchStats, 2000);
// Initial fetch
fetchStats();
</script>
</body>
</html>

22
weather_overlay/README.md Normal file
View File

@@ -0,0 +1,22 @@
# Weather Overlay
This overlay displays the current weather for a specific location. It's designed to be used as a browser source in streaming software like OBS Studio.
## Installation
1. **Add to OBS Studio:**
* Add a new "Browser" source to your scene.
* Set the URL to the `weather.html` file in this directory.
* Set the width and height to your desired dimensions.
2. **Customize Location:**
To set your location, you need to open the `weather.html` file in a regular web browser (like Firefox or Chrome) first.
1. Open the `weather.html` file in your browser.
2. Enter a city name in the input field and click the "Update" button.
3. The overlay will now show the weather for the new location. This location will be saved in your browser's local storage, so it will be remembered the next time you open the overlay.
4. You can now add the `weather.html` file as a browser source in OBS Studio. It will use the location you just set.
**Note:** To reset the location, you can open the browser's developer tools (usually by pressing F12), go to the "Application" or "Storage" tab, find "Local Storage", and delete the `weather_latitude` and `weather_longitude` keys.

View File

@@ -1,79 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weather Overlay</title>
<style>
body {
font-family: sans-serif;
color: white;
background-color: rgba(0, 0, 0, 0.5);
padding: 20px;
border-radius: 10px;
}
#weather-container {
display: flex;
align-items: center;
}
#weather-icon {
width: 50px;
height: 50px;
margin-right: 20px;
}
</style>
</head>
<body>
<div id="weather-container">
<img id="weather-icon" src="" alt="Weather Icon">
<div>
<h1 id="temperature"></h1>
<p id="weather-description"></p>
</div>
</div>
<script>
const latitude = 40.71;
const longitude = -74.01;
const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true`;
fetch(apiUrl)
.then(response => response.json())
.then(data => {
const temperature = data.current_weather.temperature;
const weatherCode = data.current_weather.weathercode;
document.getElementById('temperature').textContent = `${temperature}°C`;
document.getElementById('weather-description').textContent = getWeatherDescription(weatherCode);
document.getElementById('weather-icon').src = getWeatherIcon(weatherCode);
})
.catch(error => console.error('Error fetching weather data:', error));
function getWeatherDescription(code) {
// This is a simplified mapping. You can expand it for more detailed descriptions.
if (code === 0) return "Clear sky";
if (code > 0 && code < 4) return "Partly cloudy";
if (code > 44 && code < 49) return "Fog";
if (code > 50 && code < 58) return "Drizzle";
if (code > 60 && code < 68) return "Rain";
if (code > 70 && code < 78) return "Snow";
if (code > 80 && code < 83) return "Rain showers";
if (code > 94) return "Thunderstorm";
return "Cloudy";
}
function getWeatherIcon(code) {
// This is a simplified mapping. You can expand it with more icons.
if (code === 0) return "https://openweathermap.org/img/wn/01d@2x.png"; // Clear sky
if (code > 0 && code < 4) return "https://openweathermap.org/img/wn/02d@2x.png"; // Partly cloudy
if (code > 44 && code < 49) return "https://openweathermap.org/img/wn/50d@2x.png"; // Fog
if (code > 50 && code < 58) return "https://openweathermap.org/img/wn/09d@2x.png"; // Drizzle
if (code > 60 && code < 68) return "https://openweathermap.org/img/wn/10d@2x.png"; // Rain
if (code > 70 && code < 78) return "https://openweathermap.org/img/wn/13d@2x.png"; // Snow
if (code > 80 && code < 83) return "https://openweathermap.org/img/wn/09d@2x.png"; // Rain showers
if (code > 94) return "https://openweathermap.org/img/wn/11d@2x.png"; // Thunderstorm
return "https://openweathermap.org/img/wn/03d@2x.png"; // Cloudy
}
</script>
</body>
</html>

View File

@@ -0,0 +1,152 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weather Overlay</title>
<style>
body {
font-family: sans-serif;
color: white;
background-color: rgba(0, 0, 0, 0.5);
padding: 20px;
border-radius: 10px;
}
#weather-container {
display: flex;
align-items: center;
}
#weather-icon {
width: 50px;
height: 50px;
margin-right: 20px;
}
#location-container {
background-color: rgba(0, 0, 0, 0.7);
border: 1px solid white;
padding: 10px;
margin-top: 20px;
z-index: 100;
}
#city-input {
padding: 5px;
border: 1px solid #ccc;
border-radius: 5px;
}
#update-button {
padding: 5px 10px;
border: 1px solid #ccc;
border-radius: 5px;
background-color: #4CAF50;
color: white;
cursor: pointer;
}
</style>
</head>
<body>
<div id="weather-container">
<img id="weather-icon" src="" alt="Weather Icon">
<div>
<h1 id="temperature"></h1>
<p id="weather-description"></p>
</div>
</div>
<div id="location-container">
<input type="text" id="city-input" placeholder="Enter city name">
<button id="update-button">Update</button>
</div>
<script>
const defaultLatitude = 40.71;
const defaultLongitude = -74.01;
function saveLocation(latitude, longitude) {
localStorage.setItem('weather_latitude', latitude);
localStorage.setItem('weather_longitude', longitude);
}
function loadLocation() {
const latitude = localStorage.getItem('weather_latitude');
const longitude = localStorage.getItem('weather_longitude');
return { latitude, longitude };
}
function fetchWeather(latitude, longitude) {
const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true`;
fetch(apiUrl)
.then(response => response.json())
.then(data => {
const temperature = data.current_weather.temperature;
const weatherCode = data.current_weather.weathercode;
document.getElementById('temperature').textContent = `${temperature}°C`;
document.getElementById('weather-description').textContent = getWeatherDescription(weatherCode);
document.getElementById('weather-icon').src = getWeatherIcon(weatherCode);
})
.catch(error => console.error('Error fetching weather data:', error));
}
function getCoordinates(city) {
const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${city}`;
fetch(geocodingUrl)
.then(response => response.json())
.then(data => {
if (data.results && data.results.length > 0) {
const latitude = data.results[0].latitude;
const longitude = data.results[0].longitude;
saveLocation(latitude, longitude);
fetchWeather(latitude, longitude);
document.getElementById('location-container').style.display = 'none';
} else {
alert("Could not find city. Please try again.");
}
})
.catch(error => console.error('Error fetching coordinates:', error));
}
document.getElementById('update-button').addEventListener('click', () => {
const city = document.getElementById('city-input').value;
if (city) {
getCoordinates(city);
}
});
function getWeatherDescription(code) {
// This is a simplified mapping. You can expand it for more detailed descriptions.
if (code === 0) return "Clear sky";
if (code > 0 && code < 4) return "Partly cloudy";
if (code > 44 && code < 49) return "Fog";
if (code > 50 && code < 58) return "Drizzle";
if (code > 60 && code < 68) return "Rain";
if (code > 70 && code < 78) return "Snow";
if (code > 80 && code < 83) return "Rain showers";
if (code > 94) return "Thunderstorm";
return "Cloudy";
}
function getWeatherIcon(code) {
// This is a simplified mapping. You can expand it with more icons.
if (code === 0) return "https://openweathermap.org/img/wn/01d@2x.png"; // Clear sky
if (code > 0 && code < 4) return "https://openweathermap.org/img/wn/02d@2x.png"; // Partly cloudy
if (code > 44 && code < 49) return "https://openweathermap.org/img/wn/50d@2x.png"; // Fog
if (code > 50 && code < 58) return "https://openweathermap.org/img/wn/09d@2x.png"; // Drizzle
if (code > 60 && code < 68) return "https://openweathermap.org/img/wn/10d@2x.png"; // Rain
if (code > 70 && code < 78) return "https://openweathermap.org/img/wn/13d@2x.png"; // Snow
if (code > 80 && code < 83) return "https://openweathermap.org/img/wn/09d@2x.png"; // Rain showers
if (code > 94) return "https://openweathermap.org/img/wn/11d@2x.png"; // Thunderstorm
return "https://openweathermap.org/img/wn/03d@2x.png"; // Cloudy
}
// Initial fetch
const storedLocation = loadLocation();
if (storedLocation.latitude && storedLocation.longitude) {
fetchWeather(storedLocation.latitude, storedLocation.longitude);
} else {
fetchWeather(defaultLatitude, defaultLongitude);
}
</script>
</body>
</html>