adds interactivity to services.swpc.noaa.gov/images/animations. uses prefect for a backend to scrape images generate movies with ffmpeg. flask frontend at /iframe + proxy server via bare domain and API access via /api.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

157 lines
5.3 KiB

import threading
from flows import create_animations
6 months ago
import logging
import os
import re
import time
from datetime import datetime
import requests
from flask import Flask, Response, render_template, request, send_from_directory
from prefect.deployments import run_deployment
PORT = 9021
PROTO = "https"
BARE = True
6 months ago
app = Flask(__name__)
logging.basicConfig(level=logging.DEBUG)
def deploy_name():
return datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S") + "Z"
def get_host():
host = os.environ.get("LIGHTNING_CLOUDSPACE_HOST")
if host is None:
default_host = os.environ.get("HOST_NAME", "0.0.0.0")
return f"{default_host}:{PORT}" if not BARE else f"{default_host}"
6 months ago
else:
return f"{PORT}-{host}" if not BARE else f"{host}"
6 months ago
@app.route("/iframe")
@app.route("/iframe/")
@app.route("/iframe/<path:subpath>")
def home(subpath="images/animations/", proto=PROTO):
6 months ago
host = get_host()
initial_url = f"{proto}://{host}/{subpath}"
api_url = f"{proto}://{host}/api"
6 months ago
return render_template(
"index.html", initial_url=initial_url, host=f"{proto}://{host}", api_url=api_url
6 months ago
)
@app.route("/api", methods=["POST"])
def handle_api():
data = request.json # This assumes you're sending JSON data.
url = data.get("url")
if not url.endswith("/"):
url += "/"
logging.debug(f"Received URL: {url}")
params = {"url": url, "limit": 24 * 60, "ext": None}
response = run_deployment(
name="create-animations/noaa-animate",
parameters=params,
flow_run_name=f"{deploy_name()}.webapp.{url}",
)
# response is a FlowRun - need to get what we want from it.
# Process the data as needed.
return {
"status": "success",
"message": f"{url} processed successfully",
# "response": response,
}, 200
@app.route("/videos/<path:filename>")
def custom_static(filename):
return send_from_directory("./out", filename)
6 months ago
@app.route("/", methods=["GET"])
@app.route("/<path:url>", methods=["GET"])
def proxy(url="", proto=PROTO):
6 months ago
original_base_url = "https://services.swpc.noaa.gov"
host = get_host()
proxy_base_url = f"{proto}://{host}/"
6 months ago
target_url = f"{original_base_url}/{url}"
logging.debug(f"Fetching URL: {target_url}")
try:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
}
response = requests.get(target_url, headers=headers, stream=True)
excluded_headers = [
"content-encoding",
"content-length",
"transfer-encoding",
"connection",
]
headers = [
(name, value)
for (name, value) in response.raw.headers.items()
if name.lower() not in excluded_headers
]
if "text/html" in response.headers.get("Content-Type", ""):
content = response.content.decode("utf-8")
content = re.sub(r"'http://", "'https://", content)
content = re.sub(
r"https?://services.swpc.noaa.gov", proxy_base_url, content
)
content = content.replace(
"</body>",
f"""
<script>
window.addEventListener('load', function() {{
var hasSufficientImages = false;
var observer = new MutationObserver(function(mutations) {{
mutations.forEach(function(mutation) {{
if (mutation.type === 'childList') {{
console.log('observer');
checkImages();
}}
}});
}});
observer.observe(document.body, {{ childList: true, subtree: true }});
function checkImages() {{
var links = document.querySelectorAll('a');
var imageLinkRegex = /\.(jpg|jpeg|png)(\\?.*)?(#.*)?$/i;
var numImages = Array.from(links).filter(link => imageLinkRegex.test(link.href)).length;
console.log('Number of eligible links:', numImages);
hasSufficientImages = numImages >= 60;
window.parent.postMessage({{ type: 'urlUpdate', url: '{original_base_url}/{url}', eligible: hasSufficientImages }}, '*');
}}
// Run initial check in case content is already loaded
console.log('initial');
checkImages();
}});
</script>
</body>""",
)
content = content.encode("utf-8")
return Response(content, status=response.status_code, headers=headers)
else:
return Response(
response.content, status=response.status_code, headers=headers
)
except Exception as e:
logging.error(f"Error fetching URL: {e}")
return Response(f"Error fetching URL: {e}", status=500)
if __name__ == "__main__":
threading.Thread(target=create_animations.serve, daemon=True, kwargs={"name": "noaa-animate", "limit": 8}).start()
6 months ago
app.run(host="0.0.0.0", port=9021, debug=True)