Compare commits

...

26 Commits

Author SHA1 Message Date
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
c45cfc9aa0 docs: Update README and index.html with new overlays 2025-10-31 09:41:41 +01:00
67ac5f3613 Merge remote-tracking branch 'origin/main'
Please enter a commit message to explain why this merge is necessary,
2025-10-31 09:39:10 +01:00
d8bc68c7b5 feat: Initial commit of existing overlay files 2025-10-31 09:39:07 +01:00
16 changed files with 655 additions and 6 deletions

15
Other overlay ideas.md Normal file
View File

@@ -0,0 +1,15 @@
The overlays are to be posted to https://gitea.ramforth.net/ramforth/Overlays
That way, the webpage on https://overlays.ramforth.net/ will be updated.
Also, the web page structure for https://overlays.ramforth.net/ needs an overhaul, and needs more style and fun. Graphics, and pictures.
Basic ideas:
* An overlay that can take RSS feeds and make a ticker style horizontal scroller for the headlines
* A What's playing on Spotify overlay
* Overlay for Youtube music "now playing"
New Suggestions:
* **Live Chat Overlay:** Display chat messages from a YouTube or Twitch stream in real-time.
* **Goals & Countdown Overlay:** Show stream goals (followers, subs, etc.) with a progress bar, or a countdown to a specific event.
* **Social Media Feed:** Display a live feed of posts from a Twitter/X hashtag or account.
* **System Monitor:** Show real-time PC stats like CPU/GPU temperature and usage.
* **Weather Overlay:** Display the current weather for a specified location.

View File

@@ -1,8 +1,26 @@
# Overlays
# Ramforth Overlays
This repository serves as a collection of various overlays for streaming and other purposes.
Each subdirectory contains overlays for a specific service or project.
This repository contains a collection of browser source overlays for streaming software like OBS Studio.
## Available Overlays
* **Azuracast:** Overlays for displaying "Now Playing" information from an Azuracast radio station.
* **Azuracast "Now Playing"**: Displays the currently playing song from an Azuracast radio station.
* `overlay-text-only.html`: A clean, text-only overlay.
* `overlay-with-album-art.html`: An overlay that includes album art.
* **Weather Overlay**: Displays the current weather for a specific location.
* **System Monitor Overlay**: Displays real-time system information like CPU and memory usage.
## Future Ideas
* An overlay that can take RSS feeds and make a ticker style horizontal scroller for the headlines
* A What's playing on Spotify overlay
* Overlay for Youtube music "now playing"
* Live Chat Overlay: Display chat messages from a YouTube or Twitch stream in real-time.
* Goals & Countdown Overlay: Show stream goals (followers, subs, etc.) with a progress bar, or a countdown to a specific event.
* Social Media Feed: Display a live feed of posts from a Twitter/X hashtag or account.
## Usage
To use these overlays, you can clone this repository and open the HTML files in a browser source in your streaming software.
For detailed instructions on each overlay, please refer to the individual `README.md` files in the respective overlay directories.

View File

@@ -75,8 +75,24 @@
<h3>Azuracast Radio Overlays</h3>
<ul>
<li><a href="/azuracast/overlay-text-only.html">Text-only Now Playing Overlay</a></li>
<li><a href="/azuracast/overlay-with-album-art.html">Now Playing Overlay with Album Art</a></li>
<li><a href="/overlay-text-only.html">Text-only Now Playing Overlay</a></li>
<li><a href="/overlay-with-album-art.html">Now Playing Overlay with Album Art</a></li>
</ul>
<h3>Weather Overlay</h3>
<ul>
<li><a href="/weather_overlay/weather.html">Weather Overlay</a></li>
</ul>
<h3>System Monitor Overlay</h3>
<ul>
<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>
<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://www.nasa.gov/news-release/feed/"; // Default NASA 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

@@ -0,0 +1,42 @@
import psutil
import http.server
import socketserver
import json
PORT = 8000
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):
if self.path == '/stats':
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
stats = {
'cpu_percent': psutil.cpu_percent(interval=1),
'memory_percent': psutil.virtual_memory().percent
}
self.wfile.write(json.dumps(stats).encode('utf-8'))
else:
super().do_GET()
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)
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nShutting down server.")
httpd.shutdown()

View File

@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Monitor</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>
<h1>System Monitor</h1>
<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>

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

@@ -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>