Compare commits

...

2 Commits

Author SHA1 Message Date
acgnhik
e8d655ef2b release: 1.2.4 2022-01-15 19:13:39 +08:00
acgnhiki
047c098271 release: 1.2.3 2021-12-11 17:54:54 +08:00
10 changed files with 92 additions and 35 deletions

View File

@@ -1,5 +1,15 @@
# 更新日志
## 1.2.4
- 修复回收空间时文件不存在异常
- 修复章节标记时间异常导致转封装出错
## 1.2.3
- 修复直播间标题有特殊字符导致文件创建失败
- 修复弹幕有特殊字符导致弹幕录制出错
## 1.2.2
- 避免 url 失效出现一直超时不能录制

View File

@@ -36,41 +36,41 @@ include_package_data = True
python_requires = >= 3.8
install_requires =
typing-extensions >= 3.10.0.0
fastapi >= 0.65.2, < 0.66.0
email_validator >=1.1.2, < 2.0.0
typer >= 0.3.2, < 0.4.0
aiohttp >= 3.6.2, < 3.7.0
requests >= 2.24.0, < 2.25.0
aiofiles >= 0.5.0, < 0.6.0
tenacity >= 6.2.0, < 6.3.0
colorama >= 0.4.3, < 0.5.0
humanize >= 3.2.0, < 3.3.0
tqdm >= 4.56.2, < 4.57.0
attrs >= 21.2.0, < 21.3.0
lxml >= 4.6.2, < 4.7.0
fastapi >= 0.70.0, < 0.71.0
email_validator >= 1.1.3, < 2.0.0
typer >= 0.4.0, < 0.5.0
aiohttp >= 3.8.1, < 4.0.0
requests >= 2.24.0, < 3.0.0
aiofiles >= 0.8.0, < 0.9.0
tenacity >= 8.0.1, < 9.0.0
colorama >= 0.4.4, < 0.5.0
humanize >= 3.13.1, < 4.0.0
tqdm >= 4.62.3, < 5.0.0
attrs >= 21.2.0, < 22.0.0
lxml >= 4.6.4, < 5.0.0
toml >= 0.10.2, < 0.11.0
psutil >= 5.8.0, < 5.9.0
rx >= 3.1.1, < 3.2.0
bitarray >= 2.2.5, < 2.3.0
brotli >= 1.0.9, < 1.1.0
uvicorn[standard] >=0.12.0, < 0.15.0
psutil >= 5.8.0, < 6.0.0
rx >= 3.2.0, < 4.0.0
bitarray >= 2.2.5, < 3.0.0
brotli >= 1.0.9, < 2.0.0
uvicorn[standard] >= 0.15.0, < 0.16.0
[options.extras_require]
dev =
flake8 >= 3.9.2, < 4.0.0
flake8 >= 4.0.1, < 5.0.0
mypy >= 0.910, < 1.000
setuptools >= 57.0.0, < 58.0.0
setuptools >= 59.4.0, < 60.0.0
wheel >= 0.37, < 0.38.0
build >= 0.5.1, < 0.6.0
build >= 0.7.0, < 0.8.0
# missing stub packages
types-requests >= 2.25.1, < 2.26.0
types-requests >= 2.26.1, < 3.0.0
types-aiofiles >= 0.1.7, < 0.2.0
types-toml >= 0.1.3, < 0.2.0
types-setuptools >= 57.0.0, < 58.0.0
types-toml >= 0.10.1, < 0.11.0
types-setuptools >= 57.4.4, < 58.0.0
speedups = aiohttp[speedups] >= 3.6.2, < 3.7.0
speedups = aiohttp[speedups] >= 3.8.1, < 4.0.0
[options.packages.find]
where = src

View File

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

View File

@@ -3,7 +3,6 @@ from typing import Any, Callable, Optional, Type, cast
from tenacity import wait_exponential, RetryCallState
from tenacity import _utils
from tenacity import compat as _compat
class wait_exponential_for_same_exceptions(wait_exponential):
@@ -25,7 +24,6 @@ class wait_exponential_for_same_exceptions(wait_exponential):
self._prev_exc_ts: Optional[float] = None
self._last_wait_time: float = 0
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state: RetryCallState) -> float:
if (
retry_state.outcome is not None and

View File

@@ -39,6 +39,7 @@ from ..bili.live import Live
from ..bili.typing import QualityNumber
from ..flv.stream_processor import StreamProcessor, BaseOutputFileManager
from ..utils.mixins import AsyncCooperationMix, AsyncStoppableMixin
from ..path import escape_path
from ..flv.exceptions import FlvStreamCorruptedError
from ..bili.exceptions import (
LiveRoomHidden, LiveRoomLocked, LiveRoomEncrypted, NoStreamUrlAvailable
@@ -330,6 +331,9 @@ class StreamRecorder(
logger.debug('Response received')
response.raise_for_status()
if self._stopped:
return
assert self._stream_processor is not None
self._stream_processor.process_stream(
io.BufferedReader(
@@ -501,10 +505,10 @@ class OutputFileManager(BaseOutputFileManager, AsyncCooperationMix):
date_time = datetime.fromtimestamp(self._start_time)
relpath = self.path_template.format(
roomid=self._live.room_id,
uname=self._live.user_info.name,
title=self._live.room_info.title,
area=self._live.room_info.area_name,
parent_area=self._live.room_info.parent_area_name,
uname=escape_path(self._live.user_info.name),
title=escape_path(self._live.room_info.title),
area=escape_path(self._live.room_info.area_name),
parent_area=escape_path(self._live.room_info.parent_area_name),
year=date_time.year,
month=str(date_time.month).rjust(2, '0'),
day=str(date_time.day).rjust(2, '0'),

View File

@@ -1,6 +1,8 @@
from __future__ import annotations
import html
import asyncio
import logging
import unicodedata
from typing import AsyncIterator, Final, List, Any
from lxml import etree
@@ -16,6 +18,9 @@ from .models import (
__all__ = 'DanmakuReader', 'DanmakuWriter'
logger = logging.getLogger(__name__)
class DanmakuReader: # TODO rewrite
def __init__(self, path: str) -> None:
self._path = path
@@ -146,7 +151,12 @@ class DanmakuWriter:
'user': dm.uname,
}
elem = etree.Element('d', attrib=attrib)
elem.text = dm.text
try:
elem.text = dm.text
except ValueError:
# ValueError: All strings must be XML compatible: Unicode or ASCII,
# no NULL bytes or control characters
elem.text = remove_control_characters(dm.text)
return ' ' + etree.tostring(elem, encoding='utf8').decode() + '\n'
def _serialize_gift_send_record(self, record: GiftSendRecord) -> str:
@@ -166,7 +176,10 @@ class DanmakuWriter:
value_serializer=record_value_serializer,
)
elem = etree.Element('sc', attrib=attrib)
elem.text = record.message
try:
elem.text = record.message
except ValueError:
elem.text = remove_control_characters(record.message)
return ' ' + etree.tostring(elem, encoding='utf8').decode() + '\n'
@@ -180,3 +193,7 @@ def record_value_serializer(
if not isinstance(value, str):
return str(value)
return value
def remove_control_characters(s: str) -> str:
return ''.join(c for c in s if unicodedata.category(c) != 'Cc')

View File

@@ -7,6 +7,12 @@ import asyncio
from functools import partial
from typing import Iterable, List
from tenacity import (
retry,
wait_none,
stop_after_attempt,
retry_if_exception_type,
)
from .helpers import delete_file, is_space_enough
from .space_monitor import SpaceMonitor, DiskUsage, SpaceEventListener
@@ -65,6 +71,11 @@ class SpaceReclaimer(SpaceEventListener, SwitchableMixin):
return True
return False
@retry(
retry=retry_if_exception_type(OSError),
wait=wait_none(),
stop=stop_after_attempt(3),
)
async def _get_record_file_paths(self, max_ctime: float) -> List[str]:
glob_path = os.path.join(self.path, '*/**/*.*')
paths: Iterable[Path]

View File

@@ -4,6 +4,7 @@ from .helpers import (
danmaku_path,
raw_danmaku_path,
extra_metadata_path,
escape_path,
)
@@ -13,4 +14,5 @@ __all__ = (
'danmaku_path',
'raw_danmaku_path',
'extra_metadata_path',
'escape_path',
)

View File

@@ -1,3 +1,4 @@
import re
import os
from pathlib import PurePath
@@ -8,6 +9,7 @@ __all__ = (
'danmaku_path',
'raw_danmaku_path',
'extra_metadata_path',
'escape_path',
)
@@ -31,3 +33,7 @@ def raw_danmaku_path(video_path: str) -> str:
def extra_metadata_path(video_path: str) -> str:
return video_path + '.meta.json'
def escape_path(path: str) -> str:
return re.sub(r'[\\/:*?"<>|]', '', path)

View File

@@ -1,3 +1,4 @@
import logging
import json
from typing import Iterable, cast
@@ -8,6 +9,9 @@ from ..flv.stream_processor import JoinPoint
from ..flv.helpers import make_comment_for_joinpoints
logger = logging.getLogger(__name__)
async def make_metadata_file(flv_path: str) -> str:
path = flv_path + '.meta'
async with aiofiles.open(path, 'wb') as f:
@@ -57,11 +61,16 @@ def _make_chapters(
result = ''
for i in range(1, len(timestamps)):
start = timestamps[i - 1]
end = timestamps[i]
if end < start:
logger.warning(f'Chapter end time {end} before start {start}')
end = start
result += f"""\
[CHAPTER]
TIMEBASE=1/1000
START={timestamps[i-1]}
END={timestamps[i]}
START={start}
END={end}
title=segment \\#{i}
"""
return result