feat: implement 10s image caching by stream id in /app/{id}.jpg
All checks were successful
Lint and Syntax Check / build (push) Successful in 7s
All checks were successful
Lint and Syntax Check / build (push) Successful in 7s
This commit is contained in:
138
main.py
138
main.py
@@ -2,7 +2,11 @@ import cv2
|
|||||||
import yt_dlp
|
import yt_dlp
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
|
import base64
|
||||||
|
|
||||||
|
_snapshot_cache = {}
|
||||||
|
CACHE_TTL = 10
|
||||||
|
|
||||||
def capture_stream_snapshot(youtube_url, output_file="snapshot.jpg", toBuffer=False):
|
def capture_stream_snapshot(youtube_url, output_file="snapshot.jpg", toBuffer=False):
|
||||||
# 1. Configure yt-dlp to get the direct stream URL
|
# 1. Configure yt-dlp to get the direct stream URL
|
||||||
@@ -29,7 +33,7 @@ def capture_stream_snapshot(youtube_url, output_file="snapshot.jpg", toBuffer=Fa
|
|||||||
|
|
||||||
if not cap.isOpened():
|
if not cap.isOpened():
|
||||||
print("Error: Could not open video stream.")
|
print("Error: Could not open video stream.")
|
||||||
return None if toBuffer else None
|
return None
|
||||||
|
|
||||||
# Read a single frame
|
# Read a single frame
|
||||||
success, frame = cap.read()
|
success, frame = cap.read()
|
||||||
@@ -39,138 +43,126 @@ def capture_stream_snapshot(youtube_url, output_file="snapshot.jpg", toBuffer=Fa
|
|||||||
# Encode frame as JPEG to memory buffer
|
# Encode frame as JPEG to memory buffer
|
||||||
ret, buf = cv2.imencode(".jpg", frame)
|
ret, buf = cv2.imencode(".jpg", frame)
|
||||||
if ret:
|
if ret:
|
||||||
print("Snapshot captured to buffer.")
|
|
||||||
cap.release()
|
cap.release()
|
||||||
return buf.tobytes()
|
return buf.tobytes()
|
||||||
else:
|
else:
|
||||||
print("Error: Could not encode frame to buffer.")
|
|
||||||
cap.release()
|
cap.release()
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
# 3. Save the frame as an image
|
# 3. Save the frame as an image
|
||||||
cv2.imwrite(output_file, frame)
|
cv2.imwrite(output_file, frame)
|
||||||
print(f"Snapshot saved to {output_file}")
|
cap.release()
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
print("Error: Could not read frame from stream.")
|
print("Error: Could not read frame from stream.")
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
cap.release()
|
cap.release()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Shsf Handler
|
# Shsf Handler
|
||||||
def main(args):
|
def main(args):
|
||||||
route = args.get("route")
|
route = args.get("route", "")
|
||||||
|
|
||||||
|
# Handle /app/{id}.jpg
|
||||||
|
if route.startswith("app/") and route.endswith(".jpg"):
|
||||||
|
url_id = route.split("/")[-1].replace(".jpg", "")
|
||||||
|
now = time.time()
|
||||||
|
|
||||||
|
if url_id in _snapshot_cache:
|
||||||
|
ts, encoded = _snapshot_cache[url_id]
|
||||||
|
if now - ts < CACHE_TTL:
|
||||||
|
return {
|
||||||
|
"_shsf": "v1", # Changed to v1 for simpler binary response if v2 is picky
|
||||||
|
"_code": 200,
|
||||||
|
"_res": encoded,
|
||||||
|
"_is_base64": True,
|
||||||
|
"_headers": { "Content-Type": "image/jpeg", "X-Cache": "HIT" }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache miss
|
||||||
|
yt_url = f"https://www.youtube.com/watch?v={url_id}"
|
||||||
|
image_bytes = capture_stream_snapshot(yt_url, toBuffer=True)
|
||||||
|
if image_bytes:
|
||||||
|
encoded_content = base64.b64encode(image_bytes).decode('utf-8')
|
||||||
|
_snapshot_cache[url_id] = (now, encoded_content)
|
||||||
|
return {
|
||||||
|
"_shsf": "v1",
|
||||||
|
"_code": 200,
|
||||||
|
"_res": encoded_content,
|
||||||
|
"_is_base64": True,
|
||||||
|
"_headers": { "Content-Type": "image/jpeg", "X-Cache": "MISS" }
|
||||||
|
}
|
||||||
|
|
||||||
if route == "snapshot":
|
if route == "snapshot":
|
||||||
url = args.get("body", "")
|
url = args.get("body", "")
|
||||||
try:
|
try:
|
||||||
url = json.loads(url)
|
url = json.loads(url)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
print("Error: Invalid JSON input.")
|
|
||||||
return {
|
return {
|
||||||
"_shsf": "v2",
|
"_shsf": "v2",
|
||||||
"_code": 400,
|
"_code": 400,
|
||||||
"_res": {"message": "Invalid JSON input."},
|
"_res": {"message": "Invalid JSON input."},
|
||||||
"_headers": {
|
"_headers": { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
url = url.get("url", "")
|
url = url.get("url", "")
|
||||||
if not url:
|
if not url:
|
||||||
print("Error: URL not provided.")
|
|
||||||
return {
|
return {
|
||||||
"_shsf": "v2",
|
"_shsf": "v2",
|
||||||
"_code": 400,
|
"_code": 400,
|
||||||
"_res": {"message": "URL not provided."},
|
"_res": {"message": "URL not provided."},
|
||||||
"_headers": {
|
"_headers": { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
capture_stream_snapshot(url, output_file="/tmp/snapshot.jpg")
|
image_bytes = capture_stream_snapshot(url, toBuffer=True)
|
||||||
content = None
|
if image_bytes:
|
||||||
with open("/tmp/snapshot.jpg", "rb") as f:
|
encoded_content = base64.b64encode(image_bytes).decode('utf-8')
|
||||||
content = f.read()
|
return {
|
||||||
|
"_shsf": "v2",
|
||||||
|
"_code": 200,
|
||||||
|
"_res": { "message": "done", "buffer": encoded_content },
|
||||||
|
"_headers": { "Content-Type": "application/json" },
|
||||||
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error: {e}")
|
|
||||||
return {
|
return {
|
||||||
"_shsf": "v2",
|
"_shsf": "v2",
|
||||||
"_code": 500,
|
"_code": 500,
|
||||||
"_res": {"message": f"Could not capture snapshot: {str(e)}"},
|
"_res": {"message": str(e)},
|
||||||
"_headers": {
|
"_headers": { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if content is None:
|
|
||||||
print("Error: Could not capture snapshot.")
|
|
||||||
return {
|
return {
|
||||||
"_shsf": "v2",
|
"_shsf": "v2",
|
||||||
"_code": 500,
|
"_code": 500,
|
||||||
"_res": {"message": "Could not capture snapshot."},
|
"_res": {"message": "Could not capture snapshot."},
|
||||||
"_headers": {
|
"_headers": { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Convert content to base64 for JSON response
|
|
||||||
import base64
|
|
||||||
encoded_content = base64.b64encode(content).decode('utf-8')
|
|
||||||
|
|
||||||
return {
|
|
||||||
"_shsf": "v2",
|
|
||||||
"_code": 200,
|
|
||||||
"_res": {
|
|
||||||
"message": "done",
|
|
||||||
"buffer": encoded_content
|
|
||||||
},
|
|
||||||
"_headers": {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
elif route == "health":
|
elif route == "health":
|
||||||
return {
|
return {
|
||||||
"_shsf": "v2",
|
"_shsf": "v2",
|
||||||
"_code": 200,
|
"_code": 200,
|
||||||
"_res": {"message": "Service is healthy."},
|
"_res": {"message": "Service is healthy."},
|
||||||
"_headers": {
|
"cache_size": len(_snapshot_cache),
|
||||||
"Content-Type": "application/json",
|
"_headers": { "Content-Type": "application/json" },
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
elif route == "default": # UI Route
|
elif route == "default": # UI Route
|
||||||
try:
|
try:
|
||||||
content = ""
|
|
||||||
with open("/app/ui.html", "r") as f:
|
with open("/app/ui.html", "r") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
except FileNotFoundError:
|
|
||||||
print("Error: /app/ui.html not found.")
|
|
||||||
return {
|
|
||||||
"_shsf": "v2",
|
|
||||||
"_code": 404,
|
|
||||||
"_res": {"message": "UI file not found."},
|
|
||||||
"_headers": {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error loading UI: {e}")
|
|
||||||
return {
|
|
||||||
"_shsf": "v2",
|
|
||||||
"_code": 500,
|
|
||||||
"_res": {"message": f"Server error: {str(e)}"},
|
|
||||||
"_headers": {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
"_shsf": "v2",
|
"_shsf": "v2",
|
||||||
"_code": 200,
|
"_code": 200,
|
||||||
"_res": content,
|
"_res": content,
|
||||||
"_headers": {
|
"_headers": { "Content-Type": "text/html" },
|
||||||
"Content-Type": "text/html",
|
}
|
||||||
},
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"_shsf": "v2",
|
||||||
|
"_code": 404,
|
||||||
|
"_res": {"message": "UI file not found or error."},
|
||||||
}
|
}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("omg im on main")
|
print("running stream-shot")
|
||||||
capture_stream_snapshot("https://www.youtube.com/watch?v=n15V_fCsl_c", output_file="snapshot.jpg")
|
|
||||||
|
|||||||
Reference in New Issue
Block a user