stream_sampler.py (2576B)
1 import aiohttp 2 import asyncio 3 import datetime 4 import logging 5 import os 6 import random 7 import subprocess 8 import time 9 10 from tempfile import mkstemp 11 12 DEFAULT_HEADERS = { 13 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0", 14 "Accept": "*/*", 15 "Accept-Language": "en-US,en;q=0.9", 16 "Accept-Encoding": "gzip, deflate, br, zstd", 17 "Origin": "https://www.surfline.com", 18 "DNT": "1", 19 "Referer": "https://www.surfline.com/", 20 "Cache-Control": "no-cache", 21 } 22 23 class StreamSampler: 24 def __init__(self, cam_name, data_dir="./data"): 25 self.base_url = f"https://hls.cdn-surfline.com/oregon/wc-{cam_name}" 26 self.cam_name = cam_name 27 self.data_dir = data_dir 28 29 async def get_recent_frames(self): 30 async with aiohttp.ClientSession( 31 raise_for_status=True, 32 skip_auto_headers=list(DEFAULT_HEADERS.keys()), 33 headers=DEFAULT_HEADERS, 34 ) as session: 35 chunk_url = await self.__get_recent_chunk_url(session) 36 chunk_file = await self.__get_recent_chunk(session, chunk_url) 37 return self.__extract_frames(chunk_file) 38 39 async def __get_recent_chunk_url(self, session): 40 url = f"{self.base_url}/playlist.m3u8" 41 async with session.get(url) as res: 42 chunklist = await res.text() 43 # Only check last/final chunk from chunklist. 44 *_, chunk = [l for l in chunklist.splitlines() if not l.startswith("#")] 45 return f"{self.base_url}/{chunk}" 46 47 async def __get_recent_chunk(self, session, chunk_url): 48 async with session.get(chunk_url) as res: 49 bytes = await res.read() 50 fd, chunk_file = mkstemp() 51 with os.fdopen(fd, "wb") as f: 52 f.write(bytes) 53 return chunk_file 54 55 def __extract_frames(self, chunk_file): 56 dt = datetime.datetime.now() 57 day, hour, ts = dt.strftime("%m%d%Y"), dt.strftime("%H"), dt.strftime("%s") 58 frames_dir = f"{self.data_dir}/{self.cam_name}/{day}/{hour}" 59 if not os.path.exists(frames_dir): 60 os.makedirs(frames_dir) 61 subprocess.run(["ffmpeg", 62 "-hide_banner", "-loglevel", "3", 63 "-i", chunk_file, 64 "-vf", "select=eq(pict_type\,I)", 65 "-vsync", "vfr", 66 f"{frames_dir}/{ts}-thumb-%04d.jpg"]) 67 os.remove(chunk_file) # cleanup 68 # Return filenames of (ffmpeg generated) frames to caller. 69 return [f for f in os.scandir(frames_dir) if f.name.startswith(f"{ts}-thumb")]