almost live, but horrible hack for stream capture, and not live ffmpeg, just live save of images

This commit is contained in:
devdesk 2024-02-15 00:26:10 +02:00
parent 7d75ad7596
commit 17c7d0e555
6 changed files with 153 additions and 48 deletions

View File

@ -3,6 +3,29 @@
https://telavivmakers.org/tamiwiki/projects/thermalcam
### Starting the stream
#### Enable jumbo frames
```
sudo ip link set eth0 mtu 9000
```
#### Send start packet
You need to send a special packet.
Sending it via sudo because of raw sockets:
```bash
sudo ./venv/bin/python ./replay.py
```
To send it you need the capability to open sockets in raw mode, but that does not work well with scripts (see [1]
[1] setcap for executables, not helpful for python scripts:
```
setcap cap_net_raw,cap_net_admin=eip ./replay.py
```
### Rust lib usage
# if you don't already have a virtualenv. Linux specific, adjust to your OS.

138
decode.py Executable file → Normal file
View File

@ -1,9 +1,12 @@
#!/usr/bin/env python3
import argparse
from pathlib import Path
import os
import subprocess
from io import BytesIO
import numpy as np
from tqdm import tqdm
from datetime import datetime
import pandas as pd
import pcapng
from struct import unpack
@ -12,6 +15,7 @@ from PIL import Image
# Create the parser
parser = argparse.ArgumentParser(description="Process a pcap file.")
parser.add_argument("--live", action="store_true", help="Process images live")
# Add an argument for the pcap file, with a default value
parser.add_argument('input_file', nargs='?', default='in.pcap', help='The pcap file to process')
@ -19,31 +23,57 @@ parser.add_argument('input_file', nargs='?', default='in.pcap', help='The pcap f
# Parse the arguments
args = parser.parse_args()
# Now use args.input_file as the file to process
input_file = args.input_file
basename = os.path.splitext(os.path.basename(input_file))[0]
# TODO - probably a better way to do this
def live_capture():
while True:
# TODO: This is wrong:
# during scapy.all.sniff construction we lose about 27 packets.
# we work around it by capturing 2* packets(frame), so we just lose
# about half the frames.
cap = scapy.all.sniff(iface="enp1s0f0", count=32 * 2)
for pkt in cap:
l = len(bytes(pkt))
if l != 6972:
continue
data = bytes(pkt)
with open('udp.bytes', 'ab+') as fd:
fd.write(bytes(pkt.payload))
yield data
# Read packets from a pcap file
scanner = pcapng.scanner.FileScanner(open(input_file, "rb"))
blocks = tqdm(scanner)
# Helper function to safely get an attribute from an object
def tryget(obj, att):
if hasattr(obj, att):
return getattr(obj, att)
return None
if args.live:
print('live stream, import scapy')
import scapy.all
print('open stream')
stream = live_capture()
start = datetime.now()
timestamp = start.strftime('%Y%m%d_%H%M%S')
basename = f'live_{timestamp}'
blocks = tqdm(stream)
else:
# Now use args.input_file as the file to process
input_file = args.input_file
basename = os.path.splitext(os.path.basename(input_file))[0]
stream = open(input_file, 'rb')
# Read packets from a pcap file
scanner = pcapng.scanner.FileScanner(stream)
blocks = tqdm(scanner)
def rightsize(it):
for i, obj in enumerate(it):
if not hasattr(obj, 'packet_len'):
if isinstance(obj, bytes):
l = len(obj)
data = obj
else:
if not hasattr(obj, 'packet_len'):
continue
l = obj.packet_len
data = obj.packet_data
if l != 6972:
continue
len = obj.packet_len
if len != 6972:
continue
yield obj.packet_data
yield data
def removestart(it):
@ -52,7 +82,6 @@ def removestart(it):
yield x[0x2A:]
# Function to parse packet data
def parse(data):
hdr = 4 + 2 * 7 # Header length
@ -73,26 +102,38 @@ def parsed(it):
# Function to group data into frames
def frames(it):
current = []
#otherdata = []
for obj in it:
if obj['part'] == 0:
if len(current) > 0:
yield b"".join(current)
current = []
#otherdata = []
current.append(obj["data"])
#otherdata.append(obj)
if len(current) > 0:
yield b"".join(current)
WIDTH = 384
HEIGHT = 288
def iterimages(it, width, height, pixelformat=">H"):
def skip_bad_frames(it, width=WIDTH, height=HEIGHT):
for frame in it:
if len(frame) != width * height * 2: # 16 bpp
# Will be fixed when we stopped doing restarts
#print(f'{len(frame)} != {width} * {height} * 2')
continue
yield frame
def iterimages(it, width=WIDTH, height=HEIGHT, pixelformat=">H"):
for frame in it:
yield Image.fromarray(np.frombuffer(frame, dtype=pixelformat).reshape(width, height))
# Get frames and convert them to images
frames = frames(parsed(removestart(rightsize(blocks))))
images = iterimages(it=frames, width=384, height=288)
frames = skip_bad_frames(frames(parsed(removestart(rightsize(blocks)))))
# Create the directory for frames if not exists
frame_dir = f"frames/{basename}"
@ -100,26 +141,37 @@ if not os.path.exists(frame_dir):
os.makedirs(frame_dir)
# Save each image as a PNG file
for i, img in enumerate(images):
img.save(f'frames/{basename}/{basename}_{i:04}.png')
if not args.live:
images = iterimages(it=frames)
for i, img in enumerate(images):
img.save(f'frames/{basename}/{basename}_{i:04}.png')
ffmpeg_input = f"frames/{basename}/{basename}_%04d.png"
command = [
"ffmpeg",
"-y", # Overwrite output file without asking
"-hide_banner", # Hide banner
"-loglevel", "info", # Log level
"-f", "image2", # Input format
"-framerate", "25", # Framerate
"-i", ffmpeg_input, # Input file pattern
"-vf", "transpose=1", # Video filter for transposing
"-s", "384x288", # Size of one frame
"-vcodec", "libopenh264", # Video codec
"-pix_fmt", "yuv420p", # Pixel format: YUV 4:2:0
"thermal.mp4", # Output file in MP4 container
]
# Produce a video from the saved images
ffmpeg_input = f"frames/{basename}/{basename}_%04d.png"
command = [
"ffmpeg",
"-y", # Overwrite output file without asking
"-hide_banner", # Hide banner
"-loglevel", "info", # Log level
"-f", "image2", # Input format
"-framerate", "25", # Framerate
"-i", ffmpeg_input, # Input file pattern
"-vf", "transpose=1", # Video filter for transposing
"-s", "384x288", # Size of one frame
"-vcodec", "libx264", # Video codec
"-pix_fmt", "yuv420p", # Pixel format: YUV 4:2:0
"thermal.mp4", # Output file in MP4 container
]
subprocess.run(command)
print("to play: ffplay thermal.mp4")
subprocess.run(command)
print("to play: ffplay thermal.mp4")
else:
output = 'to_ffmpeg'
# live: write to named pipe
if not Path(output).exists():
print(f'making fifo at {output}')
os.mkfifo(output)
fd = open(output, 'wb')
for i, frame in enumerate(frames):
print(f'frame {i:3}')
Image.fromarray(np.frombuffer(frame, dtype='>H').reshape(WIDTH, HEIGHT)).save(f'test_{i:04}.png')
fd.write(frame)

9
listen.py Normal file
View File

@ -0,0 +1,9 @@
from socket import socket, AF_INET, SOCK_DGRAM
s = socket(AF_INET, SOCK_DGRAM)
s.bind(('', 8090))
while True:
d = s.recvfrom(1024)
print(d)

1
replay.py Normal file → Executable file
View File

@ -1,3 +1,4 @@
#!/usr/bin/env python3
#replay the "trigger" packet.
#this packets will start the source broadcasting its packets.

View File

@ -2,11 +2,31 @@
from pathlib import Path
from thermaldecoder import decode
import numpy as np
import subprocess
import matplotlib.pyplot as plt
# Create a directory to store the frames if it doesn't exist
root = Path('frames')
root.mkdir(exist_ok=True)
frames = list(decode('in.pcap'))
f = np.array(frames[0])
f.shape = (384, 288)
plt.imshow(f)
plt.show()
# Decode the frames from the pcap file
frames = list(decode('indesk.pcapng'))
# Iterate over the frames
for i, frame in enumerate(frames):
try:
# Convert the frame to an image file
img_path = root / f"frame_{i}.png"
f = np.array(frame)
f.shape = (384, 288)
plt.imshow(f)
plt.axis('off')
plt.savefig(img_path, bbox_inches='tight', pad_inches=0)
plt.close()
# Use ffmpeg to display the image
subprocess.run(['ffmpeg', '-i', str(img_path), '-vf', 'scale=800:600', '-framerate', '25', '-f', 'image2pipe', '-'], check=True)
except ValueError as e:
print(f"Error processing frame {i}: {e}")

Binary file not shown.