diff --git a/CHANGELOG.md b/CHANGELOG.md index 9651ae3..57af5b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # 更新日志 +## 1.3.2 + +- 修复录制错误: `AssertionError: Invalid Tag` +- 修复前端 https 下不能显示主播头像 + ## 1.3.1 - 修复没成功修复的录制异常 `IndexError: list index out of range` diff --git a/src/blrec/__init__.py b/src/blrec/__init__.py index 35abae8..c070e97 100644 --- a/src/blrec/__init__.py +++ b/src/blrec/__init__.py @@ -1,4 +1,4 @@ __prog__ = 'blrec' -__version__ = '1.3.1' +__version__ = '1.3.2' __github__ = 'https://github.com/acgnhiki/blrec' diff --git a/src/blrec/bili/models.py b/src/blrec/bili/models.py index bf1e8c7..c5aab9d 100644 --- a/src/blrec/bili/models.py +++ b/src/blrec/bili/models.py @@ -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'', '\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'], diff --git a/src/blrec/core/stream_recorder.py b/src/blrec/core/stream_recorder.py index d5df229..e1cdfb0 100644 --- a/src/blrec/core/stream_recorder.py +++ b/src/blrec/core/stream_recorder.py @@ -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'), diff --git a/src/blrec/flv/exceptions.py b/src/blrec/flv/exceptions.py index 4037314..4f7fb3a 100644 --- a/src/blrec/flv/exceptions.py +++ b/src/blrec/flv/exceptions.py @@ -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): ... diff --git a/src/blrec/flv/format.py b/src/blrec/flv/format.py index 0973adb..c42df61 100644 --- a/src/blrec/flv/format.py +++ b/src/blrec/flv/format.py @@ -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) diff --git a/src/blrec/flv/models.py b/src/blrec/flv/models.py index 954fc8b..0b2dd43 100644 --- a/src/blrec/flv/models.py +++ b/src/blrec/flv/models.py @@ -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: diff --git a/src/blrec/flv/stream_processor.py b/src/blrec/flv/stream_processor.py index a8ceb98..dcba68b 100644 --- a/src/blrec/flv/stream_processor.py +++ b/src/blrec/flv/stream_processor.py @@ -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 diff --git a/src/blrec/utils/url.py b/src/blrec/utils/url.py new file mode 100644 index 0000000..15cced2 --- /dev/null +++ b/src/blrec/utils/url.py @@ -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))