diff --git a/cast-tesla b/cast-tesla
new file mode 100755
index 0000000..892b135
--- /dev/null
+++ b/cast-tesla
@@ -0,0 +1,202 @@
+#!/usr/bin/env python3
+import sys
+import os
+import subprocess
+import urllib.request
+import urllib.parse
+import mimetypes
+import http.server
+import threading
+import time
+
+RENDERER_IP = "10.0.0.11"
+RENDERER_PORT = 54321
+LOCAL_IP = "10.0.0.1"
+LOCAL_STREAM_PORT = 8201
+CONTROL_URL = f"http://{RENDERER_IP}:{RENDERER_PORT}/_urn:schemas-upnp-org:service:AVTransport_control"
+
+def send_soap(action, body_xml):
+ soap = f"""
+
+
+
+{body_xml}
+
+
+"""
+ headers = {
+ "SOAPAction": f"\"urn:schemas-upnp-org:service:AVTransport:1#{action}\"",
+ "Content-Type": "text/xml; charset=\"utf-8\""
+ }
+ req = urllib.request.Request(CONTROL_URL, data=soap.encode("utf-8"), headers=headers)
+ try:
+ with urllib.request.urlopen(req, timeout=5) as resp:
+ return resp.read().decode("utf-8")
+ except Exception as e:
+ print(f"Error executing {action}: {e}")
+ return None
+
+def cast_url(url):
+ print(f"Casting to TESLA TE-300 ({RENDERER_IP})...")
+ print(f"Media URL: {url}")
+ set_xml = f"0{url}"
+ send_soap("SetAVTransportURI", set_xml)
+ play_xml = "01"
+ send_soap("Play", play_xml)
+ print("Playback started on TV!")
+
+def stop():
+ print("Stopping playback on TESLA TE-300...")
+ send_soap("Stop", "0")
+ print("Stopped.")
+
+def pause():
+ print("Pausing playback...")
+ send_soap("Pause", "0")
+ print("Paused.")
+
+def status():
+ xml = send_soap("GetTransportInfo", "0")
+ if xml:
+ import xml.etree.ElementTree as ET
+ root = ET.fromstring(xml)
+ for state in root.iter("CurrentTransportState"):
+ print(f"Current State: {state.text}")
+
+class RangeRequestHandler(http.server.BaseHTTPRequestHandler):
+ target_file = None
+
+ def do_HEAD(self):
+ self.send_headers(head_only=True)
+
+ def do_GET(self):
+ self.send_headers(head_only=False)
+
+ def send_headers(self, head_only=False):
+ if not self.target_file or not os.path.exists(self.target_file):
+ self.send_error(404, "File Not Found")
+ return
+
+ file_size = os.path.getsize(self.target_file)
+ range_header = self.headers.get("Range")
+
+ start = 0
+ end = file_size - 1
+
+ if range_header:
+ try:
+ bytes_range = range_header.strip().split("=")[1]
+ s, e = bytes_range.split("-")
+ start = int(s) if s else 0
+ end = int(e) if e else file_size - 1
+ except:
+ pass
+
+ if range_header:
+ self.send_response(206)
+ self.send_header("Content-Range", f"bytes {start}-{end}/{file_size}")
+ else:
+ self.send_response(200)
+
+ length = (end - start) + 1
+ mime_type, _ = mimetypes.guess_type(self.target_file)
+ if not mime_type:
+ mime_type = "video/mp4"
+
+ self.send_header("Content-Type", mime_type)
+ self.send_header("Content-Length", str(length))
+ self.send_header("Accept-Ranges", "bytes")
+ self.end_headers()
+
+ if not head_only:
+ with open(self.target_file, "rb") as f:
+ f.seek(start)
+ remaining = length
+ while remaining > 0:
+ chunk = f.read(min(remaining, 65536))
+ if not chunk:
+ break
+ try:
+ self.wfile.write(chunk)
+ except (BrokenPipeError, ConnectionResetError):
+ break
+ remaining -= len(chunk)
+
+ def log_message(self, format, *args):
+ # Quiet logger
+ pass
+
+def serve_and_cast_local_file(filepath):
+ abs_path = os.path.abspath(filepath)
+ if not os.path.exists(abs_path):
+ print(f"File not found: {abs_path}")
+ sys.exit(1)
+
+ RangeRequestHandler.target_file = abs_path
+
+ # Run the streaming HTTP server daemon in background
+ class ThreadingServer(http.server.ThreadingHTTPServer):
+ daemon_threads = True
+
+ try:
+ server = ThreadingServer(("0.0.0.0", LOCAL_STREAM_PORT), RangeRequestHandler)
+ except OSError:
+ # Port already open, assume existing server is ready
+ server = None
+
+ if server:
+ t = threading.Thread(target=server.serve_forever, daemon=True)
+ t.start()
+
+ stream_url = f"http://{LOCAL_IP}:{LOCAL_STREAM_PORT}/{urllib.parse.quote(os.path.basename(abs_path))}"
+ cast_url(stream_url)
+
+ if server:
+ print("Serving stream in background. Press Ctrl+C to stop casting.")
+ try:
+ while True:
+ time.sleep(1)
+ except KeyboardInterrupt:
+ print("\nStopping...")
+ stop()
+
+def main():
+ if len(sys.argv) < 2:
+ print("Usage: cast-tesla ")
+ print("\nExamples on t520:")
+ print(" cast-tesla /mnt/kof17/video/Video8_02_fix.mp4 (file on boxC)")
+ print(" cast-tesla ~/Downloads/my_video.mp4 (local file on t520)")
+ print(" cast-tesla https://ocko-live.ssl.cdn.cra.cz/channels/ocko/playlist.m3u8")
+ print(" cast-tesla http://10.0.0.14:9000/radio.mp3")
+ print(" cast-tesla status")
+ print(" cast-tesla stop")
+ sys.exit(1)
+
+ arg = sys.argv[1]
+
+ if arg == "stop":
+ stop()
+ elif arg == "pause":
+ pause()
+ elif cmd_status := (arg == "status"):
+ status()
+ elif arg.startswith("http://") or arg.startswith("https://"):
+ cast_url(arg)
+ elif arg.startswith("/mnt/kof17") or arg.startswith("boxC:"):
+ clean_path = arg[5:] if arg.startswith("boxC:") else arg
+ print(f"Delegating to boxC: {clean_path}")
+ subprocess.run(["ssh", "boxC", f"cast-tesla '{clean_path}'"])
+ elif os.path.exists(arg):
+ serve_and_cast_local_file(arg)
+ else:
+ # Check if it exists on boxC
+ res = subprocess.run(["ssh", "boxC", f"test -e '{arg}'"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ if res.returncode == 0:
+ print(f"File found on boxC: {arg}")
+ subprocess.run(["ssh", "boxC", f"cast-tesla '{arg}'"])
+ else:
+ print(f"Error: Target '{arg}' not found locally or on boxC.")
+ sys.exit(1)
+
+if __name__ == "__main__":
+ main()