mirror of
https://github.com/acgnhiki/blrec.git
synced 2024-12-28 09:30:13 +08:00
feat: enrich metadata
This commit is contained in:
parent
8b2444f6cc
commit
04aae19735
@ -1,5 +1,7 @@
|
||||
import asyncio
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from typing import Dict, List, cast
|
||||
|
||||
@ -23,6 +25,7 @@ from .typing import ApiPlatform, QualityNumber, ResponseData, StreamCodec, Strea
|
||||
|
||||
__all__ = ('Live',)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_INFO_PATTERN = re.compile(
|
||||
rb'<script>\s*window\.__NEPTUNE_IS_MY_WAIFU__\s*=\s*(\{.*?\})\s*</script>'
|
||||
@ -163,6 +166,14 @@ class Live:
|
||||
user_info_data = await self._appapi.get_user_info(uid)
|
||||
return UserInfo.from_app_api_data(user_info_data)
|
||||
|
||||
async def get_timestamp(self) -> int:
|
||||
try:
|
||||
ts = await self.get_server_timestamp()
|
||||
except Exception as e:
|
||||
logger.warning(f'Failed to get timestamp from server: {repr(e)}')
|
||||
ts = int(time.time())
|
||||
return ts
|
||||
|
||||
async def get_server_timestamp(self) -> int:
|
||||
# the timestamp on the server at the moment in seconds
|
||||
return await self._webapi.get_timestamp()
|
||||
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import TYPE_CHECKING, Any, Dict
|
||||
from typing import TYPE_CHECKING, Any, Dict, Union
|
||||
|
||||
from .. import __github__, __prog__, __version__
|
||||
from ..bili.helpers import get_quality_name
|
||||
@ -24,9 +24,28 @@ class MetadataProvider:
|
||||
return self._make_metadata()
|
||||
|
||||
def _make_metadata(self) -> Dict[str, Any]:
|
||||
tz = timezone(timedelta(hours=8))
|
||||
live_start_time = datetime.fromtimestamp(
|
||||
self._live.room_info.live_start_time, timezone(timedelta(hours=8))
|
||||
self._live.room_info.live_start_time, tz
|
||||
)
|
||||
if self._stream_recorder.stream_available_time is None:
|
||||
stream_available_time: Union[datetime, str] = 'N/A'
|
||||
else:
|
||||
stream_available_time = datetime.fromtimestamp(
|
||||
self._stream_recorder.stream_available_time, tz
|
||||
)
|
||||
if self._stream_recorder.hls_stream_available_time is None:
|
||||
hls_stream_available_time: Union[datetime, str] = 'N/A'
|
||||
else:
|
||||
hls_stream_available_time = datetime.fromtimestamp(
|
||||
self._stream_recorder.hls_stream_available_time, tz
|
||||
)
|
||||
if self._stream_recorder.record_start_time is None:
|
||||
record_start_time: Union[datetime, str] = 'N/A'
|
||||
else:
|
||||
record_start_time = datetime.fromtimestamp(
|
||||
self._stream_recorder.record_start_time, tz
|
||||
)
|
||||
|
||||
assert self._stream_recorder.real_quality_number is not None
|
||||
stream_quality = '{} ({}{})'.format(
|
||||
@ -46,6 +65,9 @@ B站直播录像
|
||||
分区:{self._live.room_info.parent_area_name} - {self._live.room_info.area_name}
|
||||
房间号:{self._live.room_info.room_id}
|
||||
开播时间:{live_start_time}
|
||||
开始推流时间: {stream_available_time}
|
||||
HLS流可用时间: {hls_stream_available_time}
|
||||
录像起始时间: {record_start_time}
|
||||
流主机: {self._stream_recorder.stream_host}
|
||||
流格式:{self._stream_recorder.stream_format}
|
||||
流画质:{stream_quality}
|
||||
@ -59,6 +81,9 @@ B站直播录像
|
||||
'Area': self._live.room_info.area_name,
|
||||
'ParentArea': self._live.room_info.parent_area_name,
|
||||
'LiveStartTime': str(live_start_time),
|
||||
'StreamAvailableTime': str(stream_available_time),
|
||||
'HLSStreamAvailableTime': str(hls_stream_available_time),
|
||||
'RecordStartTime': str(record_start_time),
|
||||
'StreamHost': self._stream_recorder.stream_host,
|
||||
'StreamFormat': self._stream_recorder.stream_format,
|
||||
'StreamQuality': stream_quality,
|
||||
|
@ -1,14 +1,9 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Tuple
|
||||
|
||||
import aiohttp
|
||||
from tenacity import retry, retry_if_exception_type, stop_after_delay, wait_exponential
|
||||
|
||||
from ..bili.live import Live
|
||||
from ..path import escape_path
|
||||
from ..utils.mixins import AsyncCooperationMixin
|
||||
@ -26,28 +21,9 @@ class PathProvider(AsyncCooperationMixin):
|
||||
self.path_template = path_template
|
||||
|
||||
def __call__(self) -> Tuple[str, int]:
|
||||
timestamp = self._get_timestamp()
|
||||
path = self._make_path(timestamp)
|
||||
return path, timestamp
|
||||
|
||||
def _get_timestamp(self) -> int:
|
||||
try:
|
||||
return self._get_server_timestamp()
|
||||
except Exception as e:
|
||||
logger.warning(f'Failed to get server timestamp: {repr(e)}')
|
||||
return self._get_local_timestamp()
|
||||
|
||||
def _get_local_timestamp(self) -> int:
|
||||
return int(time.time())
|
||||
|
||||
@retry(
|
||||
reraise=True,
|
||||
retry=retry_if_exception_type((asyncio.TimeoutError, aiohttp.ClientError)),
|
||||
wait=wait_exponential(multiplier=0.1, max=1),
|
||||
stop=stop_after_delay(3),
|
||||
)
|
||||
def _get_server_timestamp(self) -> int:
|
||||
return self._run_coroutine(self._live.get_server_timestamp())
|
||||
ts = self._run_coroutine(self._live.get_timestamp())
|
||||
path = self._make_path(ts)
|
||||
return path, ts
|
||||
|
||||
def _make_path(self, timestamp: int) -> str:
|
||||
date_time = datetime.fromtimestamp(timestamp)
|
||||
|
@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Iterator, Optional
|
||||
@ -366,12 +367,14 @@ class Recorder(
|
||||
async def on_live_ended(self, live: Live) -> None:
|
||||
logger.info('The live has ended')
|
||||
self._stream_available = False
|
||||
self._stream_recorder.stream_available_time = None
|
||||
await self._stop_recording()
|
||||
self._print_waiting_message()
|
||||
|
||||
async def on_live_stream_available(self, live: Live) -> None:
|
||||
logger.debug('The live stream becomes available')
|
||||
self._stream_available = True
|
||||
self._stream_recorder.stream_available_time = await live.get_timestamp()
|
||||
await self._stream_recorder.start()
|
||||
|
||||
async def on_live_stream_reset(self, live: Live) -> None:
|
||||
|
@ -40,6 +40,7 @@ class StreamRecorder(
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._live = live
|
||||
self.stream_format = stream_format
|
||||
self.fmp4_stream_timeout = fmp4_stream_timeout
|
||||
|
||||
@ -77,6 +78,26 @@ class StreamRecorder(
|
||||
def stream_host(self) -> str:
|
||||
return self._impl.stream_host
|
||||
|
||||
@property
|
||||
def record_start_time(self) -> Optional[int]:
|
||||
return self._impl.record_start_time
|
||||
|
||||
@property
|
||||
def stream_available_time(self) -> Optional[int]:
|
||||
return self._impl.stream_available_time
|
||||
|
||||
@stream_available_time.setter
|
||||
def stream_available_time(self, ts: Optional[int]) -> None:
|
||||
self._impl.stream_available_time = ts
|
||||
|
||||
@property
|
||||
def hls_stream_available_time(self) -> Optional[int]:
|
||||
return self._impl.hls_stream_available_time
|
||||
|
||||
@hls_stream_available_time.setter
|
||||
def hls_stream_available_time(self, ts: Optional[int]) -> None:
|
||||
self._impl.hls_stream_available_time = ts
|
||||
|
||||
@property
|
||||
def dl_total(self) -> int:
|
||||
return self._impl.dl_total
|
||||
@ -206,11 +227,15 @@ class StreamRecorder(
|
||||
return self._impl.stopped
|
||||
|
||||
async def _do_start(self) -> None:
|
||||
self.hls_stream_available_time = None
|
||||
stream_format = self.stream_format
|
||||
if stream_format == 'fmp4':
|
||||
logger.info('Waiting for the fmp4 stream becomes available...')
|
||||
available = await self._wait_fmp4_stream()
|
||||
if not available:
|
||||
if available:
|
||||
if self.stream_available_time is not None:
|
||||
self.hls_stream_available_time = await self._live.get_timestamp()
|
||||
else:
|
||||
logger.warning(
|
||||
'The specified stream format (fmp4) is not available '
|
||||
f'in {self.fmp4_stream_timeout} seconcds, '
|
||||
@ -265,6 +290,8 @@ class StreamRecorder(
|
||||
return
|
||||
|
||||
self._impl.remove_listener(self)
|
||||
stream_available_time = self._impl.stream_available_time
|
||||
hls_stream_available_time = self._impl.hls_stream_available_time
|
||||
|
||||
self._impl = cls(
|
||||
live=self._impl._live,
|
||||
@ -279,5 +306,7 @@ class StreamRecorder(
|
||||
)
|
||||
|
||||
self._impl.add_listener(self)
|
||||
self._impl.stream_available_time = stream_available_time
|
||||
self._impl.hls_stream_available_time = hls_stream_available_time
|
||||
|
||||
logger.debug(f'Changed stream recorder impl to {cls.__name__}')
|
||||
|
@ -110,6 +110,9 @@ class StreamRecorderImpl(
|
||||
self._threads: List[Thread] = []
|
||||
self._files: List[str] = []
|
||||
self._stream_profile: StreamProfile = {}
|
||||
self._record_start_time: Optional[int] = None
|
||||
self._stream_available_time: Optional[int] = None
|
||||
self._hls_stream_available_time: Optional[int] = None
|
||||
|
||||
def on_profile_updated(profile: StreamProfile) -> None:
|
||||
self._stream_profile = profile
|
||||
@ -119,6 +122,7 @@ class StreamRecorderImpl(
|
||||
def on_file_opened(args: Tuple[str, int]) -> None:
|
||||
logger.info(f"Video file created: '{args[0]}'")
|
||||
self._files.append(args[0])
|
||||
self._record_start_time = args[1]
|
||||
self._emit_event('video_file_created', *args)
|
||||
|
||||
def on_file_closed(path: str) -> None:
|
||||
@ -136,6 +140,26 @@ class StreamRecorderImpl(
|
||||
def stream_host(self) -> str:
|
||||
return self._stream_url_resolver.stream_host
|
||||
|
||||
@property
|
||||
def record_start_time(self) -> Optional[int]:
|
||||
return self._record_start_time
|
||||
|
||||
@property
|
||||
def stream_available_time(self) -> Optional[int]:
|
||||
return self._stream_available_time
|
||||
|
||||
@stream_available_time.setter
|
||||
def stream_available_time(self, ts: Optional[int]) -> None:
|
||||
self._stream_available_time = ts
|
||||
|
||||
@property
|
||||
def hls_stream_available_time(self) -> Optional[int]:
|
||||
return self._hls_stream_available_time
|
||||
|
||||
@hls_stream_available_time.setter
|
||||
def hls_stream_available_time(self, ts: Optional[int]) -> None:
|
||||
self._hls_stream_available_time = ts
|
||||
|
||||
@property
|
||||
def dl_total(self) -> int:
|
||||
return self._dl_statistics.count
|
||||
@ -266,6 +290,7 @@ class StreamRecorderImpl(
|
||||
def _reset(self) -> None:
|
||||
self._files.clear()
|
||||
self._stream_profile = {}
|
||||
self._record_start_time = None
|
||||
|
||||
async def _do_start(self) -> None:
|
||||
logger.debug('Starting stream recorder...')
|
||||
|
Loading…
Reference in New Issue
Block a user