You want one button on a screen that shows the live picture from a camera at a remote site. Today that view usually lives behind a browser sequence: start the VPN client, log in to the portal, open the camera page, click through to the video. The camera itself speaks HTTP, so your own program can fetch the same image the browser shows — a JPEG snapshot or an MJPEG stream — with one request. Below you will find where that image address hides, why a first attempt returns a page of code or a wall of symbols, how to stay logged in through a remote-access gateway, and how to pick a frame rate the uplink can carry. You need the camera's address, its login, and any language with an HTTP client; the snippets here are Python.
Why does the camera's address give you a page of code instead of a picture?
Because the address you requested is the camera's HTML index page: the browser runs that markup, then fetches the real image from a second URL, and your program stopped after step one, so it received text instead of pixels.
An HTTP GET to / or /index.html returns markup — links to style sheets, scripts and image elements, nothing more. None of it executes in your program. The picture you watch in the browser is a separate request the page fires after it loads: a JPEG snapshot address for stills, an MJPEG address for video. Request that address directly and the camera answers with bytes, not markup.
Some cameras answer an index request with a line such as "No HTML page found". That reply means the path serves no page at all and the stream lives elsewhere; the next section finds it. For the still-image case end to end, read grabbing images from an IP camera over an HTTP snapshot URL, which this guide extends to live views and remote gateways.
How do you find the exact snapshot URL in the browser's tools?
Open the camera page in a desktop browser, press F12, reload with the Network tab open, and filter for image and media requests: the request that repeats on its own is the live stream, and the one-shot .jpg request next to it is your snapshot address.
- Open the camera's web page and log in the way you always do.
- Press F12, switch to the Network tab, clear the list, then reload the page.
- Start the live view if it does not start on its own — the stream request only appears once video flows.
- Filter the list: type
jpg,cgiormjpegin the filter box, or use the Img and Media filters. - Right-click the repeating request and copy its address. One appearance means snapshot; a request whose size keeps growing means MJPEG.
- Paste the address into a new tab. If you see the bare picture with no page around it, you have the right URL.
Cameras name these endpoints in a handful of common shapes: /snapshot.jpg, /image.jpg, /video.cgi, /webcam/?action=stream. If the copied address carries credentials as user:pass@host, strip them and send authentication properly. Streams sometimes sit on a second port such as 8080; RTSP on port 554 is a separate protocol and needs a client library, so leave it out of a one-button viewer.
Why do the bytes come back as symbols and letters?
Because you are reading binary as text: the response body is a compressed JPEG, and printing it or storing it as a string scrambles it — write the raw bytes to a .jpg file instead and the picture opens intact.
A JPEG is a stream of bytes, not characters, and most of them are not printable. print() on the body gives exactly the wall of symbols you saw; the image was fine, the handling was not. Keep everything in bytes:
import requests
snapshot_url = "https://gw.example.net/proxy/192.168.1.2/snapshot.jpg" # str
frame_path = "frame.jpg" # str
resp = requests.get(snapshot_url, auth=("user", "pass"), timeout=5)
resp.raise_for_status()
with open(frame_path, "wb") as fh: # "wb", never "w"
fh.write(resp.content) # bytes, not resp.text
Then prove it with the first two bytes of the saved file:
with open(frame_path, "rb") as fh:
head = fh.read(2)
assert head == b"\xff\xd8" # every JPEG opens with FF D8
That check is the whole test. Every JPEG file starts with the two bytes FF D8, so a match means the camera sent an image no matter what the console showed. If the first byte is 0x3C — a < — you received an HTML page, which is the login problem two sections down. In PHP the same rule applies: hand the body to file_put_contents rather than echo. Swap in your own gateway address, path and credentials.
How do you turn one snapshot into a live view?
Poll the snapshot address about once per second — for a remote site that already reads as live — or open the camera's MJPEG stream, one long HTTP response that hands you JPEG frames back to back with boundary markers between them.
Polling is the remote-safe option. A timer fires, your program fetches one JPEG, overwrites frame.jpg, and the display reloads it. Reconnection costs nothing: the next tick starts a fresh request, so a dropped link loses one frame, not the session. Through a cloud relay one fetch typically takes 0.2-1 s of round trip, so poll at 1 s and the view stays current without queuing.
MJPEG is the LAN option. The camera answers with Content-Type: multipart/x-mixed-replace plus a boundary string, then streams parts without ending the response; each part carries one JPEG, usually with its own Content-Length. Honor that length for a tidy parser, or cut on the JPEG markers for a quick one:
resp_stream = session.get(mjpeg_url, stream=True) # response that stays open
buffer = b"" # bytes
while True:
buffer += resp_stream.raw.read(1024) # bytes from the open response
start = buffer.find(b"\xff\xd8") # JPEG start
end = buffer.find(b"\xff\xd9") # JPEG end
if start >= 0 and end > start:
frame = buffer[start:end + 2]
show_frame(frame) # your display call
buffer = buffer[end + 2:]
Check the loop the same way as the snapshot: count frames per minute and compare with the rate the camera reports. Long-lived streams also idle out at some gateways, which is the second reason polling wins remotely. If the frames must feed analysis rather than eyes, decoding barcodes from a live RTSP or MJPEG stream covers the consumption side. And if the device is a wrist camera with no web page at all, live wrist camera image access over a dedicated TCP port walks that variant.
Why does your program get the gateway's login page?
Because the request arrives without a session: the gateway insists on its own login before it will proxy any device, so your program must authenticate once, keep the cookie that comes back, and send it with every later request.
In the browser you log in to the portal, the portal sets a session cookie, and every device page then loads under a proxy path such as /proxy/192.168.1.2/. Your program repeats that sequence instead of skipping it:
session = requests.Session() # keeps cookies across calls
session.post(login_url, data=login_fields) # dict built from the login form
img = session.get(snapshot_url, timeout=5)
The keys of login_fields are the input names on the gateway's own login form — read the page source to get them. Many gateways also publish a web API with a token; prefer that over replaying the form. Spot the bounce cheaply: the body starts with < (0x3C) instead of FF D8, or the content type says text/html — discard the session, log in again, retry once. Keep credentials in an environment variable or a protected configuration file, never in the script you share.
How much bandwidth does a live view need?
One 1920 × 1080 JPEG at normal quality is about 150 KB, so a 1 fps poll uses roughly 1.2 Mbit/s and a 10 fps stream about 12 Mbit/s — pick the frame rate to fit the uplink, not the other way round.
The arithmetic, from the sensor up:
- 1920 × 1080 = 2,073,600 pixels.
- At about 0.6 bit per pixel, mid-quality surveillance JPEG, that is 2,073,600 × 0.6 = 1,244,160 bits, about 150 KB per frame.
- Poll at 1 fps: 150 KB × 8 = 1,200 kbit/s, about 1.2 Mbit/s.
- Stream at 10 fps: 150 KB × 10 × 8 = 12,000 kbit/s = 12 Mbit/s.
- Drop to 640 × 480: 307,200 pixels × 0.6 is about 23 KB per frame; at 2 fps that is roughly 0.4 Mbit/s.
Site broadband and cellular uplinks commonly deliver a few Mbit/s upstream, shared with PLC and SCADA traffic. So: a remote one-button viewer polls at 1 fps in 1080p, or 2 fps in VGA when the link is thin. A station on the plant LAN can take 10-15 fps MJPEG — 12-18 Mbit/s, comfortable on a switched 100 Mbit/s link.
What about the lens and the cable in front of the sensor?
The address can only carry what the sensor saw: a lens with the right focal length and image circle, plus a clean Ethernet run to the camera, decide whether the picture is worth fetching at all.
Say the camera watches a 4.0 m wide aisle from 6.0 m, on a 2/3-inch sensor that is 8.8 mm across:
That lands inside a 6-15 mm varifocal range, so one lens also covers a later re-aim without a reorder:
- Type
- CCTV varifocal
- Sensor format
- 2/3"
- Mount
- C-mount
- Resolution
- Up to 3 MP
On the cable side, distance and bending rule the link: for reach, bandwidth and drag-chain choices on camera cabling, see 5GBASE-T camera interface: bandwidth, cables and distance. Screw-locked connectors stop the vibration walk-out that later shows up as intermittent snapshots, and both parts sit under CCTV lenses and camera data cables in the shop:
- Type
- Camera data cables
- Length
- 15 m
When is an IP camera the wrong tool for the job?
When the picture must be measured rather than watched: counting, reading, gauging or triggering on product needs uncompressed, exposure-controlled, hardware-triggered frames, and an area scan camera over GigE or USB3 provides all three where a surveillance camera provides none.
A JPEG snapshot is lossy, the camera picks its own exposure and rate, and there is no trigger input. For a remote "running or stopped, bin full or empty" check, none of that matters — stay with this method.
The moment the image feeds a decision, switch: browse area scan cameras or the camera finder. The LS-CMR-0166 is a 12 MP USB3 area scan camera delivering 4000 × 3000 frames at 30 fps. Its rolling shutter suits still or slow scenes; for fast product, filter the finder for global shutter.
If resolution and aspect ratio drive the choice, picking a 16, 20 or 24 MP camera by aspect ratio and format lays out the trade, and commanding a GigE Vision camera over Ethernet shows the same fetch-and-save loop for a vision camera.
- Sensor format
- 1/1.7"
- Interface
- USB3
- Resolution
- 12 MP
- Shutter
- Rolling
When any link in the chain is unclear — gateway behavior, lens reach, camera grade — contact us; expert support comes with every order.
The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.
Questions
Can I use the camera's RTSP stream instead of an MJPEG URL?
You can, but it changes the toolchain: RTSP with H.264 runs on port 554, needs a client library and a codec, and its frames are not JPEGs, so the FF D8 check does not apply. For a one-button viewer, snapshot polling or MJPEG over plain HTTP is the shorter path; take RTSP when the camera offers nothing else or you must record.
Why does the URL work in my browser but time out in my program?
Three causes, in order of likelihood: the copied address carried a session token that has since expired; the gateway only accepts calls that look like the browser's, so send the same headers; or the camera allows one viewer at a time and your open browser tab holds the slot. Close the tab, copy the bare path, and authenticate in code.
How do I stop the gateway session from expiring during a long unattended run?
Do not try to keep it alive — detect and rebuild. When a poll returns a body that starts with a `<` byte instead of FF D8, discard the session, log in again and retry that poll once. An unattended viewer runs for weeks this way, while a session held open dies at the gateway's idle timeout.
How do I show the fetched frame in my own window or web page?
Write each poll to the same file path and point your image control or page image element at it, refreshing once per second. Browsers and operating systems cache images, so append a changing query string — a timestamp works — to the address the display reloads, and every refresh then shows the new frame.
Is one frame per second enough for a remote station view?
For presence and state — running or stopped, bin full or empty, door open or closed — 1 to 2 fps is enough, because one clear frame answers a scene-state question. For counting product, reading codes or catching fast events it is not; that is the trigger-and-area-scan-camera case, not a frame-rate fix.


