release: 1.3.2

fix #13
fix #14
This commit is contained in:
acgnhik 2022-01-29 10:08:06 +08:00
parent 2f65aa54ca
commit 451e744ceb
9 changed files with 74 additions and 40 deletions

View File

@ -1,5 +1,10 @@
# 更新日志
## 1.3.2
- 修复录制错误: `AssertionError: Invalid Tag`
- 修复前端 https 下不能显示主播头像
## 1.3.1
- 修复没成功修复的录制异常 `IndexError: list index out of range`

View File

@ -1,4 +1,4 @@
__prog__ = 'blrec'
__version__ = '1.3.1'
__version__ = '1.3.2'
__github__ = 'https://github.com/acgnhiki/blrec'

View File

@ -8,6 +8,7 @@ from lxml import html
from lxml.html.clean import clean_html
from .typing import ResponseData
from ..utils.url import ensure_scheme
__all__ = 'LiveStatus', 'RoomInfo', 'UserInfo'
@ -38,9 +39,9 @@ class RoomInfo:
@staticmethod
def from_data(data: ResponseData) -> 'RoomInfo':
if (timestamp := data.get('live_start_time', None)) is not None:
if (timestamp := data.get('live_start_time')) is not None:
live_start_time = cast(int, timestamp)
elif (time_string := data.get('live_time', None)) is not None:
elif (time_string := data.get('live_time')) is not None:
if time_string == '0000-00-00 00:00:00':
live_start_time = 0
else:
@ -49,6 +50,9 @@ class RoomInfo:
else:
raise ValueError(f'Failed to init live_start_time: {data}')
if (cover := data.get('cover') or data.get('user_cover', '')):
cover = ensure_scheme(cover, 'https')
if (description := data['description']):
description = re.sub(r'<br\s*/?>', '\n', description)
tree = html.fromstring(description)
@ -66,7 +70,7 @@ class RoomInfo:
live_start_time=live_start_time,
online=int(data['online']),
title=data['title'],
cover=data.get('cover', None) or data.get('user_cover', None),
cover=cover,
tags=data['tags'],
description=description,
)
@ -86,7 +90,7 @@ class UserInfo:
return UserInfo(
name=data['name'],
gender=data['sex'],
face=data['face'],
face=ensure_scheme(data['face'], 'https'),
uid=data['mid'],
level=data['level'],
sign=data['sign'],

View File

@ -25,8 +25,8 @@ from tenacity import (
stop_after_delay,
stop_after_attempt,
retry_if_result,
retry_if_exception,
retry_if_exception_type,
retry_if_not_exception_type,
Retrying,
TryAgain,
)
@ -268,7 +268,7 @@ class StreamRecorder(
reraise=True,
retry=(
retry_if_result(lambda r: not self._stopped) |
retry_if_exception(lambda e: not isinstance(e, OSError))
retry_if_not_exception_type((OSError, NotImplementedError))
),
wait=wait_exponential_for_same_exceptions(max=60),
before_sleep=before_sleep_log(logger, logging.DEBUG, 'main_loop'),

View File

@ -1,10 +1,21 @@
class FlvStreamCorruptedError(Exception):
class FlvDataError(ValueError):
...
class FlvFileCorruptedError(Exception):
class InvalidFlvHeaderError(FlvDataError):
...
class InvalidFlvTagError(FlvDataError):
...
class FlvStreamCorruptedError(FlvDataError):
...
class FlvFileCorruptedError(FlvDataError):
...

View File

@ -4,6 +4,7 @@ import attr
from .struct_io import StructReader, StructWriter
from .io_protocols import RandomIO
from .exceptions import InvalidFlvHeaderError, InvalidFlvTagError
from .models import (
FlvTag,
TagType,
@ -38,7 +39,8 @@ class FlvParser:
def parse_header(self) -> FlvHeader:
signature = self._reader.read(3).decode()
assert signature == 'FLV', 'Not a FLV file'
if signature != 'FLV':
raise InvalidFlvHeaderError(signature)
version = self._reader.read_ui8()
type_flag = self._reader.read_ui8()
data_offset = self._reader.read_ui32()
@ -65,7 +67,7 @@ class FlvParser:
elif tag_header.tag_type == TagType.SCRIPT:
tag = ScriptTag(**arguments)
else:
raise ValueError(tag_header.tag_type)
raise InvalidFlvTagError(tag_header.tag_type)
if no_body:
self._stream.seek(tag.tag_end_offset)
@ -78,30 +80,32 @@ class FlvParser:
def parse_flv_tag_header(self) -> FlvTagHeader:
flag = self._reader.read_ui8()
filtered = bool(flag & 0b0010_0000)
assert not filtered, 'Unsupported Filtered FLV Tag'
if filtered:
raise NotImplementedError('Unsupported Filtered FLV Tag')
tag_type = TagType(flag & 0b0001_1111)
data_size = self._reader.read_ui24()
assert data_size > 0, 'Invalid Tag'
timestamp = self._reader.read_ui24()
timestamp_extended = self._reader.read_ui8()
timestamp = timestamp_extended << 24 | timestamp
stream_id = self._reader.read_ui24()
return FlvTagHeader(
tag_header = FlvTagHeader(
filtered, tag_type, data_size, timestamp, stream_id
)
if data_size <= 0:
raise InvalidFlvTagError(tag_header)
return tag_header
def parse_audio_tag_header(self) -> AudioTagHeader:
flag = self._reader.read_ui8()
sound_format = SoundFormat(flag >> 4)
if sound_format != SoundFormat.AAC:
raise NotImplementedError(
f'Unsupported sound format: {sound_format}'
)
sound_rate = SoundRate((flag >> 2) & 0b0000_0011)
sound_size = SoundSize((flag >> 1) & 0b0000_0001)
sound_type = SoundType(flag & 0b0000_0001)
if sound_format != SoundFormat.AAC:
aac_packet_type = None
else:
aac_packet_type = AACPacketType(self._reader.read_ui8())
aac_packet_type = AACPacketType(self._reader.read_ui8())
return AudioTagHeader(
sound_format, sound_rate, sound_size, sound_type, aac_packet_type
)
@ -110,14 +114,12 @@ class FlvParser:
flag = self._reader.read_ui8()
frame_type = FrameType(flag >> 4)
codec_id = CodecID(flag & 0b0000_1111)
if codec_id != CodecID.AVC:
avc_packet_type = None
composition_time = None
else:
avc_packet_type = AVCPacketType(self._reader.read_ui8())
composition_time = self._reader.read_ui24()
raise NotImplementedError(
f'Unsupported video codec: {codec_id}'
)
avc_packet_type = AVCPacketType(self._reader.read_ui8())
composition_time = self._reader.read_ui24()
return VideoTagHeader(
frame_type, codec_id, avc_packet_type, composition_time
)
@ -149,7 +151,7 @@ class FlvDumper:
elif tag.is_script_tag():
pass
else:
raise ValueError(tag.tag_type)
raise InvalidFlvTagError(tag.tag_type)
if tag.body is None:
self._stream.seek(tag.tag_end_offset)
@ -164,22 +166,25 @@ class FlvDumper:
self._writer.write_ui24(tag.stream_id)
def dump_audio_tag_header(self, tag: AudioTag) -> None:
if tag.sound_format != SoundFormat.AAC:
raise NotImplementedError(
f'Unsupported sound format: {tag.sound_format}'
)
self._writer.write_ui8(
(tag.sound_format.value << 4) |
(tag.sound_rate.value << 2) |
(tag.sound_size.value << 1) |
tag.sound_type.value
)
if tag.aac_packet_type is not None:
assert tag.sound_format == SoundFormat.AAC
self._writer.write_ui8(tag.aac_packet_type)
self._writer.write_ui8(tag.aac_packet_type)
def dump_video_tag_header(self, tag: VideoTag) -> None:
if tag.codec_id != CodecID.AVC:
raise NotImplementedError(
f'Unsupported video codec: {tag.codec_id}'
)
self._writer.write_ui8(
(tag.frame_type.value << 4) | tag.codec_id.value
)
if tag.codec_id == CodecID.AVC:
assert tag.avc_packet_type is not None
assert tag.composition_time is not None
self._writer.write_ui8(tag.avc_packet_type.value)
self._writer.write_ui24(tag.composition_time)
self._writer.write_ui8(tag.avc_packet_type.value)
self._writer.write_ui24(tag.composition_time)

View File

@ -207,7 +207,7 @@ class AudioTag(FlvTag):
sound_rate: SoundRate
sound_size: SoundSize
sound_type: SoundType
aac_packet_type: Optional[AACPacketType]
aac_packet_type: AACPacketType
@property
def header_size(self) -> int:
@ -230,8 +230,8 @@ class AudioTag(FlvTag):
class VideoTag(FlvTag):
frame_type: FrameType
codec_id: CodecID
avc_packet_type: Optional[AVCPacketType]
composition_time: Optional[int]
avc_packet_type: AVCPacketType
composition_time: int
@property
def header_size(self) -> int:

View File

@ -22,6 +22,7 @@ from .io import FlvReader, FlvWriter
from .io_protocols import RandomIO
from .utils import format_offest, format_timestamp
from .exceptions import (
FlvDataError,
FlvStreamCorruptedError,
AudioParametersChanged,
VideoParametersChanged,
@ -375,6 +376,8 @@ class StreamProcessor:
except EOFError:
logger.debug('The input stream exhausted')
break
except FlvDataError as e:
raise FlvStreamCorruptedError(repr(e))
except Exception as e:
logger.debug(f'Failed to read data, due to: {repr(e)}')
raise

6
src/blrec/utils/url.py Normal file
View File

@ -0,0 +1,6 @@
from urllib.parse import urlparse, urlunparse
from typing import Literal
def ensure_scheme(url: str, scheme: Literal['http', 'https']) -> str:
return urlunparse(urlparse(url)._replace(scheme=scheme))