Download flv_bridge.py from Reooiq/Hode: direct link, hf CLI and curl.
- Browser
- Download file 4.8 kB
-
https://huggingface.co/spaces/Reooiq/Hode/resolve/main/flv_bridge.py
- Command line
-
hf download hf://spaces/Reooiq/Hode/flv_bridge.py
-
curl -L -o flv_bridge.py https://huggingface.co/spaces/Reooiq/Hode/resolve/main/flv_bridge.py
4.8 kB
| """Streaming legacy HEVC FLV (codec-id 12) -> Enhanced FLV hvc1. | |
| Only tag headers/container fields are changed. Encoded video payload, CTS and DTS | |
| are preserved. H.264 and already-enhanced FLV tags pass through unchanged. | |
| No whole-recording buffering, decoding, rescaling or frame-rate conversion. | |
| Extra guard for YouTube ingestion: | |
| FormatChanged is raised the moment a *different* video decoder configuration | |
| record (AVCDecoderConfigurationRecord / HEVCDecoderConfigurationRecord, i.e. | |
| SPS/PPS or VPS/SPS/PPS) shows up in the middle of a session. That is exactly | |
| what a phone does when the layout changes (solo <-> PK round) or when the | |
| encoder restarts after the app was backgrounded and resumed. | |
| Why it matters: `ffmpeg -c:v copy` cannot detect an input parameter change, so | |
| it silently forwards the new frames while the FLV it already sent to YouTube | |
| declares the old width/height in onMetaData and in the first sequence header. | |
| YouTube then stretches/re-locks the picture -> fast zoom in-out, stutter, | |
| a long "repeated clip", freeze, black flash. The caller restarts the sender on | |
| this exception; the fresh RTMPS session re-declares the new size cleanly | |
| (a ~1-3 s gap instead of minutes of broken video). | |
| This is pure byte inspection of tag headers: no decoding, no re-encoding, no | |
| rescaling, so the original picture quality and the original source are untouched. | |
| """ | |
| class FormatChanged(Exception): | |
| """Source video decoder configuration changed mid-stream.""" | |
| def _video_signature(body): | |
| """Fingerprint of a video sequence header, or None if `body` is not one.""" | |
| if not body:return None | |
| if body[0]&0x80: # Enhanced RTMP | |
| if body[0]&0x0F:return None # PacketType 0 == sequence header | |
| return bytes(body[1:5])+body[5:] # FourCC + decoder config record | |
| if (body[0]&15)==7 and len(body)>1 and body[1]==0: # legacy AVC sequence header | |
| return b'avc1'+body[5:] | |
| return None | |
| class FLVBridge: | |
| def __init__(self,detect_format_change=True): | |
| self.buffer=bytearray();self.header_done=False;self.converted_tags=0 | |
| self.detect_format_change=detect_format_change | |
| self.video_cfg=None;self.format_changes=0 | |
| def feed(self,chunk): | |
| self.buffer.extend(chunk);output=bytearray() | |
| if not self.header_done: | |
| if len(self.buffer)<9:return b'' | |
| if self.buffer[:3]!=b'FLV':raise ValueError('Source is not FLV') | |
| header_size=int.from_bytes(self.buffer[5:9],'big')+4 | |
| if not 13<=header_size<=1048576:raise ValueError('Invalid FLV header size') | |
| if len(self.buffer)<header_size:return b'' | |
| output.extend(self.buffer[:header_size]);del self.buffer[:header_size];self.header_done=True | |
| offset=0 | |
| while len(self.buffer)-offset>=11: | |
| header=bytearray(self.buffer[offset:offset+11]);size=int.from_bytes(header[1:4],'big');total=11+size+4 | |
| if len(self.buffer)-offset<total:break | |
| body=bytes(self.buffer[offset+11:offset+11+size]);tag_type=header[0] | |
| if tag_type & 0x20:raise ValueError('Filtered/encrypted FLV tag is unsupported') | |
| if (tag_type & 31)==9 and body and not (body[0]&0x80) and (body[0]&15)==12: | |
| if len(body)<5:raise ValueError('Truncated HEVC FLV video header') | |
| frame=(body[0]>>4)&7;packet=body[1] | |
| if packet==0: # HEVC configuration record, no composition time in SequenceStart | |
| body=bytes([0x80|(frame<<4)])+b'hvc1'+body[5:] | |
| elif packet==1: # CodedFrames includes the same signed 24-bit composition offset | |
| body=bytes([0x80|(frame<<4)|1])+b'hvc1'+body[2:] | |
| elif packet==2: | |
| body=bytes([0x80|(frame<<4)|2])+b'hvc1'+body[5:] | |
| else:raise ValueError('Unsupported legacy HEVC packet type') | |
| self.converted_tags+=1 | |
| if (tag_type & 31)==9: | |
| signature=_video_signature(body) | |
| if signature is not None: | |
| if self.video_cfg is None:self.video_cfg=signature | |
| elif signature!=self.video_cfg: | |
| self.format_changes+=1 | |
| if self.detect_format_change: | |
| raise FormatChanged('video decoder configuration changed mid-stream') | |
| self.video_cfg=signature | |
| header[1:4]=len(body).to_bytes(3,'big') | |
| output.extend(header);output.extend(body);output.extend((11+len(body)).to_bytes(4,'big')) | |
| offset+=total | |
| if offset:del self.buffer[:offset] | |
| if len(self.buffer)>17*1024*1024:raise ValueError('FLV packet buffer exceeds limit') | |
| return bytes(output) | |