diff --git a/.flake8 b/.flake8 index e54a711..164ef1e 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,6 @@ [flake8] max-line-length = 88 -ignore = D203, W504, W503 +ignore = D203, W504, W503, E203 exclude = __*, .*, diff --git a/setup.cfg b/setup.cfg index c0c5d7f..a236e8e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,7 +54,7 @@ install_requires = m3u8 >= 1.0.0, < 2.0.0 jsonpath == 0.82 psutil >= 5.8.0, < 6.0.0 - rx >= 3.2.0, < 4.0.0 + reactivex >= 4.0.0, < 5.0.0 bitarray >= 2.2.5, < 3.0.0 brotli >= 1.0.9, < 2.0.0 uvicorn[standard] >= 0.15.0, < 0.16.0 @@ -65,6 +65,7 @@ install_requires = mypy == 0.910 # https://github.com/samuelcolvin/pydantic/issues/3528 isort >= 5.10.1 black >= 22.3.0 + autoflake >= 1.4 setuptools >= 59.4.0 wheel >= 0.37 diff --git a/src/blrec/application.py b/src/blrec/application.py index 20b433b..9035866 100644 --- a/src/blrec/application.py +++ b/src/blrec/application.py @@ -7,8 +7,7 @@ import attr import psutil from . import __prog__, __version__ -from .flv.data_analyser import MetaData -from .core.stream_analyzer import StreamProfile +from .flv.operators import MetaData, StreamProfile from .disk_space import SpaceMonitor, SpaceReclaimer from .bili.helpers import ensure_room_id from .task import ( diff --git a/src/blrec/bili/exceptions.py b/src/blrec/bili/exceptions.py index 0581127..e1201e0 100644 --- a/src/blrec/bili/exceptions.py +++ b/src/blrec/bili/exceptions.py @@ -34,3 +34,7 @@ class NoStreamCodecAvailable(Exception): class NoStreamQualityAvailable(Exception): pass + + +class NoAlternativeStreamAvailable(Exception): + pass diff --git a/src/blrec/bili/live.py b/src/blrec/bili/live.py index c3cd24f..bd8f983 100644 --- a/src/blrec/bili/live.py +++ b/src/blrec/bili/live.py @@ -1,30 +1,27 @@ -import re -import json import asyncio +import json +import re from typing import Dict, List, cast import aiohttp from jsonpath import jsonpath -from tenacity import ( - retry, - wait_exponential, - stop_after_delay, - retry_if_exception_type, -) - +from tenacity import retry, retry_if_exception_type, stop_after_delay, wait_exponential from .api import AppApi, WebApi -from .models import LiveStatus, RoomInfo, UserInfo -from .typing import ( - ApiPlatform, StreamFormat, QualityNumber, StreamCodec, ResponseData -) from .exceptions import ( - LiveRoomHidden, LiveRoomLocked, LiveRoomEncrypted, NoStreamAvailable, - NoStreamFormatAvailable, NoStreamCodecAvailable, NoStreamQualityAvailable, + LiveRoomEncrypted, + LiveRoomHidden, + LiveRoomLocked, + NoAlternativeStreamAvailable, + NoStreamAvailable, + NoStreamCodecAvailable, + NoStreamFormatAvailable, + NoStreamQualityAvailable, ) +from .models import LiveStatus, RoomInfo, UserInfo +from .typing import ApiPlatform, QualityNumber, ResponseData, StreamCodec, StreamFormat - -__all__ = 'Live', +__all__ = ('Live',) _INFO_PATTERN = re.compile( @@ -34,9 +31,7 @@ _LIVE_STATUS_PATTERN = re.compile(rb'"live_status"\s*:\s*(\d)') class Live: - def __init__( - self, room_id: int, user_agent: str = '', cookie: str = '' - ) -> None: + def __init__(self, room_id: int, user_agent: str = '', cookie: str = '') -> None: self._room_id = room_id self._user_agent = user_agent self._cookie = cookie @@ -142,9 +137,7 @@ class Live: self._room_info = await self.get_room_info() @retry( - retry=retry_if_exception_type(( - asyncio.TimeoutError, aiohttp.ClientError, - )), + retry=retry_if_exception_type((asyncio.TimeoutError, aiohttp.ClientError)), wait=wait_exponential(max=10), stop=stop_after_delay(60), ) @@ -158,9 +151,7 @@ class Live: return RoomInfo.from_data(room_info_data) @retry( - retry=retry_if_exception_type(( - asyncio.TimeoutError, aiohttp.ClientError, - )), + retry=retry_if_exception_type((asyncio.TimeoutError, aiohttp.ClientError)), wait=wait_exponential(max=10), stop=stop_after_delay(60), ) @@ -176,14 +167,15 @@ class Live: # the timestamp on the server at the moment in seconds return await self._webapi.get_timestamp() - async def get_live_stream_urls( + async def get_live_stream_url( self, qn: QualityNumber = 10000, *, api_platform: ApiPlatform = 'android', stream_format: StreamFormat = 'flv', stream_codec: StreamCodec = 'avc', - ) -> List[str]: + select_alternative: bool = False, + ) -> str: if api_platform == 'android': info = await self._appapi.get_room_play_info(self._room_id, qn) else: @@ -193,23 +185,30 @@ class Live: streams = jsonpath(info, '$.playurl_info.playurl.stream[*]') if not streams: - raise NoStreamAvailable(qn, stream_format, stream_codec) - formats = jsonpath(streams, f'$[*].format[?(@.format_name == "{stream_format}")]') # noqa + raise NoStreamAvailable(stream_format, stream_codec, qn) + formats = jsonpath( + streams, f'$[*].format[?(@.format_name == "{stream_format}")]' + ) if not formats: - raise NoStreamFormatAvailable(qn, stream_format, stream_codec) - codecs = jsonpath(formats, f'$[*].codec[?(@.codec_name == "{stream_codec}")]') # noqa + raise NoStreamFormatAvailable(stream_format, stream_codec, qn) + codecs = jsonpath(formats, f'$[*].codec[?(@.codec_name == "{stream_codec}")]') if not codecs: - raise NoStreamCodecAvailable(qn, stream_format, stream_codec) + raise NoStreamCodecAvailable(stream_format, stream_codec, qn) codec = codecs[0] accept_qn = cast(List[QualityNumber], codec['accept_qn']) if qn not in accept_qn or codec['current_qn'] != qn: - raise NoStreamQualityAvailable(qn, stream_format, stream_codec) + raise NoStreamQualityAvailable(stream_format, stream_codec, qn) - return [ - i['host'] + codec['base_url'] + i['extra'] - for i in codec['url_info'] - ] + urls = [i['host'] + codec['base_url'] + i['extra'] for i in codec['url_info']] + + if not select_alternative: + return urls[0] + + try: + return urls[1] + except IndexError: + raise NoAlternativeStreamAvailable(stream_format, stream_codec, qn) def _check_room_play_info(self, data: ResponseData) -> None: if data.get('is_hidden'): diff --git a/src/blrec/core/danmaku_dumper.py b/src/blrec/core/danmaku_dumper.py index ed2c620..34e042e 100644 --- a/src/blrec/core/danmaku_dumper.py +++ b/src/blrec/core/danmaku_dumper.py @@ -13,7 +13,7 @@ from tenacity import ( from .. import __version__, __prog__, __github__ from .danmaku_receiver import DanmakuReceiver, DanmuMsg from .stream_recorder import StreamRecorder, StreamRecorderEventListener -from .statistics import StatisticsCalculator +from .statistics import Statistics from ..bili.live import Live from ..exception import exception_callback, submit_exception from ..event.event_emitter import EventListener, EventEmitter @@ -73,19 +73,19 @@ class DanmakuDumper( self._lock: asyncio.Lock = asyncio.Lock() self._path: Optional[str] = None self._files: List[str] = [] - self._calculator = StatisticsCalculator(interval=60) + self._statistics = Statistics(interval=60) @property def danmu_total(self) -> int: - return self._calculator.count + return self._statistics.count @property def danmu_rate(self) -> float: - return self._calculator.rate + return self._statistics.rate @property def elapsed(self) -> float: - return self._calculator.elapsed + return self._statistics.elapsed @property def dumping_path(self) -> Optional[str]: @@ -145,7 +145,7 @@ class DanmakuDumper( async def _do_dump(self) -> None: assert self._path is not None logger.debug('Started dumping danmaku') - self._calculator.reset() + self._statistics.reset() try: async with DanmakuWriter(self._path) as writer: @@ -169,14 +169,14 @@ class DanmakuDumper( logger.info(f"Danmaku file completed: '{self._path}'") await self._emit('danmaku_file_completed', self._path) logger.debug('Stopped dumping danmaku') - self._calculator.freeze() + self._statistics.freeze() async def _dumping_loop(self, writer: DanmakuWriter) -> None: while True: msg = await self._receiver.get_message() if isinstance(msg, DanmuMsg): await writer.write_danmu(self._make_danmu(msg)) - self._calculator.submit(1) + self._statistics.submit(1) elif isinstance(msg, GiftSendMsg): if not self.record_gift_send: continue diff --git a/src/blrec/core/flv_stream_recorder_impl.py b/src/blrec/core/flv_stream_recorder_impl.py index bea462c..1c21523 100644 --- a/src/blrec/core/flv_stream_recorder_impl.py +++ b/src/blrec/core/flv_stream_recorder_impl.py @@ -1,21 +1,14 @@ -import io import logging from typing import Optional -from urllib.parse import urlparse -import requests -import urllib3 -from tenacity import TryAgain -from tqdm import tqdm +from reactivex.scheduler import NewThreadScheduler from ..bili.live import Live from ..bili.typing import QualityNumber -from ..flv.exceptions import FlvDataError, FlvStreamCorruptedError -from ..flv.stream_processor import StreamProcessor -from .stream_analyzer import StreamProfile -from .stream_recorder_impl import StreamProxy, StreamRecorderImpl +from ..flv import operators as flv_ops +from .stream_recorder_impl import StreamRecorderImpl -__all__ = 'FLVStreamRecorderImpl', +__all__ = ('FLVStreamRecorderImpl',) logger = logging.getLogger(__name__) @@ -49,113 +42,29 @@ class FLVStreamRecorderImpl(StreamRecorderImpl): ) def _run(self) -> None: - logger.debug('Stream recorder thread started') - try: - with tqdm( - desc='Recording', - unit='B', - unit_scale=True, - unit_divisor=1024, - postfix=self._make_pbar_postfix(), - ) as progress_bar: - self._progress_bar = progress_bar - - self._stream_processor = StreamProcessor( - self._file_manager, - filesize_limit=self._filesize_limit, - duration_limit=self._duration_limit, - analyse_data=True, - dedup_join=True, - save_extra_metadata=True, - backup_timestamp=True, - ) - - def update_size(size: int) -> None: - progress_bar.update(size) - self._rec_calculator.submit(size) - - def update_stream_profile(profile: StreamProfile) -> None: - self._stream_profile = profile - - self._stream_processor.size_updates.subscribe(update_size) - self._stream_processor.stream_profile_updates.subscribe( - update_stream_profile - ) - - with requests.Session() as self._session: - self._main_loop() - except TryAgain: - pass - except Exception as e: - self._handle_exception(e) - finally: - self._stopped = True - if self._stream_processor is not None: - self._stream_processor.finalize() - self._stream_processor = None - self._progress_bar = None - self._dl_calculator.freeze() - self._rec_calculator.freeze() - self._emit_event('stream_recording_stopped') - logger.debug('Stream recorder thread stopped') - - def _streaming_loop(self) -> None: - url = self._get_live_stream_url() - - while not self._stopped: - try: - self._streaming(url) - except requests.exceptions.HTTPError as e: - # frequently occurred when the live just started or ended. - logger.warning(repr(e)) - self._defer_retry(1, 'streaming_loop') - # the url may has been forbidden or expired - # when the status code is 404 or 403 - if e.response.status_code in (403, 404): - url = self._get_live_stream_url() - except requests.exceptions.Timeout as e: - logger.warning(repr(e)) - except urllib3.exceptions.TimeoutError as e: - logger.warning(repr(e)) - except urllib3.exceptions.ProtocolError as e: - # ProtocolError('Connection broken: IncompleteRead( - logger.warning(repr(e)) - except requests.exceptions.ConnectionError as e: - logger.warning(repr(e)) - self._wait_for_connection_error() - except (FlvDataError, FlvStreamCorruptedError) as e: - logger.warning(repr(e)) - if not self._use_alternative_stream: - self._use_alternative_stream = True - else: - self._rotate_api_platform() - url = self._get_live_stream_url() - - def _streaming(self, url: str) -> None: - logger.debug(f'Requesting live stream... {url}') - self._stream_url = url - self._stream_host = urlparse(url).hostname or '' - - with self._session.get( - url, - stream=True, - headers=self._live.headers, - timeout=self.read_timeout, - ) as response: - logger.debug('Response received') - response.raise_for_status() - - if self._stopped: - return - - assert self._stream_processor is not None - self._stream_processor.set_metadata(self._make_metadata()) - - stream_proxy = StreamProxy(response.raw) - stream_proxy.size_updates.subscribe( - lambda n: self._dl_calculator.submit(n) + self._subscription = ( + self._stream_param_holder.get_stream_params() # type: ignore + .pipe( + self._stream_url_resolver, + self._stream_fetcher, + self._dl_statistics, + self._stream_parser, + self._request_exception_handler, + self._connection_error_handler, + flv_ops.process(), + self._cutter, + self._limiter, + self._join_point_extractor, + self._prober, + self._injector, + self._analyser, + self._dumper, + self._rec_statistics, + self._progress_bar, + self._exception_handler, ) - - self._stream_processor.process_stream( - io.BufferedReader(stream_proxy, buffer_size=8192) + .subscribe( + on_completed=lambda: self._emit_event('stream_recording_completed'), + scheduler=NewThreadScheduler(self._thread_factory('StreamRecorder')), ) + ) diff --git a/src/blrec/core/hls_stream_recorder_impl.py b/src/blrec/core/hls_stream_recorder_impl.py index e860e29..b93e924 100644 --- a/src/blrec/core/hls_stream_recorder_impl.py +++ b/src/blrec/core/hls_stream_recorder_impl.py @@ -1,51 +1,22 @@ -import io import logging -import time -from contextlib import suppress -from datetime import datetime -from queue import Empty, Queue -from threading import Condition, Event, Lock, Thread -from typing import Final, List, Optional, Set -from urllib.parse import urlparse +from typing import Optional -import m3u8 -import requests -import urllib3 -from m3u8.model import Segment -from ordered_set import OrderedSet -from tenacity import ( - RetryError, - Retrying, - TryAgain, - retry, - retry_if_exception_type, - retry_if_not_exception_type, - retry_if_result, - stop_after_delay, - wait_exponential, -) -from tqdm import tqdm +from reactivex import operators as ops +from reactivex.scheduler import NewThreadScheduler from ..bili.live import Live from ..bili.typing import QualityNumber -from ..flv.exceptions import FlvDataError, FlvStreamCorruptedError -from ..flv.stream_processor import StreamProcessor -from ..utils.mixins import SupportDebugMixin -from .stream_analyzer import StreamProfile, ffprobe -from .stream_recorder_impl import StreamProxy, StreamRecorderImpl -from .stream_remuxer import StreamRemuxer +from ..flv import operators as flv_ops +from . import operators as core_ops +from .stream_recorder_impl import StreamRecorderImpl -__all__ = 'HLSStreamRecorderImpl', +__all__ = ('HLSStreamRecorderImpl',) logger = logging.getLogger(__name__) -class FailedToFetchSegments(Exception): - pass - - -class HLSStreamRecorderImpl(StreamRecorderImpl, SupportDebugMixin): +class HLSStreamRecorderImpl(StreamRecorderImpl): def __init__( self, live: Live, @@ -71,417 +42,50 @@ class HLSStreamRecorderImpl(StreamRecorderImpl, SupportDebugMixin): filesize_limit=filesize_limit, duration_limit=duration_limit, ) - self._init_for_debug(self._live.room_id) - self._init_section_data: Optional[bytes] = None - self._ready_to_fetch_segments = Condition() - self._failed_to_fetch_segments = Event() - self._stream_analysed_lock = Lock() - self._last_seg_uris: OrderedSet[str] = OrderedSet() - self._MAX_LAST_SEG_URIS: Final[int] = 30 + + self._playlist_fetcher = core_ops.PlaylistFetcher(self._live, self._session) + self._playlist_resolver = core_ops.PlaylistResolver() + self._segment_fetcher = core_ops.SegmentFetcher(self._live, self._session) + self._segment_remuxer = core_ops.SegmentRemuxer(live) def _run(self) -> None: - logger.debug('Stream recorder thread started') - try: - if self._debug: - path = '{}/playlist-{}-{}.m3u8'.format( - self._debug_dir, - self._live.room_id, - datetime.now().strftime('%Y-%m-%d-%H%M%S-%f'), - ) - self._playlist_debug_file = open(path, 'wt', encoding='utf-8') - - self._session = requests.Session() - self._session.headers.update(self._live.headers) - - self._stream_remuxer = StreamRemuxer( - self._live.room_id, - remove_filler_data=True, - ) - self._segment_queue: Queue[Segment] = Queue(maxsize=1000) - self._segment_data_queue: Queue[bytes] = Queue(maxsize=100) - self._stream_host_available = Event() - - self._segment_fetcher_thread = Thread( - target=self._run_segment_fetcher, - name=f'SegmentFetcher::{self._live.room_id}', - daemon=True, - ) - self._segment_fetcher_thread.start() - - self._segment_data_feeder_thread = Thread( - target=self._run_segment_data_feeder, - name=f'SegmentDataFeeder::{self._live.room_id}', - daemon=True, - ) - self._segment_data_feeder_thread.start() - - self._stream_processor_thread = Thread( - target=self._run_stream_processor, - name=f'StreamProcessor::{self._live.room_id}', - daemon=True, - ) - self._stream_processor_thread.start() - - try: - self._main_loop() - finally: - if self._stream_processor is not None: - self._stream_processor.cancel() - self._stream_processor_thread.join(timeout=10) - self._segment_fetcher_thread.join(timeout=10) - self._segment_data_feeder_thread.join(timeout=10) - self._stream_remuxer.stop() - self._stream_remuxer.raise_for_exception() - self._last_seg_uris.clear() - del self._segment_queue - del self._segment_data_queue - except TryAgain: - pass - except Exception as e: - self._handle_exception(e) - finally: - self._stopped = True - with suppress(Exception): - self._playlist_debug_file.close() - self._emit_event('stream_recording_stopped') - logger.debug('Stream recorder thread stopped') - - def _streaming_loop(self) -> None: - url = self._get_live_stream_url() - - while not self._stopped: - try: - self._playlist_fetcher(url) - except requests.exceptions.HTTPError as e: - # frequently occurred when the live just started or ended. - logger.warning(repr(e)) - self._defer_retry(1, 'streaming_loop') - # the url may has been forbidden or expired - # when the status code is 404 or 403 - if e.response.status_code in (403, 404): - url = self._get_live_stream_url() - except requests.exceptions.ConnectionError as e: - logger.warning(repr(e)) - self._wait_for_connection_error() - except FailedToFetchSegments: - url = self._get_live_stream_url() - except RetryError as e: - logger.warning(repr(e)) - - def _playlist_fetcher(self, url: str) -> None: - self._stream_url = url - self._stream_host = urlparse(url).hostname or '' - self._stream_host_available.set() - with self._stream_analysed_lock: - self._stream_analysed = False - - while not self._stopped: - if self._failed_to_fetch_segments.is_set(): - with self._segment_queue.mutex: - self._segment_queue.queue.clear() - with self._ready_to_fetch_segments: - self._ready_to_fetch_segments.notify_all() - self._failed_to_fetch_segments.clear() - raise FailedToFetchSegments() - - content = self._fetch_playlist(url) - playlist = m3u8.loads(content, uri=url) - - if self._debug: - self._playlist_debug_file.write(content + '\n') - - if playlist.is_variant: - url = sorted( - playlist.playlists, - key=lambda p: p.stream_info.bandwidth - )[-1].absolute_uri - logger.debug(f'playlist changed to variant playlist: {url}') - self._stream_url = url - self._stream_host = urlparse(url).hostname or '' - with self._stream_analysed_lock: - self._stream_analysed = False - continue - - curr_seg_uris: Set[str] = set() - for seg in playlist.segments: - curr_seg_uris.add(seg.uri) - if seg.uri not in self._last_seg_uris: - self._segment_queue.put(seg, timeout=60) - self._last_seg_uris.add(seg.uri) - if len(self._last_seg_uris) > self._MAX_LAST_SEG_URIS: - self._last_seg_uris.pop(0) - - if ( - self._last_seg_uris and - not curr_seg_uris.intersection(self._last_seg_uris) - ): - logger.debug( - 'segments broken!\n' - f'last segments uris: {self._last_seg_uris}\n' - f'current segments uris: {curr_seg_uris}' - ) - with self._stream_analysed_lock: - self._stream_analysed = False - - if playlist.is_endlist: - logger.debug('playlist ended') - - time.sleep(1) - - def _run_segment_fetcher(self) -> None: - logger.debug('Segment fetcher thread started') - try: - self._segment_fetcher() - except Exception as e: - logger.exception(e) - self._handle_exception(e) - finally: - self._dl_calculator.freeze() - logger.debug('Segment fetcher thread stopped') - - def _segment_fetcher(self) -> None: - assert self._stream_remuxer is not None - init_section = None - self._init_section_data = None - num_of_continuously_failed = 0 - self._failed_to_fetch_segments.clear() - - while not self._stopped: - try: - seg = self._segment_queue.get(timeout=1) - except Empty: - continue - for attempt in Retrying( - reraise=True, - retry=( - retry_if_result(lambda r: not self._stopped) | - retry_if_not_exception_type((OSError, NotImplementedError)) + self._subscription = ( + self._stream_param_holder.get_stream_params() # type: ignore + .pipe( + self._stream_url_resolver, + ops.subscribe_on( + NewThreadScheduler(self._thread_factory('PlaylistFetcher')) ), - ): - if attempt.retry_state.attempt_number > 3: - break - with attempt: - try: - if ( - getattr(seg, 'init_section', None) and - ( - not init_section or - seg.init_section.uri != init_section.uri - ) - ): - data = self._fetch_segment( - seg.init_section.absolute_uri - ) - init_section = seg.init_section - self._init_section_data = data - self._segment_data_queue.put(data, timeout=60) - data = self._fetch_segment(seg.absolute_uri) - self._segment_data_queue.put(data, timeout=60) - except requests.exceptions.HTTPError as e: - logger.warning(f'Failed to fetch segment: {repr(e)}') - if e.response.status_code in (403, 404, 599): - num_of_continuously_failed += 1 - if num_of_continuously_failed >= 3: - self._failed_to_fetch_segments.set() - with self._ready_to_fetch_segments: - self._ready_to_fetch_segments.wait() - num_of_continuously_failed = 0 - self._failed_to_fetch_segments.clear() - break - except requests.exceptions.ConnectionError as e: - logger.warning(repr(e)) - self._wait_for_connection_error() - except RetryError as e: - logger.warning(repr(e)) - break - else: - num_of_continuously_failed = 0 - break - - def _run_segment_data_feeder(self) -> None: - logger.debug('Segment data feeder thread started') - try: - self._segment_data_feeder() - except Exception as e: - logger.exception(e) - self._handle_exception(e) - finally: - logger.debug('Segment data feeder thread stopped') - - def _segment_data_feeder(self) -> None: - assert self._stream_remuxer is not None - MAX_SEGMENT_DATA_CACHE = 3 - segment_data_cache: List[bytes] = [] - bytes_io = io.BytesIO() - segment_count = 0 - - def on_next(profile: StreamProfile) -> None: - self._stream_profile = profile - - def on_error(e: Exception) -> None: - logger.warning(f'Failed to analyse stream: {repr(e)}') - - while not self._stopped: - try: - data = self._segment_data_queue.get(timeout=1) - except Empty: - continue - else: - with self._stream_analysed_lock: - if not self._stream_analysed: - if self._init_section_data and not bytes_io.getvalue(): - bytes_io.write(self._init_section_data) - else: - bytes_io.write(data) - segment_count += 1 - - if segment_count >= 3: - ffprobe(bytes_io.getvalue()).subscribe( - on_next, on_error - ) - bytes_io = io.BytesIO() - segment_count = 0 - self._stream_analysed = True - - try: - if self._stream_remuxer.stopped: - self._stream_remuxer.start() - while True: - ready = self._stream_remuxer.wait(timeout=1) - if self._stopped: - return - if ready: - break - if segment_data_cache: - if self._init_section_data: - self._stream_remuxer.input.write( - self._init_section_data - ) - for cached_data in segment_data_cache: - if cached_data == self._init_section_data: - continue - self._stream_remuxer.input.write(cached_data) - - self._stream_remuxer.input.write(data) - except BrokenPipeError as e: - if not self._stopped: - logger.warning(repr(e)) - else: - logger.debug(repr(e)) - except ValueError as e: - if not self._stopped: - logger.warning(repr(e)) - else: - logger.debug(repr(e)) - - segment_data_cache.append(data) - if len(segment_data_cache) > MAX_SEGMENT_DATA_CACHE: - segment_data_cache.pop(0) - - def _run_stream_processor(self) -> None: - logger.debug('Stream processor thread started') - assert self._stream_remuxer is not None - - with tqdm( - desc='Recording', - unit='B', - unit_scale=True, - unit_divisor=1024, - postfix=self._make_pbar_postfix(), - ) as progress_bar: - self._progress_bar = progress_bar - - def update_size(size: int) -> None: - progress_bar.update(size) - self._rec_calculator.submit(size) - - self._stream_processor = StreamProcessor( - self._file_manager, - filesize_limit=self._filesize_limit, - duration_limit=self._duration_limit, - analyse_data=True, - dedup_join=True, - save_extra_metadata=True, - backup_timestamp=True, + self._playlist_fetcher, + self._request_exception_handler, + self._connection_error_handler, + self._playlist_resolver, + ops.observe_on( + NewThreadScheduler(self._thread_factory('SegmentFetcher')) + ), + self._segment_fetcher, + self._dl_statistics, + self._prober, + ops.observe_on( + NewThreadScheduler(self._thread_factory('SegmentRemuxer')) + ), + self._segment_remuxer, + ops.observe_on( + NewThreadScheduler(self._thread_factory('StreamRecorder')) + ), + self._stream_parser, + flv_ops.process(), + self._cutter, + self._limiter, + self._join_point_extractor, + self._injector, + self._analyser, + self._dumper, + self._rec_statistics, + self._progress_bar, + self._exception_handler, ) - self._stream_processor.size_updates.subscribe(update_size) - - try: - while not self._stopped: - while True: - ready = self._stream_remuxer.wait(timeout=1) - if self._stopped: - return - if ready: - break - - self._stream_host_available.wait() - self._stream_processor.set_metadata(self._make_metadata()) - - try: - self._stream_processor.process_stream( - StreamProxy( - self._stream_remuxer.output, - read_timeout=10, - ) # type: ignore - ) - except BrokenPipeError as e: - logger.debug(repr(e)) - except TimeoutError as e: - logger.debug(repr(e)) - self._stream_remuxer.stop() - except FlvDataError as e: - logger.warning(repr(e)) - self._stream_remuxer.stop() - except FlvStreamCorruptedError as e: - logger.warning(repr(e)) - self._stream_remuxer.stop() - except ValueError as e: - logger.warning(repr(e)) - self._stream_remuxer.stop() - except Exception as e: - if not self._stopped: - logger.exception(e) - self._handle_exception(e) - else: - logger.debug(repr(e)) - finally: - self._stream_processor.finalize() - self._progress_bar = None - self._rec_calculator.freeze() - logger.debug('Stream processor thread stopped') - - @retry( - retry=retry_if_exception_type(( - requests.exceptions.Timeout, - urllib3.exceptions.TimeoutError, - urllib3.exceptions.ProtocolError, - )), - wait=wait_exponential(multiplier=0.1, max=1), - stop=stop_after_delay(10), - ) - def _fetch_playlist(self, url: str) -> str: - response = self._session.get(url, timeout=3) - response.raise_for_status() - response.encoding = 'utf-8' - return response.text - - @retry( - retry=retry_if_exception_type(( - requests.exceptions.Timeout, - urllib3.exceptions.TimeoutError, - urllib3.exceptions.ProtocolError, - )), - wait=wait_exponential(multiplier=0.1, max=5), - stop=stop_after_delay(60), - ) - def _fetch_segment(self, url: str) -> bytes: - with self._session.get(url, stream=True, timeout=10) as response: - response.raise_for_status() - - bytes_io = io.BytesIO() - for chunk in response: - bytes_io.write(chunk) - self._dl_calculator.submit(len(chunk)) - - return bytes_io.getvalue() + .subscribe( + on_completed=lambda: self._emit_event('stream_recording_completed') + ) + ) diff --git a/src/blrec/core/metadata_provider.py b/src/blrec/core/metadata_provider.py new file mode 100644 index 0000000..0d86b13 --- /dev/null +++ b/src/blrec/core/metadata_provider.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from collections import OrderedDict +from datetime import datetime, timedelta, timezone +from typing import TYPE_CHECKING, Any, Dict + +from .. import __github__, __prog__, __version__ +from ..bili.helpers import get_quality_name +from ..bili.live import Live + +if TYPE_CHECKING: + from .stream_recorder_impl import StreamRecorderImpl + +__all__ = ('MetadataProvider',) + + +class MetadataProvider: + def __init__(self, live: Live, stream_recorder: StreamRecorderImpl) -> None: + super().__init__() + self._live = live + self._stream_recorder = stream_recorder + + def __call__(self) -> Dict[str, Any]: + return self._make_metadata() + + def _make_metadata(self) -> Dict[str, Any]: + live_start_time = datetime.fromtimestamp( + self._live.room_info.live_start_time, timezone(timedelta(hours=8)) + ) + + assert self._stream_recorder.real_quality_number is not None + stream_quality = '{} ({}{})'.format( + get_quality_name(self._stream_recorder.real_quality_number), + self._stream_recorder.real_quality_number, + ', bluray' if '_bluray' in self._stream_recorder.stream_url else '', + ) + + return { + 'Title': self._live.room_info.title, + 'Artist': self._live.user_info.name, + 'Date': str(live_start_time), + 'Comment': f'''\ +B站直播录像 +主播:{self._live.user_info.name} +标题:{self._live.room_info.title} +分区:{self._live.room_info.parent_area_name} - {self._live.room_info.area_name} +房间号:{self._live.room_info.room_id} +开播时间:{live_start_time} +流主机: {self._stream_recorder.stream_host} +流格式:{self._stream_recorder.stream_format} +流画质:{stream_quality} +录制程序:{__prog__} v{__version__} {__github__}''', + 'description': OrderedDict( + { + 'UserId': str(self._live.user_info.uid), + 'UserName': self._live.user_info.name, + 'RoomId': str(self._live.room_info.room_id), + 'RoomTitle': self._live.room_info.title, + 'Area': self._live.room_info.area_name, + 'ParentArea': self._live.room_info.parent_area_name, + 'LiveStartTime': str(live_start_time), + 'StreamHost': self._stream_recorder.stream_host, + 'StreamFormat': self._stream_recorder.stream_format, + 'StreamQuality': stream_quality, + 'Recorder': f'{__prog__} v{__version__} {__github__}', + } + ), + } diff --git a/src/blrec/core/operators/__init__.py b/src/blrec/core/operators/__init__.py new file mode 100644 index 0000000..67f3fae --- /dev/null +++ b/src/blrec/core/operators/__init__.py @@ -0,0 +1,34 @@ +from .connection_error_handler import ConnectionErrorHandler +from .exception_handler import ExceptionHandler +from .hls_prober import HLSProber, StreamProfile +from .playlist_fetcher import PlaylistFetcher +from .playlist_resolver import PlaylistResolver +from .progress_bar import ProgressBar +from .request_exception_handler import RequestExceptionHandler +from .segment_fetcher import InitSectionData, SegmentData, SegmentFetcher +from .segment_remuxer import SegmentRemuxer +from .sized_statistics import SizedStatistics +from .stream_fetcher import StreamFetcher +from .stream_parser import StreamParser +from .stream_statistics import StreamStatistics +from .stream_url_resolver import StreamURLResolver + +__all__ = ( + 'ConnectionErrorHandler', + 'ExceptionHandler', + 'HLSProber', + 'InitSectionData', + 'PlaylistFetcher', + 'PlaylistResolver', + 'ProgressBar', + 'RequestExceptionHandler', + 'SegmentData', + 'SegmentFetcher', + 'SegmentRemuxer', + 'SizedStatistics', + 'StreamFetcher', + 'StreamParser', + 'StreamProfile', + 'StreamStatistics', + 'StreamURLResolver', +) diff --git a/src/blrec/core/operators/connection_error_handler.py b/src/blrec/core/operators/connection_error_handler.py new file mode 100644 index 0000000..58196ad --- /dev/null +++ b/src/blrec/core/operators/connection_error_handler.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import logging +import time +from typing import Optional, TypeVar + +import aiohttp +import requests +from reactivex import Observable, abc + +from ...bili.live import Live +from ...utils import operators as utils_ops +from ...utils.mixins import AsyncCooperationMixin + +__all__ = ('ConnectionErrorHandler',) + + +logger = logging.getLogger(__name__) + +_T = TypeVar('_T') + + +class ConnectionErrorHandler(AsyncCooperationMixin): + def __init__( + self, + live: Live, + *, + disconnection_timeout: Optional[int] = None, + check_interval: int = 3, + ) -> None: + super().__init__() + self._live = live + self.disconnection_timeout = disconnection_timeout or 600 # seconds + self.check_interval = check_interval + + def __call__(self, source: Observable[_T]) -> Observable[_T]: + return self._handle(source).pipe( + utils_ops.retry(should_retry=self._should_retry) + ) + + def _handle(self, source: Observable[_T]) -> Observable[_T]: + def subscribe( + observer: abc.ObserverBase[_T], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + def on_error(exc: Exception) -> None: + try: + raise exc + except ( + aiohttp.ClientConnectionError, + requests.exceptions.ConnectionError, + ) as e: + logger.warning(repr(e)) + if self._wait_for_connection_error(): + observer.on_error(exc) + else: + observer.on_completed() + except Exception: + pass + observer.on_error(exc) + + return source.subscribe( + observer.on_next, on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) + + def _should_retry(self, exc: Exception) -> bool: + if isinstance( + exc, (aiohttp.ClientConnectionError, requests.exceptions.ConnectionError) + ): + return True + else: + return False + + def _wait_for_connection_error(self) -> bool: + timeout = self.disconnection_timeout + logger.info(f'Waiting {timeout} seconds for connection recovery... ') + timebase = time.monotonic() + while not self._run_coroutine(self._live.check_connectivity()): + if timeout is not None and time.monotonic() - timebase > timeout: + logger.error(f'Connection not recovered in {timeout} seconds') + return False + time.sleep(self.check_interval) + else: + logger.info('Connection recovered') + return True diff --git a/src/blrec/core/operators/exception_handler.py b/src/blrec/core/operators/exception_handler.py new file mode 100644 index 0000000..033f53a --- /dev/null +++ b/src/blrec/core/operators/exception_handler.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +import errno +import logging +from typing import Optional, TypeVar + +from reactivex import Observable, abc + +from ...bili.exceptions import LiveRoomEncrypted, LiveRoomHidden, LiveRoomLocked +from ...utils import operators as utils_ops +from ...utils.mixins import AsyncCooperationMixin + +__all__ = ('ExceptionHandler',) + + +logger = logging.getLogger(__name__) + +_T = TypeVar('_T') + + +class ExceptionHandler(AsyncCooperationMixin): + def __call__(self, source: Observable[_T]) -> Observable[_T]: + return self._handle(source).pipe(utils_ops.retry(delay=1)) + + def _handle(self, source: Observable[_T]) -> Observable[_T]: + def subscribe( + observer: abc.ObserverBase[_T], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + def on_error(exc: Exception) -> None: + self._submit_exception(exc) + try: + raise exc + except OSError as e: + logger.critical(repr(e), exc_info=e) + if e.errno == errno.ENOSPC: + # OSError(28, 'No space left on device') + observer.on_completed() + else: + observer.on_error(exc) + except LiveRoomHidden: + logger.error('The live room has been hidden!') + observer.on_completed() + except LiveRoomLocked: + logger.error('The live room has been locked!') + observer.on_completed() + except LiveRoomEncrypted: + logger.error('The live room has been encrypted!') + observer.on_completed() + except Exception: + observer.on_error(exc) + + return source.subscribe( + observer.on_next, on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) diff --git a/src/blrec/core/operators/hls_prober.py b/src/blrec/core/operators/hls_prober.py new file mode 100644 index 0000000..ca91245 --- /dev/null +++ b/src/blrec/core/operators/hls_prober.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import io +import logging +from typing import List, Optional, Union + +from reactivex import Observable, Subject, abc + +from ...utils.ffprobe import StreamProfile, ffprobe +from .segment_fetcher import InitSectionData, SegmentData + +__all__ = ('HLSProber', 'StreamProfile') + + +logger = logging.getLogger(__name__) + + +class HLSProber: + def __init__(self) -> None: + self._profiles: Subject[StreamProfile] = Subject() + + def _reset(self) -> None: + self._gathering: bool = False + self._gathered_items: List[Union[InitSectionData, SegmentData]] = [] + + @property + def profiles(self) -> Observable[StreamProfile]: + return self._profiles + + def __call__( + self, source: Observable[Union[InitSectionData, SegmentData]] + ) -> Observable[Union[InitSectionData, SegmentData]]: + return self._probe(source) + + def _probe( + self, source: Observable[Union[InitSectionData, SegmentData]] + ) -> Observable[Union[InitSectionData, SegmentData]]: + def subscribe( + observer: abc.ObserverBase[Union[InitSectionData, SegmentData]], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + self._reset() + + def on_next(item: Union[InitSectionData, SegmentData]) -> None: + if isinstance(item, InitSectionData): + self._gathered_items.clear() + self._gathering = True + + if self._gathering: + self._gathered_items.append(item) + if len(self._gathered_items) >= 10: + try: + self._do_probe() + except Exception as e: + logger.warning(f'Failed to probe stream: {repr(e)}') + finally: + self._gathered_items.clear() + self._gathering = False + + observer.on_next(item) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) + + def _do_probe(self) -> None: + bytes_io = io.BytesIO() + for item in self._gathered_items: + bytes_io.write(item.payload) + + def on_next(profile: StreamProfile) -> None: + self._profiles.on_next(profile) + + def on_error(e: Exception) -> None: + logger.warning(f'Failed to probe stream by ffprobe: {repr(e)}') + + ffprobe(bytes_io.getvalue()).subscribe(on_next, on_error) diff --git a/src/blrec/core/operators/playlist_fetcher.py b/src/blrec/core/operators/playlist_fetcher.py new file mode 100644 index 0000000..6bb3747 --- /dev/null +++ b/src/blrec/core/operators/playlist_fetcher.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +import logging +import time +from datetime import datetime +from typing import Optional + +import m3u8 +import requests +import urllib3 +from reactivex import Observable, abc +from reactivex.disposable import CompositeDisposable, Disposable, SerialDisposable +from tenacity import retry, retry_if_exception_type, stop_after_delay, wait_exponential + +from ...bili.live import Live +from ...utils.mixins import SupportDebugMixin + +__all__ = ('PlaylistFetcher',) + + +logger = logging.getLogger(__name__) + + +class PlaylistFetcher(SupportDebugMixin): + def __init__(self, live: Live, session: requests.Session) -> None: + super().__init__() + self._init_for_debug(live.room_id) + self._live = live + self._session = session + + def __call__(self, source: Observable[str]) -> Observable[m3u8.M3U8]: + return self._fetch(source) + + def _fetch(self, source: Observable[str]) -> Observable[m3u8.M3U8]: + def subscribe( + observer: abc.ObserverBase[m3u8.M3U8], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + if self._debug: + path = '{}/playlist-{}-{}.m3u8'.format( + self._debug_dir, + self._live.room_id, + datetime.now().strftime('%Y-%m-%d-%H%M%S-%f'), + ) + playlist_debug_file = open(path, 'wt', encoding='utf-8') + + disposed = False + subscription = SerialDisposable() + + def on_next(url: str) -> None: + logger.info(f'Fetching playlist... {url}') + while not disposed: + try: + content = self._fetch_playlist(url) + if self._debug: + playlist_debug_file.write(content + '\n') + playlist = m3u8.loads(content, uri=url) + if playlist.is_variant: + url = self._get_best_quality_url(playlist) + logger.debug('Playlist changed to variant playlist') + on_next(url) + except Exception as e: + logger.warning(f'Failed to fetch playlist: {repr(e)}') + observer.on_error(e) + else: + observer.on_next(playlist) + time.sleep(1) + + def dispose() -> None: + nonlocal disposed + disposed = True + if self._debug: + playlist_debug_file.close() + + subscription.disposable = source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return CompositeDisposable(subscription, Disposable(dispose)) + + return Observable(subscribe) + + def _get_best_quality_url(self, playlist: m3u8.M3U8) -> str: + sorted_playlists = sorted( + playlist.playlists, key=lambda p: p.stream_info.bandwidth + ) + return sorted_playlists[-1].absolute_uri + + @retry( + reraise=True, + retry=retry_if_exception_type( + ( + requests.exceptions.Timeout, + urllib3.exceptions.TimeoutError, + urllib3.exceptions.ProtocolError, + ) + ), + wait=wait_exponential(multiplier=0.1, max=1), + stop=stop_after_delay(10), + ) + def _fetch_playlist(self, url: str) -> str: + response = self._session.get(url, headers=self._live.headers, timeout=3) + response.raise_for_status() + response.encoding = 'utf-8' + return response.text diff --git a/src/blrec/core/operators/playlist_resolver.py b/src/blrec/core/operators/playlist_resolver.py new file mode 100644 index 0000000..1301016 --- /dev/null +++ b/src/blrec/core/operators/playlist_resolver.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +import logging +from typing import Final, Optional, Set + +import m3u8 +import urllib3 +from ordered_set import OrderedSet +from reactivex import Observable, abc +from reactivex.disposable import CompositeDisposable, Disposable, SerialDisposable + +__all__ = ('PlaylistResolver',) + + +logger = logging.getLogger(__name__) +logging.getLogger(urllib3.__name__).setLevel(logging.WARNING) + + +class PlaylistResolver: + _MAX_LAST_SEG_URIS: Final[int] = 30 + + def __call__(self, source: Observable[m3u8.M3U8]) -> Observable[m3u8.Segment]: + return self._solve(source) + + def _solve(self, source: Observable[m3u8.M3U8]) -> Observable[m3u8.Segment]: + def subscribe( + observer: abc.ObserverBase[m3u8.Segment], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + disposed = False + subscription = SerialDisposable() + last_seg_uris: OrderedSet[str] = OrderedSet() + + def on_next(playlist: m3u8.M3U8) -> None: + curr_seg_uris: Set[str] = set() + + for seg in playlist.segments: + if disposed: + return + curr_seg_uris.add(seg.uri) + if seg.uri not in last_seg_uris: + observer.on_next(seg) + last_seg_uris.add(seg.uri) + if len(last_seg_uris) > self._MAX_LAST_SEG_URIS: + last_seg_uris.pop(0) + + if last_seg_uris and not curr_seg_uris.intersection(last_seg_uris): + logger.debug( + 'Segments broken!\n' + f'Last segments uris: {last_seg_uris}\n' + f'Current segments uris: {curr_seg_uris}' + ) + + if playlist.is_endlist: + logger.debug('Playlist ended') + + def dispose() -> None: + nonlocal disposed + disposed = True + last_seg_uris.clear() + + subscription.disposable = source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return CompositeDisposable(subscription, Disposable(dispose)) + + return Observable(subscribe) diff --git a/src/blrec/core/operators/progress_bar.py b/src/blrec/core/operators/progress_bar.py new file mode 100644 index 0000000..7e15d9d --- /dev/null +++ b/src/blrec/core/operators/progress_bar.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import logging +from typing import Optional + +from reactivex import Observable, abc +from reactivex.disposable import CompositeDisposable, Disposable, SerialDisposable +from tqdm import tqdm + +from ...bili.live import Live +from ...flv.operators.typing import FLVStream, FLVStreamItem + +__all__ = ('ProgressBar',) + + +logger = logging.getLogger(__name__) + + +class ProgressBar: + def __init__(self, live: Live) -> None: + self._live = live + self._pbar: Optional[tqdm] = None + + def update_bar_info(self) -> None: + if self._pbar is not None: + self._pbar.set_postfix_str(self._make_pbar_postfix()) + + def __call__(self, source: FLVStream) -> FLVStream: + return self._progress(source) + + def _progress(self, source: FLVStream) -> FLVStream: + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + subscription = SerialDisposable() + + self._pbar = tqdm( + desc='Recording', + unit='B', + unit_scale=True, + unit_divisor=1024, + postfix=self._make_pbar_postfix(), + ) + + def on_next(item: FLVStreamItem) -> None: + if self._pbar is not None: + self._pbar.update(len(item)) + observer.on_next(item) + + def on_completed() -> None: + if self._pbar is not None: + self._pbar.close() + self._pbar = None + observer.on_completed() + + def dispose() -> None: + if self._pbar is not None: + self._pbar.close() + self._pbar = None + + subscription.disposable = source.subscribe( + on_next, observer.on_error, on_completed, scheduler=scheduler + ) + + return CompositeDisposable(subscription, Disposable(dispose)) + + return Observable(subscribe) + + def _make_pbar_postfix(self) -> str: + return '{room_id} - {user_name}: {room_title}'.format( + room_id=self._live.room_info.room_id, + user_name=self._live.user_info.name, + room_title=self._live.room_info.title, + ) diff --git a/src/blrec/core/operators/request_exception_handler.py b/src/blrec/core/operators/request_exception_handler.py new file mode 100644 index 0000000..e2632f6 --- /dev/null +++ b/src/blrec/core/operators/request_exception_handler.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +import asyncio +import logging +from typing import Optional, TypeVar + +import requests +import urllib3 +from reactivex import Observable, abc + +from ...utils import operators as utils_ops + +__all__ = ('RequestExceptionHandler',) + + +logger = logging.getLogger(__name__) + +_T = TypeVar('_T') + + +class RequestExceptionHandler: + def __call__(self, source: Observable[_T]) -> Observable[_T]: + return self._handle(source).pipe( + utils_ops.retry(delay=1, should_retry=self._should_retry) + ) + + def _handle(self, source: Observable[_T]) -> Observable[_T]: + def subscribe( + observer: abc.ObserverBase[_T], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + def on_error(exc: Exception) -> None: + try: + raise exc + except asyncio.exceptions.TimeoutError: + logger.warning(repr(exc)) + except requests.exceptions.Timeout: + logger.warning(repr(exc)) + except requests.exceptions.HTTPError: + logger.warning(repr(exc)) + except urllib3.exceptions.TimeoutError: + logger.warning(repr(exc)) + except urllib3.exceptions.ProtocolError: + # ProtocolError('Connection broken: IncompleteRead( + logger.warning(repr(exc)) + except Exception: + pass + observer.on_error(exc) + + return source.subscribe( + observer.on_next, on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) + + def _should_retry(self, exc: Exception) -> bool: + if isinstance( + exc, + ( + asyncio.exceptions.TimeoutError, + requests.exceptions.Timeout, + requests.exceptions.HTTPError, + urllib3.exceptions.TimeoutError, + urllib3.exceptions.ProtocolError, + ), + ): + return True + else: + return False diff --git a/src/blrec/core/operators/segment_fetcher.py b/src/blrec/core/operators/segment_fetcher.py new file mode 100644 index 0000000..4543fd4 --- /dev/null +++ b/src/blrec/core/operators/segment_fetcher.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +import logging +from typing import Optional, Union + +import attr +import m3u8 +import requests +import urllib3 +from m3u8.model import InitializationSection +from reactivex import Observable, abc +from reactivex.disposable import CompositeDisposable, Disposable, SerialDisposable +from tenacity import retry, retry_if_exception_type, stop_after_delay, wait_exponential + +from ...bili.live import Live + +__all__ = ('SegmentFetcher', 'InitSectionData', 'SegmentData') + + +logger = logging.getLogger(__name__) + + +@attr.s(auto_attribs=True, slots=True, frozen=True) +class InitSectionData: + payload: bytes + + def __len__(self) -> int: + return len(self.payload) + + +@attr.s(auto_attribs=True, slots=True, frozen=True) +class SegmentData: + payload: bytes + + def __len__(self) -> int: + return len(self.payload) + + +class SegmentFetcher: + def __init__(self, live: Live, session: requests.Session) -> None: + self._live = live + self._session = session + + def __call__( + self, source: Observable[m3u8.Segment] + ) -> Observable[Union[InitSectionData, SegmentData]]: + return self._fetch(source) + + def _fetch( + self, source: Observable[m3u8.Segment] + ) -> Observable[Union[InitSectionData, SegmentData]]: + def subscribe( + observer: abc.ObserverBase[Union[InitSectionData, SegmentData]], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + disposed = False + subscription = SerialDisposable() + init_section: Optional[InitializationSection] = None + + def on_next(seg: m3u8.Segment) -> None: + nonlocal init_section + url: str = '' + try: + if getattr(seg, 'init_section', None) and ( + not init_section or seg.init_section.uri != init_section.uri + ): + url = seg.init_section.absolute_uri + data = self._fetch_segment(url) + init_section = seg.init_section + observer.on_next(InitSectionData(payload=data)) + url = seg.absolute_uri + data = self._fetch_segment(url) + observer.on_next(SegmentData(payload=data)) + except Exception as e: + logger.warning(f'Failed to fetch segment {url}: {repr(e)}') + + def dispose() -> None: + nonlocal disposed + disposed = True + + subscription.disposable = source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return CompositeDisposable(subscription, Disposable(dispose)) + + return Observable(subscribe) + + @retry( + reraise=True, + retry=retry_if_exception_type( + ( + requests.exceptions.Timeout, + requests.exceptions.HTTPError, + urllib3.exceptions.TimeoutError, + urllib3.exceptions.ProtocolError, + ) + ), + wait=wait_exponential(multiplier=0.1, max=5), + stop=stop_after_delay(60), + ) + def _fetch_segment(self, url: str) -> bytes: + with self._session.get(url, headers=self._live.headers, timeout=10) as response: + response.raise_for_status() + return response.content diff --git a/src/blrec/core/operators/segment_remuxer.py b/src/blrec/core/operators/segment_remuxer.py new file mode 100644 index 0000000..c2fe64c --- /dev/null +++ b/src/blrec/core/operators/segment_remuxer.py @@ -0,0 +1,123 @@ +from __future__ import annotations + +import io +import logging +from typing import Final, List, Optional, Union + +import urllib3 +from reactivex import Observable, abc +from reactivex.disposable import CompositeDisposable, Disposable, SerialDisposable + +from ...bili.live import Live +from ...utils.io import wait_for +from ..stream_remuxer import StreamRemuxer +from .segment_fetcher import InitSectionData, SegmentData + +__all__ = ('SegmentRemuxer',) + + +logger = logging.getLogger(__name__) +logging.getLogger(urllib3.__name__).setLevel(logging.WARNING) + + +class SegmentRemuxer: + _MAX_SEGMENT_DATA_CACHE: Final = 3 + + def __init__(self, live: Live) -> None: + self._live = live + self._stream_remuxer = StreamRemuxer(live.room_id, remove_filler_data=True) + + def __call__( + self, source: Observable[Union[InitSectionData, SegmentData]] + ) -> Observable[io.RawIOBase]: + return self._remux(source) + + def _remux( + self, source: Observable[Union[InitSectionData, SegmentData]] + ) -> Observable[io.RawIOBase]: + def subscribe( + observer: abc.ObserverBase[io.RawIOBase], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + disposed = False + subscription = SerialDisposable() + + init_section_data: Optional[bytes] = None + segment_data_cache: List[bytes] = [] + + self._stream_remuxer.stop() + + def on_next(data: Union[InitSectionData, SegmentData]) -> None: + nonlocal init_section_data + + if isinstance(data, InitSectionData): + init_section_data = data.payload + + try: + if self._stream_remuxer.stopped: + self._stream_remuxer.start() + while True: + ready = self._stream_remuxer.wait(timeout=1) + if disposed: + return + if ready: + break + + observer.on_next(RemuxedStream(self._stream_remuxer)) + + if segment_data_cache: + if init_section_data: + self._stream_remuxer.input.write(init_section_data) + for cached_data in segment_data_cache: + if cached_data == init_section_data: + continue + self._stream_remuxer.input.write(cached_data) + + self._stream_remuxer.input.write(data.payload) + except Exception as exc: + logger.warning(f'Failed to write data to stream remuxer: {exc}') + self._stream_remuxer.stop() + + segment_data_cache.append(data.payload) + if len(segment_data_cache) > self._MAX_SEGMENT_DATA_CACHE: + segment_data_cache.pop(0) + + def dispose() -> None: + nonlocal disposed + disposed = True + + subscription.disposable = source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return CompositeDisposable(subscription, Disposable(dispose)) + + return Observable(subscribe) + + +class RemuxedStream(io.RawIOBase): + def __init__( + self, stream_remuxer: StreamRemuxer, *, read_timeout: float = 10 + ) -> None: + self._stream_remuxer = stream_remuxer + self._read_timmeout = read_timeout + self._offset: int = 0 + + def read(self, size: int = -1) -> bytes: + try: + data = wait_for( + self._stream_remuxer.output.read, + args=(size,), + timeout=self._read_timmeout, + ) + except Exception as exc: + logger.warning(f'Failed to read data from stream remuxer: {exc}') + self._stream_remuxer.stop() + raise EOFError + else: + assert data is not None + self._offset += len(data) + return data + + def tell(self) -> int: + return self._offset diff --git a/src/blrec/core/operators/sized_statistics.py b/src/blrec/core/operators/sized_statistics.py new file mode 100644 index 0000000..61857ff --- /dev/null +++ b/src/blrec/core/operators/sized_statistics.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +from typing import Optional, Sized + +from reactivex import Observable, abc + +from ..statistics import Statistics + +__all__ = ('SizedStatistics',) + + +class SizedStatistics: + def __init__(self) -> None: + self._statistics = Statistics() + + @property + def count(self) -> int: + return self._statistics.count + + @property + def rate(self) -> float: + return self._statistics.rate + + @property + def elapsed(self) -> float: + return self._statistics.elapsed + + def freeze(self) -> None: + self._statistics.freeze() + + def reset(self) -> None: + self._statistics.reset() + + def __call__(self, source: Observable[Sized]) -> Observable[Sized]: + self.reset() + return self._calc(source) + + def _calc(self, source: Observable[Sized]) -> Observable[Sized]: + def subscribe( + observer: abc.ObserverBase[Sized], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + def on_next(item: Sized) -> None: + self._statistics.submit(len(item)) + observer.on_next(item) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) diff --git a/src/blrec/core/operators/stream_fetcher.py b/src/blrec/core/operators/stream_fetcher.py new file mode 100644 index 0000000..22ca599 --- /dev/null +++ b/src/blrec/core/operators/stream_fetcher.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +import io +import logging +from typing import Optional + +import requests +from reactivex import Observable, abc + +from ...bili.live import Live +from ...utils.mixins import AsyncCooperationMixin + +__all__ = ('StreamFetcher',) + + +logger = logging.getLogger(__name__) + + +class StreamFetcher(AsyncCooperationMixin): + def __init__( + self, + live: Live, + session: requests.Session, + *, + read_timeout: Optional[int] = None, + ) -> None: + super().__init__() + self._live = live + self._session = session + self.read_timeout = read_timeout or 3 + + def __call__(self, source: Observable[str]) -> Observable[io.RawIOBase]: + return self._fetch(source) + + def _fetch(self, source: Observable[str]) -> Observable[io.RawIOBase]: + def subscribe( + observer: abc.ObserverBase[io.RawIOBase], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + def on_next(url: str) -> None: + try: + logger.info(f'Requesting live stream... {url}') + response = self._session.get( + url, + stream=True, + headers=self._live.headers, + timeout=self.read_timeout, + ) + logger.info('Response received') + response.raise_for_status() + except Exception as e: + logger.warning(f'Failed to request live stream: {repr(e)}') + observer.on_error(e) + else: + observer.on_next(response.raw) # urllib3.response.HTTPResponse + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) diff --git a/src/blrec/core/operators/stream_parser.py b/src/blrec/core/operators/stream_parser.py new file mode 100644 index 0000000..184eaca --- /dev/null +++ b/src/blrec/core/operators/stream_parser.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import io +import logging + +from reactivex import Observable +from reactivex import operators as ops + +from ...flv import operators as flv_ops +from ...flv.exceptions import FlvDataError +from ...flv.operators.typing import FLVStream +from ...utils import operators as utils_ops +from ..stream_param_holder import StreamParamHolder + +__all__ = ('StreamParser',) + + +logger = logging.getLogger(__name__) + + +class StreamParser: + def __init__( + self, stream_param_holder: StreamParamHolder, *, ignore_eof: bool = False + ) -> None: + self._stream_param_holder = stream_param_holder + self._ignore_eof = ignore_eof + + def __call__(self, source: Observable[io.RawIOBase]) -> FLVStream: + return source.pipe( # type: ignore + flv_ops.parse(ignore_eof=self._ignore_eof, backup_timestamp=True), + ops.do_action(on_error=self._before_retry), + utils_ops.retry(should_retry=self._should_retry), + ) + + def _should_retry(self, exc: Exception) -> bool: + if isinstance(exc, (EOFError, FlvDataError)): + return True + else: + return False + + def _before_retry(self, exc: Exception) -> None: + try: + raise exc + except EOFError: + logger.debug(repr(exc)) + except FlvDataError: + logger.warning(f'Failed to parse stream: {repr(exc)}') + if not self._stream_param_holder.use_alternative_stream: + self._stream_param_holder.use_alternative_stream = True + else: + self._stream_param_holder.use_alternative_stream = False + self._stream_param_holder.rotate_api_platform() + except Exception: + pass diff --git a/src/blrec/core/operators/stream_statistics.py b/src/blrec/core/operators/stream_statistics.py new file mode 100644 index 0000000..91b3b85 --- /dev/null +++ b/src/blrec/core/operators/stream_statistics.py @@ -0,0 +1,81 @@ +from __future__ import annotations + +import io +from typing import Any, Optional + +from reactivex import Observable, Subject, abc + +from ..statistics import Statistics + +__all__ = ('StreamStatistics',) + + +class StreamStatistics: + def __init__(self) -> None: + self._statistics = Statistics() + + @property + def count(self) -> int: + return self._statistics.count + + @property + def rate(self) -> float: + return self._statistics.rate + + @property + def elapsed(self) -> float: + return self._statistics.elapsed + + def freeze(self) -> None: + self._statistics.freeze() + + def reset(self) -> None: + self._statistics.reset() + + def __call__(self, source: Observable[io.RawIOBase]) -> Observable[io.RawIOBase]: + self.reset() + return self._calc(source) + + def _calc(self, source: Observable[io.RawIOBase]) -> Observable[io.RawIOBase]: + def subscribe( + observer: abc.ObserverBase[io.RawIOBase], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + def on_next(stream: io.RawIOBase) -> None: + calculable_stream = CalculableStream(stream) + calculable_stream.size_updates.subscribe(self._statistics.submit) + observer.on_next(calculable_stream) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) + + +class CalculableStream(io.RawIOBase): + def __init__(self, stream: io.RawIOBase) -> None: + self._stream = stream + self._offset: int = 0 + self._size_updates: Subject[int] = Subject() + + @property + def size_updates(self) -> Observable[int]: + return self._size_updates + + def read(self, size: int = -1) -> bytes: + data = self._stream.read(size) + assert data is not None + self._offset += len(data) + self._size_updates.on_next(len(data)) + return data + + def readinto(self, b: Any) -> int: + n = self._stream.readinto(b) + assert n is not None + self._offset += n + self._size_updates.on_next(n) + return n + + def tell(self) -> int: + return self._offset diff --git a/src/blrec/core/operators/stream_url_resolver.py b/src/blrec/core/operators/stream_url_resolver.py new file mode 100644 index 0000000..94d30a5 --- /dev/null +++ b/src/blrec/core/operators/stream_url_resolver.py @@ -0,0 +1,160 @@ +from __future__ import annotations + +import logging +from typing import Optional +from urllib.parse import urlparse + +import requests +import urllib3 +from reactivex import Observable, abc +from reactivex import operators as ops + +from ...bili.exceptions import ( + LiveRoomEncrypted, + LiveRoomHidden, + LiveRoomLocked, + NoAlternativeStreamAvailable, + NoStreamAvailable, + NoStreamCodecAvailable, + NoStreamFormatAvailable, + NoStreamQualityAvailable, +) +from ...bili.live import Live +from ...utils import operators as utils_ops +from ...utils.mixins import AsyncCooperationMixin +from ..stream_param_holder import StreamParamHolder, StreamParams + +__all__ = ('StreamURLResolver',) + + +logger = logging.getLogger(__name__) +logging.getLogger(urllib3.__name__).setLevel(logging.WARNING) + + +class StreamURLResolver(AsyncCooperationMixin): + def __init__(self, live: Live, stream_param_holder: StreamParamHolder) -> None: + super().__init__() + self._live = live + self._stream_param_holder = stream_param_holder + self._stream_url: str = '' + self._stream_host: str = '' + self._stream_params: Optional[StreamParams] = None + + @property + def stream_url(self) -> str: + return self._stream_url + + @property + def stream_host(self) -> str: + return self._stream_host + + def _reset(self) -> None: + self._stream_url = '' + self._stream_host = '' + self._stream_params = None + + def __call__(self, source: Observable[StreamParams]) -> Observable[str]: + self._reset() + return self._solve(source).pipe( + ops.do_action(on_error=self._before_retry), + utils_ops.retry(delay=1, should_retry=self._should_retry), + ) + + def _solve(self, source: Observable[StreamParams]) -> Observable[str]: + def subscribe( + observer: abc.ObserverBase[str], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + def on_next(params: StreamParams) -> None: + if self._can_resue_url(params): + observer.on_next(self._stream_url) + return + + try: + logger.info( + f'Getting the live stream url... ' + f'qn: {params.quality_number}, ' + f'format: {params.stream_format}, ' + f'api platform: {params.api_platform}, ' + f'use alternative stream: {params.use_alternative_stream}' + ) + url = self._run_coroutine( + self._live.get_live_stream_url( + params.quality_number, + api_platform=params.api_platform, + stream_format=params.stream_format, + select_alternative=params.use_alternative_stream, + ) + ) + except Exception as e: + logger.warning(f'Failed to get live stream url: {repr(e)}') + observer.on_error(e) + else: + logger.info(f"Got live stream url: '{url}'") + self._stream_url = url + self._stream_host = urlparse(url).hostname or '' + self._stream_params = params + observer.on_next(url) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) + + def _can_resue_url(self, params: StreamParams) -> bool: + if params == self._stream_params and self._stream_url: + try: + response = requests.get( + self._stream_url, stream=True, headers=self._live.headers + ) + response.raise_for_status() + except Exception: + return False + else: + return True + else: + return False + + def _should_retry(self, exc: Exception) -> bool: + if isinstance( + exc, + ( + NoStreamAvailable, + NoStreamCodecAvailable, + NoStreamFormatAvailable, + NoStreamQualityAvailable, + NoAlternativeStreamAvailable, + ), + ): + return True + else: + return False + + def _before_retry(self, exc: Exception) -> None: + try: + raise exc + except (NoStreamAvailable, NoStreamCodecAvailable, NoStreamFormatAvailable): + pass + except NoStreamQualityAvailable: + qn = self._stream_param_holder.quality_number + logger.info( + f'The specified stream quality ({qn}) is not available, ' + 'will using the original stream quality (10000) instead.' + ) + self._stream_param_holder.fall_back_quality() + except NoAlternativeStreamAvailable: + logger.debug( + 'No alternative stream url available, ' + 'will using the primary stream url instead.' + ) + self._stream_param_holder.use_alternative_stream = False + self._stream_param_holder.rotate_api_platform() + except LiveRoomHidden: + pass + except LiveRoomLocked: + pass + except LiveRoomEncrypted: + pass + except Exception: + pass diff --git a/src/blrec/core/path_provider.py b/src/blrec/core/path_provider.py new file mode 100644 index 0000000..f520112 --- /dev/null +++ b/src/blrec/core/path_provider.py @@ -0,0 +1,81 @@ +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 + +__all__ = ('PathProvider',) + +logger = logging.getLogger(__name__) + + +class PathProvider(AsyncCooperationMixin): + def __init__(self, live: Live, out_dir: str, path_template: str) -> None: + super().__init__() + self._live = live + self.out_dir = out_dir + 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()) + + def _make_path(self, timestamp: int) -> str: + date_time = datetime.fromtimestamp(timestamp) + relpath = self.path_template.format( + roomid=self._live.room_id, + 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'), + hour=str(date_time.hour).rjust(2, '0'), + minute=str(date_time.minute).rjust(2, '0'), + second=str(date_time.second).rjust(2, '0'), + ) + + pathname = os.path.abspath( + os.path.expanduser(os.path.join(self.out_dir, relpath) + '.flv') + ) + os.makedirs(os.path.dirname(pathname), exist_ok=True) + while os.path.exists(pathname): + root, ext = os.path.splitext(pathname) + m = re.search(r'_\((\d+)\)$', root) + if m is None: + root += '_(1)' + else: + root = re.sub(r'\(\d+\)$', f'({int(m.group(1)) + 1})', root) + pathname = root + ext + + return pathname diff --git a/src/blrec/core/recorder.py b/src/blrec/core/recorder.py index e73bf59..0dd4ee0 100644 --- a/src/blrec/core/recorder.py +++ b/src/blrec/core/recorder.py @@ -13,7 +13,7 @@ from ..bili.live_monitor import LiveEventListener, LiveMonitor from ..bili.models import RoomInfo from ..bili.typing import QualityNumber, StreamFormat from ..event.event_emitter import EventEmitter, EventListener -from ..flv.data_analyser import MetaData +from ..flv.operators import MetaData, StreamProfile from ..logging.room_id import aio_task_with_room_id from ..utils.mixins import AsyncStoppableMixin from .cover_downloader import CoverDownloader, CoverSaveStrategy @@ -21,7 +21,6 @@ from .danmaku_dumper import DanmakuDumper, DanmakuDumperEventListener from .danmaku_receiver import DanmakuReceiver from .raw_danmaku_dumper import RawDanmakuDumper, RawDanmakuDumperEventListener from .raw_danmaku_receiver import RawDanmakuReceiver -from .stream_analyzer import StreamProfile from .stream_recorder import StreamRecorder, StreamRecorderEventListener __all__ = 'RecorderEventListener', 'Recorder' @@ -402,8 +401,8 @@ class Recorder( async def on_raw_danmaku_file_completed(self, path: str) -> None: await self._emit('raw_danmaku_file_completed', self, path) - async def on_stream_recording_stopped(self) -> None: - logger.debug('Stream recording stopped') + async def on_stream_recording_completed(self) -> None: + logger.debug('Stream recording completed') await self._stop_recording() async def _do_start(self) -> None: diff --git a/src/blrec/core/retry.py b/src/blrec/core/retry.py deleted file mode 100644 index 2d1619b..0000000 --- a/src/blrec/core/retry.py +++ /dev/null @@ -1,60 +0,0 @@ -import logging -from typing import Any, Callable, Optional, Type, cast - -from tenacity import wait_exponential, RetryCallState -from tenacity import _utils - - -class wait_exponential_for_same_exceptions(wait_exponential): - """Wait strategy that applies exponential backoff only for same - continuing exceptions. - """ - - def __init__( - self, - multiplier: float = 1, - max: float = _utils.MAX_WAIT, - exp_base: int = 2, - min: float = 0, - continuing_criteria: float = 5.0, - ) -> None: - super().__init__(multiplier, max, exp_base, min) - self._continuing_criteria = continuing_criteria - self._prev_exc_type: Optional[Type[BaseException]] = None - self._prev_exc_ts: Optional[float] = None - self._last_wait_time: float = 0 - - def __call__(self, retry_state: RetryCallState) -> float: - if ( - retry_state.outcome is not None and - (exc := retry_state.outcome.exception()) - ): - curr_exc_type = type(exc) - curr_exc_ts = cast(float, retry_state.outcome_timestamp) - if ( - curr_exc_type is not self._prev_exc_type or - not self._is_continuing(curr_exc_ts) - ): - retry_state.attempt_number = 1 - self._prev_exc_type = curr_exc_type - self._prev_exc_ts = curr_exc_ts - - self._last_wait_time = wait_time = super().__call__(retry_state) - return wait_time - - def _is_continuing(self, curr_exc_ts: float) -> bool: - assert self._prev_exc_ts is not None - return ( - curr_exc_ts - (self._prev_exc_ts + self._last_wait_time) < - self._continuing_criteria - ) - - -def before_sleep_log( - logger: logging.Logger, log_level: int, name: str = '' -) -> Callable[[RetryCallState], Any]: - def log_it(retry_state: RetryCallState) -> None: - seconds = cast(float, getattr(retry_state.next_action, 'sleep')) - logger.log(log_level, 'Retry %s after %s seconds', name, seconds) - - return log_it diff --git a/src/blrec/core/statistics.py b/src/blrec/core/statistics.py index e3f9c41..04adf29 100644 --- a/src/blrec/core/statistics.py +++ b/src/blrec/core/statistics.py @@ -1,10 +1,10 @@ import time -__all__ = 'StatisticsCalculator', +__all__ = 'Statistics', -class StatisticsCalculator: +class Statistics: def __init__(self, interval: float = 1.0) -> None: self._interval = interval self._frozen = True diff --git a/src/blrec/core/stream_param_holder.py b/src/blrec/core/stream_param_holder.py new file mode 100644 index 0000000..175a442 --- /dev/null +++ b/src/blrec/core/stream_param_holder.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +import logging +from typing import Any, Final, Optional + +import attr +from reactivex import Observable, abc, create +from reactivex.disposable import CompositeDisposable, Disposable, SerialDisposable +from reactivex.scheduler.currentthreadscheduler import CurrentThreadScheduler + +from ..bili.typing import ApiPlatform, QualityNumber, StreamFormat + +__all__ = ('StreamParamHolder',) + + +logger = logging.getLogger(__name__) + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class StreamParams: + stream_format: StreamFormat + quality_number: QualityNumber + api_platform: ApiPlatform + use_alternative_stream: bool + + +class StreamParamHolder: + def __init__( + self, + *, + stream_format: StreamFormat = 'flv', + quality_number: QualityNumber = 10000, + api_platform: ApiPlatform = 'android', + use_alternative_stream: bool = False, + ) -> None: + super().__init__() + self._stream_format: Final = stream_format + self._quality_number = quality_number + self._real_quality_number: Optional[QualityNumber] = None + self._api_platform: ApiPlatform = api_platform + self._use_alternative_stream: bool = use_alternative_stream + self._cancelled: bool = False + + def reset(self) -> None: + self._real_quality_number = None + self._api_platform = 'android' + self._use_alternative_stream = False + self._cancelled = False + + def cancel(self) -> None: + self._cancelled = True + + @property + def stream_format(self) -> StreamFormat: + return self._stream_format + + @property + def quality_number(self) -> QualityNumber: + return self._quality_number + + @quality_number.setter + def quality_number(self, value: QualityNumber) -> None: + self._quality_number = value + + @property + def real_quality_number(self) -> QualityNumber: + return self._real_quality_number or self._quality_number + + @property + def use_alternative_stream(self) -> bool: + return self._use_alternative_stream + + @use_alternative_stream.setter + def use_alternative_stream(self, value: bool) -> None: + self._use_alternative_stream = value + + def fall_back_quality(self) -> None: + self._real_quality_number = 10000 + + def rotate_api_platform(self) -> None: + if self._api_platform == 'android': + self._api_platform = 'web' + else: + self._api_platform = 'android' + + def get_stream_params(self) -> Observable[StreamParams]: + self.reset() + + def subscribe( + observer: abc.ObserverBase[StreamParams], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + _scheduler = scheduler or CurrentThreadScheduler.singleton() + + disposed = False + cancelable = SerialDisposable() + + def action( + scheduler: abc.SchedulerBase, state: Optional[Any] = None + ) -> None: + if self._cancelled or disposed: + return + + params = StreamParams( + stream_format=self._stream_format, + quality_number=self._real_quality_number or self._quality_number, + api_platform=self._api_platform, + use_alternative_stream=self._use_alternative_stream, + ) + observer.on_next(params) + + cancelable.disposable = _scheduler.schedule(action) + + def dispose() -> None: + nonlocal disposed + disposed = True + + return CompositeDisposable(cancelable, Disposable(dispose)) + + return create(subscribe) diff --git a/src/blrec/core/stream_recorder.py b/src/blrec/core/stream_recorder.py index 5aae7f7..759a5de 100644 --- a/src/blrec/core/stream_recorder.py +++ b/src/blrec/core/stream_recorder.py @@ -6,11 +6,10 @@ from typing import Iterator, Optional from ..bili.live import Live from ..bili.typing import QualityNumber, StreamFormat from ..event.event_emitter import EventEmitter -from ..flv.data_analyser import MetaData +from ..flv.operators import MetaData, StreamProfile from ..utils.mixins import AsyncStoppableMixin from .flv_stream_recorder_impl import FLVStreamRecorderImpl from .hls_stream_recorder_impl import HLSStreamRecorderImpl -from .stream_analyzer import StreamProfile from .stream_recorder_impl import StreamRecorderEventListener __all__ = 'StreamRecorder', 'StreamRecorderEventListener' @@ -230,15 +229,15 @@ class StreamRecorder( async def on_video_file_completed(self, path: str) -> None: await self._emit('video_file_completed', path) - async def on_stream_recording_stopped(self) -> None: - await self._emit('stream_recording_stopped') + async def on_stream_recording_completed(self) -> None: + await self._emit('stream_recording_completed') async def _wait_fmp4_stream(self) -> bool: end_time = time.monotonic() + self.fmp4_stream_timeout available = False # debounce while True: try: - await self._impl._live.get_live_stream_urls(stream_format='fmp4') + await self._impl._live.get_live_stream_url(stream_format='fmp4') except Exception: available = False if time.monotonic() > end_time: diff --git a/src/blrec/core/stream_recorder_impl.py b/src/blrec/core/stream_recorder_impl.py index 1e413c7..b832156 100644 --- a/src/blrec/core/stream_recorder_impl.py +++ b/src/blrec/core/stream_recorder_impl.py @@ -1,61 +1,27 @@ -import asyncio -import errno -import io import logging -import os -import re -import time from abc import ABC, abstractmethod -from collections import OrderedDict -from datetime import datetime, timedelta, timezone from threading import Thread -from typing import Any, BinaryIO, Dict, Final, Iterator, Optional, Tuple +from typing import Any, Iterator, List, Optional, Tuple, Union -import aiohttp +import requests import urllib3 -from rx.core import Observable -from rx.subject import Subject -from tenacity import ( - Retrying, - TryAgain, - retry, - retry_if_exception_type, - retry_if_not_exception_type, - retry_if_result, - stop_after_attempt, - stop_after_delay, - wait_chain, - wait_exponential, - wait_fixed, - wait_none, -) -from tqdm import tqdm +from reactivex import abc +from reactivex.typing import StartableFactory, StartableTarget -from .. import __github__, __prog__, __version__ -from ..bili.exceptions import ( - LiveRoomEncrypted, - LiveRoomHidden, - LiveRoomLocked, - NoStreamAvailable, - NoStreamFormatAvailable, - NoStreamQualityAvailable, -) -from ..bili.helpers import get_quality_name from ..bili.live import Live -from ..bili.typing import ApiPlatform, QualityNumber, StreamFormat +from ..bili.typing import QualityNumber, StreamFormat from ..event.event_emitter import EventEmitter, EventListener -from ..flv.data_analyser import MetaData -from ..flv.stream_processor import BaseOutputFileManager, StreamProcessor +from ..flv import operators as flv_ops +from ..flv.metadata_dumper import MetadataDumper +from ..flv.operators import StreamProfile from ..logging.room_id import aio_task_with_room_id -from ..path import escape_path -from ..utils.io import wait_for from ..utils.mixins import AsyncCooperationMixin, AsyncStoppableMixin -from .retry import before_sleep_log, wait_exponential_for_same_exceptions -from .statistics import StatisticsCalculator -from .stream_analyzer import StreamProfile -from .stream_remuxer import StreamRemuxer +from . import operators as core_ops +from .metadata_provider import MetadataProvider +from .path_provider import PathProvider +from .stream_param_holder import StreamParamHolder -__all__ = 'StreamRecorderImpl', +__all__ = ('StreamRecorderImpl',) logger = logging.getLogger(__name__) @@ -63,15 +29,13 @@ logging.getLogger(urllib3.__name__).setLevel(logging.WARNING) class StreamRecorderEventListener(EventListener): - async def on_video_file_created( - self, path: str, record_start_time: int - ) -> None: + async def on_video_file_created(self, path: str, record_start_time: int) -> None: ... async def on_video_file_completed(self, path: str) -> None: ... - async def on_stream_recording_stopped(self) -> None: + async def on_stream_recording_completed(self) -> None: ... @@ -98,139 +62,183 @@ class StreamRecorderImpl( super().__init__() self._live = live - self._progress_bar: Optional[tqdm] = None - self._stream_remuxer: Optional[StreamRemuxer] = None - self._stream_processor: Optional[StreamProcessor] = None - self._dl_calculator = StatisticsCalculator() - self._rec_calculator = StatisticsCalculator() - self._file_manager = OutputFileManager( - live, out_dir, path_template, buffer_size + self._session = requests.Session() + self._stream_param_holder = StreamParamHolder( + stream_format=stream_format, quality_number=quality_number ) + self._stream_url_resolver = core_ops.StreamURLResolver( + live, self._stream_param_holder + ) + self._stream_fetcher = core_ops.StreamFetcher( + live, self._session, read_timeout=read_timeout + ) + self._stream_parser = core_ops.StreamParser( + self._stream_param_holder, ignore_eof=stream_format != 'flv' + ) + self._progress_bar = core_ops.ProgressBar(live) + self._analyser = flv_ops.Analyser() + self._metadata_provider = MetadataProvider(live, self) + self._injector = flv_ops.Injector(self._metadata_provider) + self._join_point_extractor = flv_ops.JoinPointExtractor() + self._limiter = flv_ops.Limiter(filesize_limit, duration_limit) + self._cutter = flv_ops.Cutter() + self._path_provider = PathProvider(live, out_dir, path_template) + self._dumper = flv_ops.Dumper(self._path_provider, buffer_size) + self._rec_statistics = core_ops.SizedStatistics() - self._stream_format: Final = stream_format - self._quality_number = quality_number - self._real_quality_number: Optional[QualityNumber] = None - self._api_platform: ApiPlatform = 'android' - self._use_alternative_stream: bool = False - self.buffer_size = buffer_size or io.DEFAULT_BUFFER_SIZE # bytes - self.read_timeout = read_timeout or 3 # seconds - self.disconnection_timeout = disconnection_timeout or 600 # seconds + self._prober: Union[flv_ops.Prober, core_ops.HLSProber] + self._dl_statistics: Union[core_ops.StreamStatistics, core_ops.SizedStatistics] + if stream_format == 'flv': + self._prober = flv_ops.Prober() + self._dl_statistics = core_ops.StreamStatistics() + else: + self._prober = core_ops.HLSProber() + self._dl_statistics = core_ops.SizedStatistics() - self._filesize_limit = filesize_limit or 0 - self._duration_limit = duration_limit or 0 + self._request_exception_handler = core_ops.RequestExceptionHandler() + self._connection_error_handler = core_ops.ConnectionErrorHandler( + live, disconnection_timeout=disconnection_timeout + ) + self._exception_handler = core_ops.ExceptionHandler() + self._metadata_dumper = MetadataDumper( + self._dumper, self._analyser, self._join_point_extractor + ) + self._metadata_dumper.enable() - self._stream_url: str = '' - self._stream_host: str = '' + self._subscription: abc.DisposableBase + + self._threads: List[Thread] = [] + self._files: List[str] = [] self._stream_profile: StreamProfile = {} - def on_file_created(args: Tuple[str, int]) -> None: + def on_profile_updated(profile: StreamProfile) -> None: + self._stream_profile = profile + + self._prober.profiles.subscribe(on_profile_updated) + + def on_file_opened(args: Tuple[str, int]) -> None: logger.info(f"Video file created: '{args[0]}'") + self._files.append(args[0]) self._emit_event('video_file_created', *args) def on_file_closed(path: str) -> None: logger.info(f"Video file completed: '{path}'") self._emit_event('video_file_completed', path) - self._file_manager.file_creates.subscribe(on_file_created) - self._file_manager.file_closes.subscribe(on_file_closed) + self._dumper.file_opened.subscribe(on_file_opened) + self._dumper.file_closed.subscribe(on_file_closed) @property def stream_url(self) -> str: - return self._stream_url + return self._stream_url_resolver.stream_url @property def stream_host(self) -> str: - return self._stream_host + return self._stream_url_resolver.stream_host @property def dl_total(self) -> int: - return self._dl_calculator.count + return self._dl_statistics.count @property def dl_rate(self) -> float: - return self._dl_calculator.rate + return self._dl_statistics.rate @property def rec_elapsed(self) -> float: - return self._rec_calculator.elapsed + return self._rec_statistics.elapsed @property def rec_total(self) -> int: - return self._rec_calculator.count + return self._rec_statistics.count @property def rec_rate(self) -> float: - return self._rec_calculator.rate + return self._rec_statistics.rate @property def out_dir(self) -> str: - return self._file_manager.out_dir + return self._path_provider.out_dir @out_dir.setter def out_dir(self, value: str) -> None: - self._file_manager.out_dir = value + self._path_provider.out_dir = value @property def path_template(self) -> str: - return self._file_manager.path_template + return self._path_provider.path_template @path_template.setter def path_template(self, value: str) -> None: - self._file_manager.path_template = value + self._path_provider.path_template = value @property def stream_format(self) -> StreamFormat: - return self._stream_format + return self._stream_param_holder.stream_format @property def quality_number(self) -> QualityNumber: - return self._quality_number + return self._stream_param_holder.quality_number @quality_number.setter def quality_number(self, value: QualityNumber) -> None: - self._quality_number = value + self._stream_param_holder.quality_number = value @property def real_quality_number(self) -> Optional[QualityNumber]: if self.stopped: return None - return self._real_quality_number + return self._stream_param_holder.real_quality_number @property def filesize_limit(self) -> int: - if self._stream_processor is not None: - return self._stream_processor.filesize_limit - else: - return self._filesize_limit + return self._limiter.filesize_limit @filesize_limit.setter def filesize_limit(self, value: int) -> None: - self._filesize_limit = value - if self._stream_processor is not None: - self._stream_processor.filesize_limit = value + self._limiter.filesize_limit = value @property def duration_limit(self) -> int: - if self._stream_processor is not None: - return self._stream_processor.duration_limit - else: - return self._duration_limit + return self._limiter.duration_limit @duration_limit.setter def duration_limit(self, value: int) -> None: - self._duration_limit = value - if self._stream_processor is not None: - self._stream_processor.duration_limit = value + self._limiter.duration_limit = value + + @property + def read_timeout(self) -> int: + return self._stream_fetcher.read_timeout + + @read_timeout.setter + def read_timeout(self, value: int) -> None: + self._stream_fetcher.read_timeout = value + + @property + def disconnection_timeout(self) -> int: + return self._connection_error_handler.disconnection_timeout + + @disconnection_timeout.setter + def disconnection_timeout(self, value: int) -> None: + self._connection_error_handler.disconnection_timeout = value + + @property + def buffer_size(self) -> int: + return self._dumper.buffer_size + + @buffer_size.setter + def buffer_size(self, value: int) -> None: + self._dumper.buffer_size = value @property def recording_path(self) -> Optional[str]: - return self._file_manager.curr_path + return self._dumper.path @property - def metadata(self) -> Optional[MetaData]: - if self._stream_processor is not None: - return self._stream_processor.metadata - else: + def metadata(self) -> Optional[flv_ops.MetaData]: + try: + return self._analyser.make_metadata() + except Exception: return None @property @@ -238,225 +246,58 @@ class StreamRecorderImpl( return self._stream_profile def has_file(self) -> bool: - return self._file_manager.has_file() + return bool(self._files) def get_files(self) -> Iterator[str]: - yield from self._file_manager.get_files() + yield from iter(self._files) def clear_files(self) -> None: - self._file_manager.clear_files() + self._files.clear() def can_cut_stream(self) -> bool: - if self._stream_processor is None: - return False - return self._stream_processor.can_cut_stream() + return self._cutter.can_cut_stream() def cut_stream(self) -> bool: - if self._stream_processor is None: - return False - return self._stream_processor.cut_stream() + return self._cutter.cut_stream() def update_progress_bar_info(self) -> None: - if self._progress_bar is not None: - self._progress_bar.set_postfix_str(self._make_pbar_postfix()) + self._progress_bar.update_bar_info() def _reset(self) -> None: - self._dl_calculator.reset() - self._rec_calculator.reset() - self._stream_url = '' - self._stream_host = '' + self._files.clear() self._stream_profile = {} - self._api_platform = 'android' - self._real_quality_number = None - self._use_alternative_stream = False - self._fall_back_stream_format = False async def _do_start(self) -> None: logger.debug('Starting stream recorder...') self._reset() - self._thread = Thread( - target=self._run, name=f'StreamRecorder::{self._live.room_id}' - ) - self._thread.start() + self._run() logger.debug('Started stream recorder') async def _do_stop(self) -> None: logger.debug('Stopping stream recorder...') - if self._stream_processor is not None: - self._stream_processor.cancel() - await self._loop.run_in_executor(None, self._thread.join) + self._stream_param_holder.cancel() + thread = self._thread_factory('StreamRecorderDisposer')( + self._subscription.dispose + ) + thread.start() + for thread in self._threads: + await self._loop.run_in_executor(None, thread.join, 30) + self._threads.clear() logger.debug('Stopped stream recorder') @abstractmethod def _run(self) -> None: raise NotImplementedError() - @abstractmethod - def _streaming_loop(self) -> None: - raise NotImplementedError() - - def _main_loop(self) -> None: - for attempt in Retrying( - reraise=True, - retry=( - retry_if_result(lambda r: not self._stopped) | - retry_if_not_exception_type((NotImplementedError)) - ), - wait=wait_exponential_for_same_exceptions(max=60), - before_sleep=before_sleep_log(logger, logging.DEBUG, 'main_loop'), - ): - with attempt: - try: - self._streaming_loop() - except (NoStreamAvailable, NoStreamFormatAvailable) as e: - logger.warning(f'Failed to get live stream url: {repr(e)}') - except OSError as e: - logger.critical(repr(e), exc_info=e) - if e.errno == errno.ENOSPC: - # OSError(28, 'No space left on device') - self._handle_exception(e) - self._stopped = True - except LiveRoomHidden: - logger.error('The live room has been hidden!') - self._stopped = True - except LiveRoomLocked: - logger.error('The live room has been locked!') - self._stopped = True - except LiveRoomEncrypted: - logger.error('The live room has been encrypted!') - self._stopped = True - except Exception as e: - logger.exception(e) - self._handle_exception(e) - - def _rotate_api_platform(self) -> None: - if self._api_platform == 'android': - self._api_platform = 'web' - else: - self._api_platform = 'android' - - @retry( - reraise=True, - retry=retry_if_exception_type(( - asyncio.TimeoutError, aiohttp.ClientError, - )), - wait=wait_chain(wait_none(), wait_fixed(1)), - stop=stop_after_attempt(300), - ) - def _get_live_stream_url(self) -> str: - fmt = self._stream_format - qn = self._real_quality_number or self.quality_number - logger.info( - f'Getting the live stream url... qn: {qn}, format: {fmt}, ' - f'api platform: {self._api_platform}, ' - f'use alternative stream: {self._use_alternative_stream}' - ) - try: - urls = self._run_coroutine( - self._live.get_live_stream_urls( - qn, - api_platform=self._api_platform, - stream_format=fmt, - ) + def _thread_factory(self, name: str) -> StartableFactory: + def factory(target: StartableTarget) -> Thread: + thread = Thread( + target=target, daemon=True, name=f'{name}::{self._live.room_id}' ) - except NoStreamQualityAvailable: - logger.info( - f'The specified stream quality ({qn}) is not available, ' - 'will using the original stream quality (10000) instead.' - ) - self._real_quality_number = 10000 - raise TryAgain - else: - logger.info( - f'Adopted the stream format ({fmt}) and quality ({qn})' - ) - self._real_quality_number = qn + self._threads.append(thread) + return thread - if not self._use_alternative_stream: - url = urls[0] - else: - try: - url = urls[1] - except IndexError: - self._use_alternative_stream = False - self._rotate_api_platform() - logger.info( - 'No alternative stream url available, ' - 'will using the primary stream url ' - f'from {self._api_platform} api instead.' - ) - raise TryAgain - logger.info(f"Got live stream url: '{url}'") - - return url - - def _defer_retry(self, seconds: float, name: str = '') -> None: - if seconds <= 0: - return - logger.debug(f'Retry {name} after {seconds} seconds') - time.sleep(seconds) - - def _wait_for_connection_error(self, check_interval: int = 3) -> None: - timeout = self.disconnection_timeout - logger.info(f'Waiting {timeout} seconds for connection recovery... ') - timebase = time.monotonic() - while not self._run_coroutine(self._live.check_connectivity()): - if timeout is not None and time.monotonic() - timebase > timeout: - logger.error(f'Connection not recovered in {timeout} seconds') - self._stopped = True - break - time.sleep(check_interval) - else: - logger.info('Connection recovered') - - def _make_pbar_postfix(self) -> str: - return '{room_id} - {user_name}: {room_title}'.format( - room_id=self._live.room_info.room_id, - user_name=self._live.user_info.name, - room_title=self._live.room_info.title, - ) - - def _make_metadata(self) -> Dict[str, Any]: - live_start_time = datetime.fromtimestamp( - self._live.room_info.live_start_time, timezone(timedelta(hours=8)) - ) - - assert self._real_quality_number is not None - stream_quality = '{} ({}{})'.format( - get_quality_name(self._real_quality_number), - self._real_quality_number, - ', bluray' if '_bluray' in self._stream_url else '', - ) - - return { - 'Title': self._live.room_info.title, - 'Artist': self._live.user_info.name, - 'Date': str(live_start_time), - 'Comment': f'''\ -B站直播录像 -主播:{self._live.user_info.name} -标题:{self._live.room_info.title} -分区:{self._live.room_info.parent_area_name} - {self._live.room_info.area_name} -房间号:{self._live.room_info.room_id} -开播时间:{live_start_time} -流主机: {self._stream_host} -流格式:{self._stream_format} -流画质:{stream_quality} -录制程序:{__prog__} v{__version__} {__github__}''', - 'description': OrderedDict({ - 'UserId': str(self._live.user_info.uid), - 'UserName': self._live.user_info.name, - 'RoomId': str(self._live.room_info.room_id), - 'RoomTitle': self._live.room_info.title, - 'Area': self._live.room_info.area_name, - 'ParentArea': self._live.room_info.parent_area_name, - 'LiveStartTime': str(live_start_time), - 'StreamHost': self._stream_host, - 'StreamFormat': self._stream_format, - 'StreamQuality': stream_quality, - 'Recorder': f'{__prog__} v{__version__} {__github__}', - }) - } + return factory def _emit_event(self, name: str, *args: Any, **kwds: Any) -> None: self._run_coroutine(self._emit(name, *args, **kwds)) @@ -464,155 +305,3 @@ B站直播录像 @aio_task_with_room_id async def _emit(self, *args: Any, **kwds: Any) -> None: # type: ignore await super()._emit(*args, **kwds) - - -class StreamProxy(io.RawIOBase): - def __init__( - self, - stream: io.BufferedIOBase, - *, - read_timeout: Optional[float] = None, - ) -> None: - self._stream = stream - self._read_timmeout = read_timeout - self._offset = 0 - self._size_updates = Subject() - - @property - def size_updates(self) -> Observable: - return self._size_updates - - @property - def closed(self) -> bool: - # always return False to avoid that `ValueError: read of closed file`, - # raised from `CHECK_CLOSED(self, "read of closed file")`, - # result in losing data those remaining in the buffer. - # ref: `https://gihub.com/python/cpython/blob/63298930fb531ba2bb4f23bc3b915dbf1e17e9e1/Modules/_io/bufferedio.c#L882` # noqa - return False - - def fileno(self) -> int: - return self._stream.fileno() - - def readable(self) -> bool: - return True - - def read(self, size: int = -1) -> bytes: - if self._stream.closed: - raise EOFError - if self._read_timmeout: - data = wait_for( - self._stream.read, args=(size, ), timeout=self._read_timmeout - ) - else: - data = self._stream.read(size) - self._offset += len(data) - self._size_updates.on_next(len(data)) - return data - - def tell(self) -> int: - return self._offset - - def readinto(self, b: Any) -> int: - if self._stream.closed: - raise EOFError - if self._read_timmeout: - n = wait_for( - self._stream.readinto, args=(b, ), timeout=self._read_timmeout - ) - else: - n = self._stream.readinto(b) - self._offset += n - self._size_updates.on_next(n) - return n - - def close(self) -> None: - self._stream.close() - - -class OutputFileManager(BaseOutputFileManager, AsyncCooperationMixin): - def __init__( - self, - live: Live, - out_dir: str, - path_template: str, - buffer_size: Optional[int] = None, - ) -> None: - super().__init__(buffer_size) - self._live = live - - self.out_dir = out_dir - self.path_template = path_template - - self._file_creates = Subject() - self._file_closes = Subject() - - @property - def file_creates(self) -> Observable: - return self._file_creates - - @property - def file_closes(self) -> Observable: - return self._file_closes - - def create_file(self) -> BinaryIO: - self._start_time = self._get_timestamp() - file = super().create_file() - self._file_creates.on_next((self._curr_path, self._start_time)) - return file - - def close_file(self) -> None: - path = self._curr_path - super().close_file() - self._file_closes.on_next(path) - - 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()) - - def _make_path(self) -> str: - date_time = datetime.fromtimestamp(self._start_time) - relpath = self.path_template.format( - roomid=self._live.room_id, - 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'), - hour=str(date_time.hour).rjust(2, '0'), - minute=str(date_time.minute).rjust(2, '0'), - second=str(date_time.second).rjust(2, '0'), - ) - - pathname = os.path.abspath( - os.path.expanduser(os.path.join(self.out_dir, relpath) + '.flv') - ) - os.makedirs(os.path.dirname(pathname), exist_ok=True) - while os.path.exists(pathname): - root, ext = os.path.splitext(pathname) - m = re.search(r'_\((\d+)\)$', root) - if m is None: - root += '_(1)' - else: - root = re.sub(r'\(\d+\)$', f'({int(m.group(1)) + 1})', root) - pathname = root + ext - - return pathname diff --git a/src/blrec/core/stream_remuxer.py b/src/blrec/core/stream_remuxer.py index 6b3c4b4..162c222 100644 --- a/src/blrec/core/stream_remuxer.py +++ b/src/blrec/core/stream_remuxer.py @@ -98,23 +98,23 @@ class StreamRemuxer(StoppableMixin, SupportDebugMixin): self._exception = None try: self._run_subprocess() - except BrokenPipeError as e: - logger.debug(repr(e)) - except FFmpegError as e: + except BrokenPipeError as exc: + logger.debug(repr(exc)) + except FFmpegError as exc: if not self._stopped: - logger.warning(repr(e)) + logger.warning(repr(exc)) else: - logger.debug(repr(e)) - except TimeoutError as e: - logger.debug(repr(e)) - except Exception as e: + logger.debug(repr(exc)) + except TimeoutError as exc: + logger.debug(repr(exc)) + except Exception as exc: # OSError: [Errno 22] Invalid argument # https://stackoverflow.com/questions/23688492/oserror-errno-22-invalid-argument-in-subprocess - if isinstance(e, OSError) and e.errno == errno.EINVAL: + if isinstance(exc, OSError) and exc.errno == errno.EINVAL: pass else: - self._exception = e - logger.exception(e) + self._exception = exc + logger.exception(exc) finally: self._stopped = True logger.debug('Stopped stream remuxer') diff --git a/src/blrec/data/webapp/869.0ab6b8a3f466df77.js b/src/blrec/data/webapp/869.0ab6b8a3f466df77.js new file mode 100644 index 0000000..0248b68 --- /dev/null +++ b/src/blrec/data/webapp/869.0ab6b8a3f466df77.js @@ -0,0 +1 @@ +(self.webpackChunkblrec=self.webpackChunkblrec||[]).push([[869],{5869:(jt,ot,l)=>{"use strict";l.r(ot),l.d(ot,{TasksModule:()=>ga});var u=l(9808),m=l(4182),U=l(5113),t=l(5e3);class h{constructor(o,e){this._document=e;const i=this._textarea=this._document.createElement("textarea"),s=i.style;s.position="fixed",s.top=s.opacity="0",s.left="-999em",i.setAttribute("aria-hidden","true"),i.value=o,this._document.body.appendChild(i)}copy(){const o=this._textarea;let e=!1;try{if(o){const i=this._document.activeElement;o.select(),o.setSelectionRange(0,o.value.length),e=this._document.execCommand("copy"),i&&i.focus()}}catch(i){}return e}destroy(){const o=this._textarea;o&&(o.remove(),this._textarea=void 0)}}let q=(()=>{class n{constructor(e){this._document=e}copy(e){const i=this.beginCopy(e),s=i.copy();return i.destroy(),s}beginCopy(e){return new h(e,this._document)}}return n.\u0275fac=function(e){return new(e||n)(t.LFG(u.K0))},n.\u0275prov=t.Yz7({token:n,factory:n.\u0275fac,providedIn:"root"}),n})(),dt=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({}),n})();var P=l(1894),D=l(7484),M=l(647),_=l(655),v=l(8929),y=l(7625),L=l(8693),T=l(1721),b=l(226);function Y(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"i",1),t.NdJ("click",function(s){return t.CHM(e),t.oxw().closeTag(s)}),t.qZA()}}const X=["*"];let st=(()=>{class n{constructor(e,i,s,a){this.cdr=e,this.renderer=i,this.elementRef=s,this.directionality=a,this.isPresetColor=!1,this.nzMode="default",this.nzChecked=!1,this.nzOnClose=new t.vpe,this.nzCheckedChange=new t.vpe,this.dir="ltr",this.destroy$=new v.xQ}updateCheckedStatus(){"checkable"===this.nzMode&&(this.nzChecked=!this.nzChecked,this.nzCheckedChange.emit(this.nzChecked))}closeTag(e){this.nzOnClose.emit(e),e.defaultPrevented||this.renderer.removeChild(this.renderer.parentNode(this.elementRef.nativeElement),this.elementRef.nativeElement)}clearPresetColor(){const e=this.elementRef.nativeElement,i=new RegExp(`(ant-tag-(?:${[...L.uf,...L.Bh].join("|")}))`,"g"),s=e.classList.toString(),a=[];let c=i.exec(s);for(;null!==c;)a.push(c[1]),c=i.exec(s);e.classList.remove(...a)}setPresetColor(){const e=this.elementRef.nativeElement;this.clearPresetColor(),this.isPresetColor=!!this.nzColor&&((0,L.o2)(this.nzColor)||(0,L.M8)(this.nzColor)),this.isPresetColor&&e.classList.add(`ant-tag-${this.nzColor}`)}ngOnInit(){var e;null===(e=this.directionality.change)||void 0===e||e.pipe((0,y.R)(this.destroy$)).subscribe(i=>{this.dir=i,this.cdr.detectChanges()}),this.dir=this.directionality.value}ngOnChanges(e){const{nzColor:i}=e;i&&this.setPresetColor()}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO),t.Y36(t.Qsj),t.Y36(t.SBq),t.Y36(b.Is,8))},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-tag"]],hostAttrs:[1,"ant-tag"],hostVars:10,hostBindings:function(e,i){1&e&&t.NdJ("click",function(){return i.updateCheckedStatus()}),2&e&&(t.Udp("background-color",i.isPresetColor?"":i.nzColor),t.ekj("ant-tag-has-color",i.nzColor&&!i.isPresetColor)("ant-tag-checkable","checkable"===i.nzMode)("ant-tag-checkable-checked",i.nzChecked)("ant-tag-rtl","rtl"===i.dir))},inputs:{nzMode:"nzMode",nzColor:"nzColor",nzChecked:"nzChecked"},outputs:{nzOnClose:"nzOnClose",nzCheckedChange:"nzCheckedChange"},exportAs:["nzTag"],features:[t.TTD],ngContentSelectors:X,decls:2,vars:1,consts:[["nz-icon","","nzType","close","class","ant-tag-close-icon","tabindex","-1",3,"click",4,"ngIf"],["nz-icon","","nzType","close","tabindex","-1",1,"ant-tag-close-icon",3,"click"]],template:function(e,i){1&e&&(t.F$t(),t.Hsn(0),t.YNc(1,Y,1,0,"i",0)),2&e&&(t.xp6(1),t.Q6J("ngIf","closeable"===i.nzMode))},directives:[u.O5,M.Ls],encapsulation:2,changeDetection:0}),(0,_.gn)([(0,T.yF)()],n.prototype,"nzChecked",void 0),n})(),G=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[b.vT,u.ez,m.u5,M.PV]]}),n})();var at=l(6699);const $=["nzType","avatar"];function K(n,o){if(1&n&&(t.TgZ(0,"div",5),t._UZ(1,"nz-skeleton-element",6),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("nzSize",e.avatar.size||"default")("nzShape",e.avatar.shape||"circle")}}function mt(n,o){if(1&n&&t._UZ(0,"h3",7),2&n){const e=t.oxw(2);t.Udp("width",e.toCSSUnit(e.title.width))}}function _t(n,o){if(1&n&&t._UZ(0,"li"),2&n){const e=o.index,i=t.oxw(3);t.Udp("width",i.toCSSUnit(i.widthList[e]))}}function ht(n,o){if(1&n&&(t.TgZ(0,"ul",8),t.YNc(1,_t,1,2,"li",9),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("ngForOf",e.rowsList)}}function ft(n,o){if(1&n&&(t.ynx(0),t.YNc(1,K,2,2,"div",1),t.TgZ(2,"div",2),t.YNc(3,mt,1,2,"h3",3),t.YNc(4,ht,2,1,"ul",4),t.qZA(),t.BQk()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("ngIf",!!e.nzAvatar),t.xp6(2),t.Q6J("ngIf",!!e.nzTitle),t.xp6(1),t.Q6J("ngIf",!!e.nzParagraph)}}function zt(n,o){1&n&&(t.ynx(0),t.Hsn(1),t.BQk())}const Zt=["*"];let ke=(()=>{class n{constructor(){this.nzActive=!1,this.nzBlock=!1}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275dir=t.lG2({type:n,selectors:[["nz-skeleton-element"]],hostAttrs:[1,"ant-skeleton","ant-skeleton-element"],hostVars:4,hostBindings:function(e,i){2&e&&t.ekj("ant-skeleton-active",i.nzActive)("ant-skeleton-block",i.nzBlock)},inputs:{nzActive:"nzActive",nzType:"nzType",nzBlock:"nzBlock"}}),(0,_.gn)([(0,T.yF)()],n.prototype,"nzBlock",void 0),n})(),ve=(()=>{class n{constructor(){this.nzShape="circle",this.nzSize="default",this.styleMap={}}ngOnChanges(e){if(e.nzSize&&"number"==typeof this.nzSize){const i=`${this.nzSize}px`;this.styleMap={width:i,height:i,"line-height":i}}else this.styleMap={}}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-skeleton-element","nzType","avatar"]],inputs:{nzShape:"nzShape",nzSize:"nzSize"},features:[t.TTD],attrs:$,decls:1,vars:9,consts:[[1,"ant-skeleton-avatar",3,"ngStyle"]],template:function(e,i){1&e&&t._UZ(0,"span",0),2&e&&(t.ekj("ant-skeleton-avatar-square","square"===i.nzShape)("ant-skeleton-avatar-circle","circle"===i.nzShape)("ant-skeleton-avatar-lg","large"===i.nzSize)("ant-skeleton-avatar-sm","small"===i.nzSize),t.Q6J("ngStyle",i.styleMap))},directives:[u.PC],encapsulation:2,changeDetection:0}),n})(),ye=(()=>{class n{constructor(e,i,s){this.cdr=e,this.nzActive=!1,this.nzLoading=!0,this.nzRound=!1,this.nzTitle=!0,this.nzAvatar=!1,this.nzParagraph=!0,this.rowsList=[],this.widthList=[],i.addClass(s.nativeElement,"ant-skeleton")}toCSSUnit(e=""){return(0,T.WX)(e)}getTitleProps(){const e=!!this.nzAvatar,i=!!this.nzParagraph;let s="";return!e&&i?s="38%":e&&i&&(s="50%"),Object.assign({width:s},this.getProps(this.nzTitle))}getAvatarProps(){return Object.assign({shape:this.nzTitle&&!this.nzParagraph?"square":"circle",size:"large"},this.getProps(this.nzAvatar))}getParagraphProps(){const e=!!this.nzAvatar,i=!!this.nzTitle,s={};return(!e||!i)&&(s.width="61%"),s.rows=!e&&i?3:2,Object.assign(Object.assign({},s),this.getProps(this.nzParagraph))}getProps(e){return e&&"object"==typeof e?e:{}}getWidthList(){const{width:e,rows:i}=this.paragraph;let s=[];return e&&Array.isArray(e)?s=e:e&&!Array.isArray(e)&&(s=[],s[i-1]=e),s}updateProps(){this.title=this.getTitleProps(),this.avatar=this.getAvatarProps(),this.paragraph=this.getParagraphProps(),this.rowsList=[...Array(this.paragraph.rows)],this.widthList=this.getWidthList(),this.cdr.markForCheck()}ngOnInit(){this.updateProps()}ngOnChanges(e){(e.nzTitle||e.nzAvatar||e.nzParagraph)&&this.updateProps()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO),t.Y36(t.Qsj),t.Y36(t.SBq))},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-skeleton"]],hostVars:6,hostBindings:function(e,i){2&e&&t.ekj("ant-skeleton-with-avatar",!!i.nzAvatar)("ant-skeleton-active",i.nzActive)("ant-skeleton-round",!!i.nzRound)},inputs:{nzActive:"nzActive",nzLoading:"nzLoading",nzRound:"nzRound",nzTitle:"nzTitle",nzAvatar:"nzAvatar",nzParagraph:"nzParagraph"},exportAs:["nzSkeleton"],features:[t.TTD],ngContentSelectors:Zt,decls:2,vars:2,consts:[[4,"ngIf"],["class","ant-skeleton-header",4,"ngIf"],[1,"ant-skeleton-content"],["class","ant-skeleton-title",3,"width",4,"ngIf"],["class","ant-skeleton-paragraph",4,"ngIf"],[1,"ant-skeleton-header"],["nzType","avatar",3,"nzSize","nzShape"],[1,"ant-skeleton-title"],[1,"ant-skeleton-paragraph"],[3,"width",4,"ngFor","ngForOf"]],template:function(e,i){1&e&&(t.F$t(),t.YNc(0,ft,5,3,"ng-container",0),t.YNc(1,zt,2,0,"ng-container",0)),2&e&&(t.Q6J("ngIf",i.nzLoading),t.xp6(1),t.Q6J("ngIf",!i.nzLoading))},directives:[ve,u.O5,ke,u.sg],encapsulation:2,changeDetection:0}),n})(),Ht=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[b.vT,u.ez]]}),n})();var rt=l(404),Ot=l(6462),tt=l(3677),Ct=l(6042),V=l(7957),B=l(4546),et=l(1047),Wt=l(6114),be=l(4832),Se=l(2845),Ae=l(6950),De=l(5664),E=l(969),Me=l(4170);let Ie=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[b.vT,u.ez,Ct.sL,Se.U8,Me.YI,M.PV,E.T,Ae.e4,be.g,rt.cg,De.rt]]}),n})();var Tt=l(3868),Xt=l(5737),Kt=l(685),te=l(7525),Be=l(8076),S=l(9439);function Je(n,o){if(1&n&&(t.ynx(0),t._UZ(1,"i",5),t.BQk()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("nzType",e.nzIconType||e.inferredIconType)("nzTheme",e.iconTheme)}}function Qe(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(4);t.xp6(1),t.Oqu(e.nzMessage)}}function Ue(n,o){if(1&n&&(t.TgZ(0,"span",9),t.YNc(1,Qe,2,1,"ng-container",10),t.qZA()),2&n){const e=t.oxw(3);t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.nzMessage)}}function qe(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(4);t.xp6(1),t.Oqu(e.nzDescription)}}function Re(n,o){if(1&n&&(t.TgZ(0,"span",11),t.YNc(1,qe,2,1,"ng-container",10),t.qZA()),2&n){const e=t.oxw(3);t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.nzDescription)}}function Le(n,o){if(1&n&&(t.TgZ(0,"div",6),t.YNc(1,Ue,2,1,"span",7),t.YNc(2,Re,2,1,"span",8),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("ngIf",e.nzMessage),t.xp6(1),t.Q6J("ngIf",e.nzDescription)}}function Ye(n,o){1&n&&t._UZ(0,"i",15)}function Ge(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"span",16),t._uU(2),t.qZA(),t.BQk()),2&n){const e=t.oxw(4);t.xp6(2),t.Oqu(e.nzCloseText)}}function $e(n,o){if(1&n&&(t.ynx(0),t.YNc(1,Ge,3,1,"ng-container",10),t.BQk()),2&n){const e=t.oxw(3);t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.nzCloseText)}}function Ve(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"button",12),t.NdJ("click",function(){return t.CHM(e),t.oxw(2).closeAlert()}),t.YNc(1,Ye,1,0,"ng-template",null,13,t.W1O),t.YNc(3,$e,2,1,"ng-container",14),t.qZA()}if(2&n){const e=t.MAs(2),i=t.oxw(2);t.xp6(3),t.Q6J("ngIf",i.nzCloseText)("ngIfElse",e)}}function je(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"div",1),t.NdJ("@slideAlertMotion.done",function(){return t.CHM(e),t.oxw().onFadeAnimationDone()}),t.YNc(1,Je,2,2,"ng-container",2),t.YNc(2,Le,3,2,"div",3),t.YNc(3,Ve,4,2,"button",4),t.qZA()}if(2&n){const e=t.oxw();t.ekj("ant-alert-rtl","rtl"===e.dir)("ant-alert-success","success"===e.nzType)("ant-alert-info","info"===e.nzType)("ant-alert-warning","warning"===e.nzType)("ant-alert-error","error"===e.nzType)("ant-alert-no-icon",!e.nzShowIcon)("ant-alert-banner",e.nzBanner)("ant-alert-closable",e.nzCloseable)("ant-alert-with-description",!!e.nzDescription),t.Q6J("@.disabled",e.nzNoAnimation)("@slideAlertMotion",void 0),t.xp6(1),t.Q6J("ngIf",e.nzShowIcon),t.xp6(1),t.Q6J("ngIf",e.nzMessage||e.nzDescription),t.xp6(1),t.Q6J("ngIf",e.nzCloseable||e.nzCloseText)}}let He=(()=>{class n{constructor(e,i,s){this.nzConfigService=e,this.cdr=i,this.directionality=s,this._nzModuleName="alert",this.nzCloseText=null,this.nzIconType=null,this.nzMessage=null,this.nzDescription=null,this.nzType="info",this.nzCloseable=!1,this.nzShowIcon=!1,this.nzBanner=!1,this.nzNoAnimation=!1,this.nzOnClose=new t.vpe,this.closed=!1,this.iconTheme="fill",this.inferredIconType="info-circle",this.dir="ltr",this.isTypeSet=!1,this.isShowIconSet=!1,this.destroy$=new v.xQ,this.nzConfigService.getConfigChangeEventForComponent("alert").pipe((0,y.R)(this.destroy$)).subscribe(()=>{this.cdr.markForCheck()})}ngOnInit(){var e;null===(e=this.directionality.change)||void 0===e||e.pipe((0,y.R)(this.destroy$)).subscribe(i=>{this.dir=i,this.cdr.detectChanges()}),this.dir=this.directionality.value}closeAlert(){this.closed=!0}onFadeAnimationDone(){this.closed&&this.nzOnClose.emit(!0)}ngOnChanges(e){const{nzShowIcon:i,nzDescription:s,nzType:a,nzBanner:c}=e;if(i&&(this.isShowIconSet=!0),a)switch(this.isTypeSet=!0,this.nzType){case"error":this.inferredIconType="close-circle";break;case"success":this.inferredIconType="check-circle";break;case"info":this.inferredIconType="info-circle";break;case"warning":this.inferredIconType="exclamation-circle"}s&&(this.iconTheme=this.nzDescription?"outline":"fill"),c&&(this.isTypeSet||(this.nzType="warning"),this.isShowIconSet||(this.nzShowIcon=!0))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(S.jY),t.Y36(t.sBO),t.Y36(b.Is,8))},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-alert"]],inputs:{nzCloseText:"nzCloseText",nzIconType:"nzIconType",nzMessage:"nzMessage",nzDescription:"nzDescription",nzType:"nzType",nzCloseable:"nzCloseable",nzShowIcon:"nzShowIcon",nzBanner:"nzBanner",nzNoAnimation:"nzNoAnimation"},outputs:{nzOnClose:"nzOnClose"},exportAs:["nzAlert"],features:[t.TTD],decls:1,vars:1,consts:[["class","ant-alert",3,"ant-alert-rtl","ant-alert-success","ant-alert-info","ant-alert-warning","ant-alert-error","ant-alert-no-icon","ant-alert-banner","ant-alert-closable","ant-alert-with-description",4,"ngIf"],[1,"ant-alert"],[4,"ngIf"],["class","ant-alert-content",4,"ngIf"],["type","button","tabindex","0","class","ant-alert-close-icon",3,"click",4,"ngIf"],["nz-icon","",1,"ant-alert-icon",3,"nzType","nzTheme"],[1,"ant-alert-content"],["class","ant-alert-message",4,"ngIf"],["class","ant-alert-description",4,"ngIf"],[1,"ant-alert-message"],[4,"nzStringTemplateOutlet"],[1,"ant-alert-description"],["type","button","tabindex","0",1,"ant-alert-close-icon",3,"click"],["closeDefaultTemplate",""],[4,"ngIf","ngIfElse"],["nz-icon","","nzType","close"],[1,"ant-alert-close-text"]],template:function(e,i){1&e&&t.YNc(0,je,4,23,"div",0),2&e&&t.Q6J("ngIf",!i.closed)},directives:[u.O5,M.Ls,E.f],encapsulation:2,data:{animation:[Be.Rq]},changeDetection:0}),(0,_.gn)([(0,S.oS)(),(0,T.yF)()],n.prototype,"nzCloseable",void 0),(0,_.gn)([(0,S.oS)(),(0,T.yF)()],n.prototype,"nzShowIcon",void 0),(0,_.gn)([(0,T.yF)()],n.prototype,"nzBanner",void 0),(0,_.gn)([(0,T.yF)()],n.prototype,"nzNoAnimation",void 0),n})(),We=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[b.vT,u.ez,M.PV,E.T]]}),n})();var lt=l(4147),wt=l(5197);function Xe(n,o){if(1&n&&(t.ynx(0),t._UZ(1,"i",8),t.BQk()),2&n){const e=t.oxw(3);t.xp6(1),t.Q6J("nzType",e.icon)}}function Ke(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=o.$implicit,i=t.oxw(4);t.xp6(1),t.hij(" ",e(i.nzPercent)," ")}}const tn=function(n){return{$implicit:n}};function en(n,o){if(1&n&&t.YNc(0,Ke,2,1,"ng-container",9),2&n){const e=t.oxw(3);t.Q6J("nzStringTemplateOutlet",e.formatter)("nzStringTemplateOutletContext",t.VKq(2,tn,e.nzPercent))}}function nn(n,o){if(1&n&&(t.TgZ(0,"span",5),t.YNc(1,Xe,2,1,"ng-container",6),t.YNc(2,en,1,4,"ng-template",null,7,t.W1O),t.qZA()),2&n){const e=t.MAs(3),i=t.oxw(2);t.xp6(1),t.Q6J("ngIf",("exception"===i.status||"success"===i.status)&&!i.nzFormat)("ngIfElse",e)}}function on(n,o){if(1&n&&t.YNc(0,nn,4,2,"span",4),2&n){const e=t.oxw();t.Q6J("ngIf",e.nzShowInfo)}}function sn(n,o){if(1&n&&t._UZ(0,"div",17),2&n){const e=t.oxw(4);t.Udp("width",e.nzSuccessPercent,"%")("border-radius","round"===e.nzStrokeLinecap?"100px":"0")("height",e.strokeWidth,"px")}}function an(n,o){if(1&n&&(t.TgZ(0,"div",13),t.TgZ(1,"div",14),t._UZ(2,"div",15),t.YNc(3,sn,1,6,"div",16),t.qZA(),t.qZA()),2&n){const e=t.oxw(3);t.xp6(2),t.Udp("width",e.nzPercent,"%")("border-radius","round"===e.nzStrokeLinecap?"100px":"0")("background",e.isGradient?null:e.nzStrokeColor)("background-image",e.isGradient?e.lineGradient:null)("height",e.strokeWidth,"px"),t.xp6(1),t.Q6J("ngIf",e.nzSuccessPercent||0===e.nzSuccessPercent)}}function rn(n,o){}function ln(n,o){if(1&n&&(t.ynx(0),t.YNc(1,an,4,11,"div",11),t.YNc(2,rn,0,0,"ng-template",12),t.BQk()),2&n){const e=t.oxw(2),i=t.MAs(1);t.xp6(1),t.Q6J("ngIf",!e.isSteps),t.xp6(1),t.Q6J("ngTemplateOutlet",i)}}function cn(n,o){1&n&&t._UZ(0,"div",20),2&n&&t.Q6J("ngStyle",o.$implicit)}function un(n,o){}function pn(n,o){if(1&n&&(t.TgZ(0,"div",18),t.YNc(1,cn,1,1,"div",19),t.YNc(2,un,0,0,"ng-template",12),t.qZA()),2&n){const e=t.oxw(2),i=t.MAs(1);t.xp6(1),t.Q6J("ngForOf",e.steps),t.xp6(1),t.Q6J("ngTemplateOutlet",i)}}function gn(n,o){if(1&n&&(t.TgZ(0,"div"),t.YNc(1,ln,3,2,"ng-container",2),t.YNc(2,pn,3,2,"div",10),t.qZA()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("ngIf",!e.isSteps),t.xp6(1),t.Q6J("ngIf",e.isSteps)}}function dn(n,o){if(1&n&&(t.O4$(),t._UZ(0,"stop")),2&n){const e=o.$implicit;t.uIk("offset",e.offset)("stop-color",e.color)}}function mn(n,o){if(1&n&&(t.O4$(),t.TgZ(0,"defs"),t.TgZ(1,"linearGradient",24),t.YNc(2,dn,1,2,"stop",25),t.qZA(),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("id","gradient-"+e.gradientId),t.xp6(1),t.Q6J("ngForOf",e.circleGradient)}}function _n(n,o){if(1&n&&(t.O4$(),t._UZ(0,"path",26)),2&n){const e=o.$implicit,i=t.oxw(2);t.Q6J("ngStyle",e.strokePathStyle),t.uIk("d",i.pathString)("stroke-linecap",i.nzStrokeLinecap)("stroke",e.stroke)("stroke-width",i.nzPercent?i.strokeWidth:0)}}function hn(n,o){1&n&&t.O4$()}function fn(n,o){if(1&n&&(t.TgZ(0,"div",14),t.O4$(),t.TgZ(1,"svg",21),t.YNc(2,mn,3,2,"defs",2),t._UZ(3,"path",22),t.YNc(4,_n,1,5,"path",23),t.qZA(),t.YNc(5,hn,0,0,"ng-template",12),t.qZA()),2&n){const e=t.oxw(),i=t.MAs(1);t.Udp("width",e.nzWidth,"px")("height",e.nzWidth,"px")("font-size",.15*e.nzWidth+6,"px"),t.ekj("ant-progress-circle-gradient",e.isGradient),t.xp6(2),t.Q6J("ngIf",e.isGradient),t.xp6(1),t.Q6J("ngStyle",e.trailPathStyle),t.uIk("stroke-width",e.strokeWidth)("d",e.pathString),t.xp6(1),t.Q6J("ngForOf",e.progressCirclePath)("ngForTrackBy",e.trackByFn),t.xp6(1),t.Q6J("ngTemplateOutlet",i)}}const ne=n=>{let o=[];return Object.keys(n).forEach(e=>{const i=n[e],s=function zn(n){return+n.replace("%","")}(e);isNaN(s)||o.push({key:s,value:i})}),o=o.sort((e,i)=>e.key-i.key),o};let xn=0;const ie="progress",kn=new Map([["success","check"],["exception","close"]]),vn=new Map([["normal","#108ee9"],["exception","#ff5500"],["success","#87d068"]]),yn=n=>`${n}%`;let oe=(()=>{class n{constructor(e,i,s){this.cdr=e,this.nzConfigService=i,this.directionality=s,this._nzModuleName=ie,this.nzShowInfo=!0,this.nzWidth=132,this.nzStrokeColor=void 0,this.nzSize="default",this.nzPercent=0,this.nzStrokeWidth=void 0,this.nzGapDegree=void 0,this.nzType="line",this.nzGapPosition="top",this.nzStrokeLinecap="round",this.nzSteps=0,this.steps=[],this.lineGradient=null,this.isGradient=!1,this.isSteps=!1,this.gradientId=xn++,this.progressCirclePath=[],this.trailPathStyle=null,this.dir="ltr",this.trackByFn=a=>`${a}`,this.cachedStatus="normal",this.inferredStatus="normal",this.destroy$=new v.xQ}get formatter(){return this.nzFormat||yn}get status(){return this.nzStatus||this.inferredStatus}get strokeWidth(){return this.nzStrokeWidth||("line"===this.nzType&&"small"!==this.nzSize?8:6)}get isCircleStyle(){return"circle"===this.nzType||"dashboard"===this.nzType}ngOnChanges(e){const{nzSteps:i,nzGapPosition:s,nzStrokeLinecap:a,nzStrokeColor:c,nzGapDegree:p,nzType:d,nzStatus:f,nzPercent:r,nzSuccessPercent:N,nzStrokeWidth:I}=e;f&&(this.cachedStatus=this.nzStatus||this.cachedStatus),(r||N)&&(parseInt(this.nzPercent.toString(),10)>=100?((0,T.DX)(this.nzSuccessPercent)&&this.nzSuccessPercent>=100||void 0===this.nzSuccessPercent)&&(this.inferredStatus="success"):this.inferredStatus=this.cachedStatus),(f||r||N||c)&&this.updateIcon(),c&&this.setStrokeColor(),(s||a||p||d||r||c||c)&&this.getCirclePaths(),(r||i||I)&&(this.isSteps=this.nzSteps>0,this.isSteps&&this.getSteps())}ngOnInit(){var e;this.nzConfigService.getConfigChangeEventForComponent(ie).pipe((0,y.R)(this.destroy$)).subscribe(()=>{this.updateIcon(),this.setStrokeColor(),this.getCirclePaths()}),null===(e=this.directionality.change)||void 0===e||e.pipe((0,y.R)(this.destroy$)).subscribe(i=>{this.dir=i,this.cdr.detectChanges()}),this.dir=this.directionality.value}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}updateIcon(){const e=kn.get(this.status);this.icon=e?e+(this.isCircleStyle?"-o":"-circle-fill"):""}getSteps(){const e=Math.floor(this.nzSteps*(this.nzPercent/100)),i="small"===this.nzSize?2:14,s=[];for(let a=0;a{const Q=2===e.length&&0===I;return{stroke:this.isGradient&&!Q?`url(#gradient-${this.gradientId})`:null,strokePathStyle:{stroke:this.isGradient?null:Q?vn.get("success"):this.nzStrokeColor,transition:"stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s",strokeDasharray:`${(N||0)/100*(a-c)}px ${a}px`,strokeDashoffset:`-${c/2}px`}}}).reverse()}setStrokeColor(){const e=this.nzStrokeColor,i=this.isGradient=!!e&&"string"!=typeof e;i&&!this.isCircleStyle?this.lineGradient=(n=>{const{from:o="#1890ff",to:e="#1890ff",direction:i="to right"}=n,s=(0,_._T)(n,["from","to","direction"]);return 0!==Object.keys(s).length?`linear-gradient(${i}, ${ne(s).map(({key:c,value:p})=>`${p} ${c}%`).join(", ")})`:`linear-gradient(${i}, ${o}, ${e})`})(e):i&&this.isCircleStyle?this.circleGradient=(n=>ne(this.nzStrokeColor).map(({key:o,value:e})=>({offset:`${o}%`,color:e})))():(this.lineGradient=null,this.circleGradient=[])}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO),t.Y36(S.jY),t.Y36(b.Is,8))},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-progress"]],inputs:{nzShowInfo:"nzShowInfo",nzWidth:"nzWidth",nzStrokeColor:"nzStrokeColor",nzSize:"nzSize",nzFormat:"nzFormat",nzSuccessPercent:"nzSuccessPercent",nzPercent:"nzPercent",nzStrokeWidth:"nzStrokeWidth",nzGapDegree:"nzGapDegree",nzStatus:"nzStatus",nzType:"nzType",nzGapPosition:"nzGapPosition",nzStrokeLinecap:"nzStrokeLinecap",nzSteps:"nzSteps"},exportAs:["nzProgress"],features:[t.TTD],decls:5,vars:15,consts:[["progressInfoTemplate",""],[3,"ngClass"],[4,"ngIf"],["class","ant-progress-inner",3,"width","height","fontSize","ant-progress-circle-gradient",4,"ngIf"],["class","ant-progress-text",4,"ngIf"],[1,"ant-progress-text"],[4,"ngIf","ngIfElse"],["formatTemplate",""],["nz-icon","",3,"nzType"],[4,"nzStringTemplateOutlet","nzStringTemplateOutletContext"],["class","ant-progress-steps-outer",4,"ngIf"],["class","ant-progress-outer",4,"ngIf"],[3,"ngTemplateOutlet"],[1,"ant-progress-outer"],[1,"ant-progress-inner"],[1,"ant-progress-bg"],["class","ant-progress-success-bg",3,"width","border-radius","height",4,"ngIf"],[1,"ant-progress-success-bg"],[1,"ant-progress-steps-outer"],["class","ant-progress-steps-item",3,"ngStyle",4,"ngFor","ngForOf"],[1,"ant-progress-steps-item",3,"ngStyle"],["viewBox","0 0 100 100",1,"ant-progress-circle"],["stroke","#f3f3f3","fill-opacity","0",1,"ant-progress-circle-trail",3,"ngStyle"],["class","ant-progress-circle-path","fill-opacity","0",3,"ngStyle",4,"ngFor","ngForOf","ngForTrackBy"],["x1","100%","y1","0%","x2","0%","y2","0%",3,"id"],[4,"ngFor","ngForOf"],["fill-opacity","0",1,"ant-progress-circle-path",3,"ngStyle"]],template:function(e,i){1&e&&(t.YNc(0,on,1,1,"ng-template",null,0,t.W1O),t.TgZ(2,"div",1),t.YNc(3,gn,3,2,"div",2),t.YNc(4,fn,6,15,"div",3),t.qZA()),2&e&&(t.xp6(2),t.ekj("ant-progress-line","line"===i.nzType)("ant-progress-small","small"===i.nzSize)("ant-progress-show-info",i.nzShowInfo)("ant-progress-circle",i.isCircleStyle)("ant-progress-steps",i.isSteps)("ant-progress-rtl","rtl"===i.dir),t.Q6J("ngClass","ant-progress ant-progress-status-"+i.status),t.xp6(1),t.Q6J("ngIf","line"===i.nzType),t.xp6(1),t.Q6J("ngIf",i.isCircleStyle))},directives:[u.O5,M.Ls,E.f,u.mk,u.tP,u.sg,u.PC],encapsulation:2,changeDetection:0}),(0,_.gn)([(0,S.oS)()],n.prototype,"nzShowInfo",void 0),(0,_.gn)([(0,S.oS)()],n.prototype,"nzStrokeColor",void 0),(0,_.gn)([(0,S.oS)()],n.prototype,"nzSize",void 0),(0,_.gn)([(0,T.Rn)()],n.prototype,"nzSuccessPercent",void 0),(0,_.gn)([(0,T.Rn)()],n.prototype,"nzPercent",void 0),(0,_.gn)([(0,S.oS)(),(0,T.Rn)()],n.prototype,"nzStrokeWidth",void 0),(0,_.gn)([(0,S.oS)(),(0,T.Rn)()],n.prototype,"nzGapDegree",void 0),(0,_.gn)([(0,S.oS)()],n.prototype,"nzGapPosition",void 0),(0,_.gn)([(0,S.oS)()],n.prototype,"nzStrokeLinecap",void 0),(0,_.gn)([(0,T.Rn)()],n.prototype,"nzSteps",void 0),n})(),bn=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[b.vT,u.ez,M.PV,E.T]]}),n})();var J=l(592),se=l(925);let Sn=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[u.ez]]}),n})();const An=function(n){return{$implicit:n}};function Dn(n,o){if(1&n&&t.GkF(0,3),2&n){const e=t.oxw();t.Q6J("ngTemplateOutlet",e.nzValueTemplate)("ngTemplateOutletContext",t.VKq(2,An,e.nzValue))}}function Mn(n,o){if(1&n&&(t.TgZ(0,"span",6),t._uU(1),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Oqu(e.displayInt)}}function Zn(n,o){if(1&n&&(t.TgZ(0,"span",7),t._uU(1),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Oqu(e.displayDecimal)}}function On(n,o){if(1&n&&(t.ynx(0),t.YNc(1,Mn,2,1,"span",4),t.YNc(2,Zn,2,1,"span",5),t.BQk()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("ngIf",e.displayInt),t.xp6(1),t.Q6J("ngIf",e.displayDecimal)}}function wn(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw();t.xp6(1),t.Oqu(e.nzTitle)}}function Fn(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(2);t.xp6(1),t.Oqu(e.nzPrefix)}}function Nn(n,o){if(1&n&&(t.TgZ(0,"span",7),t.YNc(1,Fn,2,1,"ng-container",2),t.qZA()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.nzPrefix)}}function Pn(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(2);t.xp6(1),t.Oqu(e.nzSuffix)}}function En(n,o){if(1&n&&(t.TgZ(0,"span",8),t.YNc(1,Pn,2,1,"ng-container",2),t.qZA()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.nzSuffix)}}let In=(()=>{class n{constructor(e){this.locale_id=e,this.displayInt="",this.displayDecimal=""}ngOnChanges(){this.formatNumber()}formatNumber(){const e="number"==typeof this.nzValue?".":(0,u.dv)(this.locale_id,u.wE.Decimal),i=String(this.nzValue),[s,a]=i.split(e);this.displayInt=s,this.displayDecimal=a?`${e}${a}`:""}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.soG))},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-statistic-number"]],inputs:{nzValue:"nzValue",nzValueTemplate:"nzValueTemplate"},exportAs:["nzStatisticNumber"],features:[t.TTD],decls:3,vars:2,consts:[[1,"ant-statistic-content-value"],[3,"ngTemplateOutlet","ngTemplateOutletContext",4,"ngIf"],[4,"ngIf"],[3,"ngTemplateOutlet","ngTemplateOutletContext"],["class","ant-statistic-content-value-int",4,"ngIf"],["class","ant-statistic-content-value-decimal",4,"ngIf"],[1,"ant-statistic-content-value-int"],[1,"ant-statistic-content-value-decimal"]],template:function(e,i){1&e&&(t.TgZ(0,"span",0),t.YNc(1,Dn,1,4,"ng-container",1),t.YNc(2,On,3,2,"ng-container",2),t.qZA()),2&e&&(t.xp6(1),t.Q6J("ngIf",i.nzValueTemplate),t.xp6(1),t.Q6J("ngIf",!i.nzValueTemplate))},directives:[u.O5,u.tP],encapsulation:2,changeDetection:0}),n})(),ae=(()=>{class n{constructor(e,i){this.cdr=e,this.directionality=i,this.nzValueStyle={},this.dir="ltr",this.destroy$=new v.xQ}ngOnInit(){var e;null===(e=this.directionality.change)||void 0===e||e.pipe((0,y.R)(this.destroy$)).subscribe(i=>{this.dir=i,this.cdr.detectChanges()}),this.dir=this.directionality.value}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO),t.Y36(b.Is,8))},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-statistic"]],inputs:{nzPrefix:"nzPrefix",nzSuffix:"nzSuffix",nzTitle:"nzTitle",nzValue:"nzValue",nzValueStyle:"nzValueStyle",nzValueTemplate:"nzValueTemplate"},exportAs:["nzStatistic"],decls:7,vars:8,consts:[[1,"ant-statistic"],[1,"ant-statistic-title"],[4,"nzStringTemplateOutlet"],[1,"ant-statistic-content",3,"ngStyle"],["class","ant-statistic-content-prefix",4,"ngIf"],[3,"nzValue","nzValueTemplate"],["class","ant-statistic-content-suffix",4,"ngIf"],[1,"ant-statistic-content-prefix"],[1,"ant-statistic-content-suffix"]],template:function(e,i){1&e&&(t.TgZ(0,"div",0),t.TgZ(1,"div",1),t.YNc(2,wn,2,1,"ng-container",2),t.qZA(),t.TgZ(3,"div",3),t.YNc(4,Nn,2,1,"span",4),t._UZ(5,"nz-statistic-number",5),t.YNc(6,En,2,1,"span",6),t.qZA(),t.qZA()),2&e&&(t.ekj("ant-statistic-rtl","rtl"===i.dir),t.xp6(2),t.Q6J("nzStringTemplateOutlet",i.nzTitle),t.xp6(1),t.Q6J("ngStyle",i.nzValueStyle),t.xp6(1),t.Q6J("ngIf",i.nzPrefix),t.xp6(1),t.Q6J("nzValue",i.nzValue)("nzValueTemplate",i.nzValueTemplate),t.xp6(1),t.Q6J("ngIf",i.nzSuffix))},directives:[In,E.f,u.PC,u.O5],encapsulation:2,changeDetection:0}),n})(),Bn=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[b.vT,u.ez,se.ud,E.T,Sn]]}),n})();var re=l(6787),Jn=l(1059),nt=l(7545),Qn=l(7138),k=l(2994),Un=l(6947),Ft=l(4090);function qn(n,o){1&n&&t.Hsn(0)}const Rn=["*"];function Ln(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(3);t.xp6(1),t.Oqu(e.nzTitle)}}function Yn(n,o){if(1&n&&(t.TgZ(0,"div",6),t.YNc(1,Ln,2,1,"ng-container",7),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.nzTitle)}}function Gn(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(3);t.xp6(1),t.Oqu(e.nzExtra)}}function $n(n,o){if(1&n&&(t.TgZ(0,"div",8),t.YNc(1,Gn,2,1,"ng-container",7),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.nzExtra)}}function Vn(n,o){if(1&n&&(t.TgZ(0,"div",3),t.YNc(1,Yn,2,1,"div",4),t.YNc(2,$n,2,1,"div",5),t.qZA()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("ngIf",e.nzTitle),t.xp6(1),t.Q6J("ngIf",e.nzExtra)}}function jn(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(2).$implicit;t.xp6(1),t.hij(" ",e.title," ")}}function Hn(n,o){}function Wn(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"td",12),t.TgZ(2,"div",13),t.TgZ(3,"span",14),t.YNc(4,jn,2,1,"ng-container",7),t.qZA(),t.TgZ(5,"span",15),t.YNc(6,Hn,0,0,"ng-template",16),t.qZA(),t.qZA(),t.qZA(),t.BQk()),2&n){const e=t.oxw().$implicit,i=t.oxw(3);t.xp6(1),t.Q6J("colSpan",e.span),t.xp6(2),t.ekj("ant-descriptions-item-no-colon",!i.nzColon),t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.title),t.xp6(2),t.Q6J("ngTemplateOutlet",e.content)}}function Xn(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(3).$implicit;t.xp6(1),t.hij(" ",e.title," ")}}function Kn(n,o){if(1&n&&(t.TgZ(0,"td",14),t.YNc(1,Xn,2,1,"ng-container",7),t.qZA()),2&n){const e=t.oxw(2).$implicit;t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.title)}}function ti(n,o){}function ei(n,o){if(1&n&&(t.ynx(0),t.YNc(1,Kn,2,1,"td",17),t.TgZ(2,"td",18),t.YNc(3,ti,0,0,"ng-template",16),t.qZA(),t.BQk()),2&n){const e=t.oxw().$implicit;t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.title),t.xp6(1),t.Q6J("colSpan",2*e.span-1),t.xp6(1),t.Q6J("ngTemplateOutlet",e.content)}}function ni(n,o){if(1&n&&(t.ynx(0),t.YNc(1,Wn,7,5,"ng-container",2),t.YNc(2,ei,4,3,"ng-container",2),t.BQk()),2&n){const e=t.oxw(3);t.xp6(1),t.Q6J("ngIf",!e.nzBordered),t.xp6(1),t.Q6J("ngIf",e.nzBordered)}}function ii(n,o){if(1&n&&(t.TgZ(0,"tr",10),t.YNc(1,ni,3,2,"ng-container",11),t.qZA()),2&n){const e=o.$implicit;t.xp6(1),t.Q6J("ngForOf",e)}}function oi(n,o){if(1&n&&(t.ynx(0),t.YNc(1,ii,2,1,"tr",9),t.BQk()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("ngForOf",e.itemMatrix)}}function si(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw().$implicit;t.xp6(1),t.hij(" ",e.title," ")}}function ai(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"td",12),t.TgZ(2,"div",13),t.TgZ(3,"span",14),t.YNc(4,si,2,1,"ng-container",7),t.qZA(),t.qZA(),t.qZA(),t.BQk()),2&n){const e=o.$implicit,i=t.oxw(4);t.xp6(1),t.Q6J("colSpan",e.span),t.xp6(2),t.ekj("ant-descriptions-item-no-colon",!i.nzColon),t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.title)}}function ri(n,o){}function li(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"td",12),t.TgZ(2,"div",13),t.TgZ(3,"span",15),t.YNc(4,ri,0,0,"ng-template",16),t.qZA(),t.qZA(),t.qZA(),t.BQk()),2&n){const e=o.$implicit;t.xp6(1),t.Q6J("colSpan",e.span),t.xp6(3),t.Q6J("ngTemplateOutlet",e.content)}}function ci(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"tr",10),t.YNc(2,ai,5,4,"ng-container",11),t.qZA(),t.TgZ(3,"tr",10),t.YNc(4,li,5,2,"ng-container",11),t.qZA(),t.BQk()),2&n){const e=o.$implicit;t.xp6(2),t.Q6J("ngForOf",e),t.xp6(2),t.Q6J("ngForOf",e)}}function ui(n,o){if(1&n&&(t.ynx(0),t.YNc(1,ci,5,2,"ng-container",11),t.BQk()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("ngForOf",e.itemMatrix)}}function pi(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw().$implicit;t.xp6(1),t.hij(" ",e.title," ")}}function gi(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"td",19),t.YNc(2,pi,2,1,"ng-container",7),t.qZA(),t.BQk()),2&n){const e=o.$implicit;t.xp6(1),t.Q6J("colSpan",e.span),t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.title)}}function di(n,o){}function mi(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"td",18),t.YNc(2,di,0,0,"ng-template",16),t.qZA(),t.BQk()),2&n){const e=o.$implicit;t.xp6(1),t.Q6J("colSpan",e.span),t.xp6(1),t.Q6J("ngTemplateOutlet",e.content)}}function _i(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"tr",10),t.YNc(2,gi,3,2,"ng-container",11),t.qZA(),t.TgZ(3,"tr",10),t.YNc(4,mi,3,2,"ng-container",11),t.qZA(),t.BQk()),2&n){const e=o.$implicit;t.xp6(2),t.Q6J("ngForOf",e),t.xp6(2),t.Q6J("ngForOf",e)}}function hi(n,o){if(1&n&&(t.ynx(0),t.YNc(1,_i,5,2,"ng-container",11),t.BQk()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("ngForOf",e.itemMatrix)}}function fi(n,o){if(1&n&&(t.ynx(0),t.YNc(1,ui,2,1,"ng-container",2),t.YNc(2,hi,2,1,"ng-container",2),t.BQk()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("ngIf",!e.nzBordered),t.xp6(1),t.Q6J("ngIf",e.nzBordered)}}let Nt=(()=>{class n{constructor(){this.nzSpan=1,this.nzTitle="",this.inputChange$=new v.xQ}ngOnChanges(){this.inputChange$.next()}ngOnDestroy(){this.inputChange$.complete()}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-descriptions-item"]],viewQuery:function(e,i){if(1&e&&t.Gf(t.Rgc,7),2&e){let s;t.iGM(s=t.CRH())&&(i.content=s.first)}},inputs:{nzSpan:"nzSpan",nzTitle:"nzTitle"},exportAs:["nzDescriptionsItem"],features:[t.TTD],ngContentSelectors:Rn,decls:1,vars:0,template:function(e,i){1&e&&(t.F$t(),t.YNc(0,qn,1,0,"ng-template"))},encapsulation:2,changeDetection:0}),(0,_.gn)([(0,T.Rn)()],n.prototype,"nzSpan",void 0),n})();const Ci={xxl:3,xl:3,lg:3,md:3,sm:2,xs:1};let le=(()=>{class n{constructor(e,i,s,a){this.nzConfigService=e,this.cdr=i,this.breakpointService=s,this.directionality=a,this._nzModuleName="descriptions",this.nzBordered=!1,this.nzLayout="horizontal",this.nzColumn=Ci,this.nzSize="default",this.nzTitle="",this.nzColon=!0,this.itemMatrix=[],this.realColumn=3,this.dir="ltr",this.breakpoint=Ft.G_.md,this.destroy$=new v.xQ}ngOnInit(){var e;this.dir=this.directionality.value,null===(e=this.directionality.change)||void 0===e||e.pipe((0,y.R)(this.destroy$)).subscribe(i=>{this.dir=i})}ngOnChanges(e){e.nzColumn&&this.prepareMatrix()}ngAfterContentInit(){const e=this.items.changes.pipe((0,Jn.O)(this.items),(0,y.R)(this.destroy$));(0,re.T)(e,e.pipe((0,nt.w)(()=>(0,re.T)(...this.items.map(i=>i.inputChange$)).pipe((0,Qn.e)(16)))),this.breakpointService.subscribe(Ft.WV).pipe((0,k.b)(i=>this.breakpoint=i))).pipe((0,y.R)(this.destroy$)).subscribe(()=>{this.prepareMatrix(),this.cdr.markForCheck()})}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}prepareMatrix(){if(!this.items)return;let e=[],i=0;const s=this.realColumn=this.getColumn(),a=this.items.toArray(),c=a.length,p=[],d=()=>{p.push(e),e=[],i=0};for(let f=0;f=s?(i>s&&(0,Un.ZK)(`"nzColumn" is ${s} but we have row length ${i}`),e.push({title:N,content:I,span:s-(i-Q)}),d()):f===c-1?(e.push({title:N,content:I,span:s-(i-Q)}),d()):e.push({title:N,content:I,span:Q})}this.itemMatrix=p}getColumn(){return"number"!=typeof this.nzColumn?this.nzColumn[this.breakpoint]:this.nzColumn}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(S.jY),t.Y36(t.sBO),t.Y36(Ft.r3),t.Y36(b.Is,8))},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-descriptions"]],contentQueries:function(e,i,s){if(1&e&&t.Suo(s,Nt,4),2&e){let a;t.iGM(a=t.CRH())&&(i.items=a)}},hostAttrs:[1,"ant-descriptions"],hostVars:8,hostBindings:function(e,i){2&e&&t.ekj("ant-descriptions-bordered",i.nzBordered)("ant-descriptions-middle","middle"===i.nzSize)("ant-descriptions-small","small"===i.nzSize)("ant-descriptions-rtl","rtl"===i.dir)},inputs:{nzBordered:"nzBordered",nzLayout:"nzLayout",nzColumn:"nzColumn",nzSize:"nzSize",nzTitle:"nzTitle",nzExtra:"nzExtra",nzColon:"nzColon"},exportAs:["nzDescriptions"],features:[t.TTD],decls:6,vars:3,consts:[["class","ant-descriptions-header",4,"ngIf"],[1,"ant-descriptions-view"],[4,"ngIf"],[1,"ant-descriptions-header"],["class","ant-descriptions-title",4,"ngIf"],["class","ant-descriptions-extra",4,"ngIf"],[1,"ant-descriptions-title"],[4,"nzStringTemplateOutlet"],[1,"ant-descriptions-extra"],["class","ant-descriptions-row",4,"ngFor","ngForOf"],[1,"ant-descriptions-row"],[4,"ngFor","ngForOf"],[1,"ant-descriptions-item",3,"colSpan"],[1,"ant-descriptions-item-container"],[1,"ant-descriptions-item-label"],[1,"ant-descriptions-item-content"],[3,"ngTemplateOutlet"],["class","ant-descriptions-item-label",4,"nzStringTemplateOutlet"],[1,"ant-descriptions-item-content",3,"colSpan"],[1,"ant-descriptions-item-label",3,"colSpan"]],template:function(e,i){1&e&&(t.YNc(0,Vn,3,2,"div",0),t.TgZ(1,"div",1),t.TgZ(2,"table"),t.TgZ(3,"tbody"),t.YNc(4,oi,2,1,"ng-container",2),t.YNc(5,fi,3,2,"ng-container",2),t.qZA(),t.qZA(),t.qZA()),2&e&&(t.Q6J("ngIf",i.nzTitle||i.nzExtra),t.xp6(4),t.Q6J("ngIf","horizontal"===i.nzLayout),t.xp6(1),t.Q6J("ngIf","vertical"===i.nzLayout))},directives:[u.O5,E.f,u.sg,u.tP],encapsulation:2,changeDetection:0}),(0,_.gn)([(0,T.yF)(),(0,S.oS)()],n.prototype,"nzBordered",void 0),(0,_.gn)([(0,S.oS)()],n.prototype,"nzColumn",void 0),(0,_.gn)([(0,S.oS)()],n.prototype,"nzSize",void 0),(0,_.gn)([(0,S.oS)(),(0,T.yF)()],n.prototype,"nzColon",void 0),n})(),Ti=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[b.vT,u.ez,E.T,se.ud]]}),n})();var A=l(1086),xt=l(8896),kt=l(353),ce=l(6498),ue=l(3489);const pe={leading:!0,trailing:!1};class yi{constructor(o,e,i,s){this.duration=o,this.scheduler=e,this.leading=i,this.trailing=s}call(o,e){return e.subscribe(new bi(o,this.duration,this.scheduler,this.leading,this.trailing))}}class bi extends ue.L{constructor(o,e,i,s,a){super(o),this.duration=e,this.scheduler=i,this.leading=s,this.trailing=a,this._hasTrailingValue=!1,this._trailingValue=null}_next(o){this.throttled?this.trailing&&(this._trailingValue=o,this._hasTrailingValue=!0):(this.add(this.throttled=this.scheduler.schedule(Si,this.duration,{subscriber:this})),this.leading?this.destination.next(o):this.trailing&&(this._trailingValue=o,this._hasTrailingValue=!0))}_complete(){this._hasTrailingValue?(this.destination.next(this._trailingValue),this.destination.complete()):this.destination.complete()}clearThrottle(){const o=this.throttled;o&&(this.trailing&&this._hasTrailingValue&&(this.destination.next(this._trailingValue),this._trailingValue=null,this._hasTrailingValue=!1),o.unsubscribe(),this.remove(o),this.throttled=null)}}function Si(n){const{subscriber:o}=n;o.clearThrottle()}class Pt{constructor(o){this.changes=o}static of(o){return new Pt(o)}notEmpty(o){if(this.changes[o]){const e=this.changes[o].currentValue;if(null!=e)return(0,A.of)(e)}return xt.E}has(o){return this.changes[o]?(0,A.of)(this.changes[o].currentValue):xt.E}notFirst(o){return this.changes[o]&&!this.changes[o].isFirstChange()?(0,A.of)(this.changes[o].currentValue):xt.E}notFirstAndEmpty(o){if(this.changes[o]&&!this.changes[o].isFirstChange()){const e=this.changes[o].currentValue;if(null!=e)return(0,A.of)(e)}return xt.E}}const ge=new t.OlP("NGX_ECHARTS_CONFIG");let de=(()=>{class n{constructor(e,i,s){this.el=i,this.ngZone=s,this.autoResize=!0,this.loadingType="default",this.chartInit=new t.vpe,this.optionsError=new t.vpe,this.chartClick=this.createLazyEvent("click"),this.chartDblClick=this.createLazyEvent("dblclick"),this.chartMouseDown=this.createLazyEvent("mousedown"),this.chartMouseMove=this.createLazyEvent("mousemove"),this.chartMouseUp=this.createLazyEvent("mouseup"),this.chartMouseOver=this.createLazyEvent("mouseover"),this.chartMouseOut=this.createLazyEvent("mouseout"),this.chartGlobalOut=this.createLazyEvent("globalout"),this.chartContextMenu=this.createLazyEvent("contextmenu"),this.chartLegendSelectChanged=this.createLazyEvent("legendselectchanged"),this.chartLegendSelected=this.createLazyEvent("legendselected"),this.chartLegendUnselected=this.createLazyEvent("legendunselected"),this.chartLegendScroll=this.createLazyEvent("legendscroll"),this.chartDataZoom=this.createLazyEvent("datazoom"),this.chartDataRangeSelected=this.createLazyEvent("datarangeselected"),this.chartTimelineChanged=this.createLazyEvent("timelinechanged"),this.chartTimelinePlayChanged=this.createLazyEvent("timelineplaychanged"),this.chartRestore=this.createLazyEvent("restore"),this.chartDataViewChanged=this.createLazyEvent("dataviewchanged"),this.chartMagicTypeChanged=this.createLazyEvent("magictypechanged"),this.chartPieSelectChanged=this.createLazyEvent("pieselectchanged"),this.chartPieSelected=this.createLazyEvent("pieselected"),this.chartPieUnselected=this.createLazyEvent("pieunselected"),this.chartMapSelectChanged=this.createLazyEvent("mapselectchanged"),this.chartMapSelected=this.createLazyEvent("mapselected"),this.chartMapUnselected=this.createLazyEvent("mapunselected"),this.chartAxisAreaSelected=this.createLazyEvent("axisareaselected"),this.chartFocusNodeAdjacency=this.createLazyEvent("focusnodeadjacency"),this.chartUnfocusNodeAdjacency=this.createLazyEvent("unfocusnodeadjacency"),this.chartBrush=this.createLazyEvent("brush"),this.chartBrushEnd=this.createLazyEvent("brushend"),this.chartBrushSelected=this.createLazyEvent("brushselected"),this.chartRendered=this.createLazyEvent("rendered"),this.chartFinished=this.createLazyEvent("finished"),this.animationFrameID=null,this.resize$=new v.xQ,this.echarts=e.echarts}ngOnChanges(e){const i=Pt.of(e);i.notFirstAndEmpty("options").subscribe(s=>this.onOptionsChange(s)),i.notFirstAndEmpty("merge").subscribe(s=>this.setOption(s)),i.has("loading").subscribe(s=>this.toggleLoading(!!s)),i.notFirst("theme").subscribe(()=>this.refreshChart())}ngOnInit(){if(!window.ResizeObserver)throw new Error("please install a polyfill for ResizeObserver");this.resizeSub=this.resize$.pipe(function vi(n,o=kt.P,e=pe){return i=>i.lift(new yi(n,o,e.leading,e.trailing))}(100,kt.z,{leading:!1,trailing:!0})).subscribe(()=>this.resize()),this.autoResize&&(this.resizeOb=this.ngZone.runOutsideAngular(()=>new window.ResizeObserver(()=>{this.animationFrameID=window.requestAnimationFrame(()=>this.resize$.next())})),this.resizeOb.observe(this.el.nativeElement))}ngOnDestroy(){window.clearTimeout(this.initChartTimer),this.resizeSub&&this.resizeSub.unsubscribe(),this.animationFrameID&&window.cancelAnimationFrame(this.animationFrameID),this.resizeOb&&this.resizeOb.unobserve(this.el.nativeElement),this.dispose()}ngAfterViewInit(){this.initChartTimer=window.setTimeout(()=>this.initChart())}dispose(){this.chart&&(this.chart.isDisposed()||this.chart.dispose(),this.chart=null)}resize(){this.chart&&this.chart.resize()}toggleLoading(e){this.chart&&(e?this.chart.showLoading(this.loadingType,this.loadingOpts):this.chart.hideLoading())}setOption(e,i){if(this.chart)try{this.chart.setOption(e,i)}catch(s){console.error(s),this.optionsError.emit(s)}}refreshChart(){return(0,_.mG)(this,void 0,void 0,function*(){this.dispose(),yield this.initChart()})}createChart(){const e=this.el.nativeElement;if(window&&window.getComputedStyle){const i=window.getComputedStyle(e,null).getPropertyValue("height");(!i||"0px"===i)&&(!e.style.height||"0px"===e.style.height)&&(e.style.height="400px")}return this.ngZone.runOutsideAngular(()=>("function"==typeof this.echarts?this.echarts:()=>Promise.resolve(this.echarts))().then(({init:s})=>s(e,this.theme,this.initOpts)))}initChart(){return(0,_.mG)(this,void 0,void 0,function*(){yield this.onOptionsChange(this.options),this.merge&&this.chart&&this.setOption(this.merge)})}onOptionsChange(e){return(0,_.mG)(this,void 0,void 0,function*(){!e||(this.chart||(this.chart=yield this.createChart(),this.chartInit.emit(this.chart)),this.setOption(this.options,!0))})}createLazyEvent(e){return this.chartInit.pipe((0,nt.w)(i=>new ce.y(s=>(i.on(e,a=>this.ngZone.run(()=>s.next(a))),()=>{this.chart&&(this.chart.isDisposed()||i.off(e))}))))}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(ge),t.Y36(t.SBq),t.Y36(t.R0b))},n.\u0275dir=t.lG2({type:n,selectors:[["echarts"],["","echarts",""]],inputs:{options:"options",theme:"theme",loading:"loading",initOpts:"initOpts",merge:"merge",autoResize:"autoResize",loadingType:"loadingType",loadingOpts:"loadingOpts"},outputs:{chartInit:"chartInit",optionsError:"optionsError",chartClick:"chartClick",chartDblClick:"chartDblClick",chartMouseDown:"chartMouseDown",chartMouseMove:"chartMouseMove",chartMouseUp:"chartMouseUp",chartMouseOver:"chartMouseOver",chartMouseOut:"chartMouseOut",chartGlobalOut:"chartGlobalOut",chartContextMenu:"chartContextMenu",chartLegendSelectChanged:"chartLegendSelectChanged",chartLegendSelected:"chartLegendSelected",chartLegendUnselected:"chartLegendUnselected",chartLegendScroll:"chartLegendScroll",chartDataZoom:"chartDataZoom",chartDataRangeSelected:"chartDataRangeSelected",chartTimelineChanged:"chartTimelineChanged",chartTimelinePlayChanged:"chartTimelinePlayChanged",chartRestore:"chartRestore",chartDataViewChanged:"chartDataViewChanged",chartMagicTypeChanged:"chartMagicTypeChanged",chartPieSelectChanged:"chartPieSelectChanged",chartPieSelected:"chartPieSelected",chartPieUnselected:"chartPieUnselected",chartMapSelectChanged:"chartMapSelectChanged",chartMapSelected:"chartMapSelected",chartMapUnselected:"chartMapUnselected",chartAxisAreaSelected:"chartAxisAreaSelected",chartFocusNodeAdjacency:"chartFocusNodeAdjacency",chartUnfocusNodeAdjacency:"chartUnfocusNodeAdjacency",chartBrush:"chartBrush",chartBrushEnd:"chartBrushEnd",chartBrushSelected:"chartBrushSelected",chartRendered:"chartRendered",chartFinished:"chartFinished"},exportAs:["echarts"],features:[t.TTD]}),n})(),Ai=(()=>{class n{static forRoot(e){return{ngModule:n,providers:[{provide:ge,useValue:e}]}}static forChild(){return{ngModule:n}}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[]]}),n})();var Di=l(4466),ct=l(2302),Mi=l(4241);function vt(n=0,o=kt.P){return(!(0,Mi.k)(n)||n<0)&&(n=0),(!o||"function"!=typeof o.schedule)&&(o=kt.P),new ce.y(e=>(e.add(o.schedule(Zi,n,{subscriber:e,counter:0,period:n})),e))}function Zi(n){const{subscriber:o,counter:e,period:i}=n;o.next(e),this.schedule({subscriber:o,counter:e+1,period:i},i)}var Oi=l(3009),wi=l(6688),yt=l(5430),Et=l(1177);function It(...n){const o=n[n.length-1];return"function"==typeof o&&n.pop(),(0,Oi.n)(n,void 0).lift(new Fi(o))}class Fi{constructor(o){this.resultSelector=o}call(o,e){return e.subscribe(new Ni(o,this.resultSelector))}}class Ni extends ue.L{constructor(o,e,i=Object.create(null)){super(o),this.resultSelector=e,this.iterators=[],this.active=0,this.resultSelector="function"==typeof e?e:void 0}_next(o){const e=this.iterators;(0,wi.k)(o)?e.push(new Ei(o)):e.push("function"==typeof o[yt.hZ]?new Pi(o[yt.hZ]()):new Ii(this.destination,this,o))}_complete(){const o=this.iterators,e=o.length;if(this.unsubscribe(),0!==e){this.active=e;for(let i=0;ithis.index}hasCompleted(){return this.array.length===this.index}}class Ii extends Et.Ds{constructor(o,e,i){super(o),this.parent=e,this.observable=i,this.stillUnsubscribed=!0,this.buffer=[],this.isComplete=!1}[yt.hZ](){return this}next(){const o=this.buffer;return 0===o.length&&this.isComplete?{value:null,done:!0}:{value:o.shift(),done:!1}}hasValue(){return this.buffer.length>0}hasCompleted(){return 0===this.buffer.length&&this.isComplete}notifyComplete(){this.buffer.length>0?(this.isComplete=!0,this.parent.notifyInactive()):this.destination.complete()}notifyNext(o){this.buffer.push(o),this.parent.checkIterators()}subscribe(){return(0,Et.ft)(this.observable,new Et.IY(this))}}var Bt=l(534),it=l(7221),bt=l(7106),Jt=l(5278),Bi=l(2340),Z=(()=>{return(n=Z||(Z={})).ALL="all",n.PREPARING="preparing",n.LIVING="living",n.ROUNDING="rounding",n.MONITOR_ENABLED="monitor_enabled",n.MONITOR_DISABLED="monitor_disabled",n.RECORDER_ENABLED="recorder_enabled",n.RECORDER_DISABLED="recorder_disabled",n.STOPPED="stopped",n.WAITTING="waitting",n.RECORDING="recording",n.REMUXING="remuxing",n.INJECTING="injecting",Z;var n})(),O=(()=>{return(n=O||(O={})).STOPPED="stopped",n.WAITING="waiting",n.RECORDING="recording",n.REMUXING="remuxing",n.INJECTING="injecting",O;var n})(),ut=(()=>{return(n=ut||(ut={})).WAITING="waiting",n.REMUXING="remuxing",n.INJECTING="injecting",ut;var n})(),x=(()=>{return(n=x||(x={})).RECORDING="recording",n.REMUXING="remuxing",n.INJECTING="injecting",n.COMPLETED="completed",n.MISSING="missing",n.BROKEN="broken",x;var n})(),Ji=l(520);const C=Bi.N.apiUrl;let St=(()=>{class n{constructor(e){this.http=e}getAllTaskData(e=Z.ALL){return this.http.get(C+"/api/v1/tasks/data",{params:{select:e}})}getTaskData(e){return this.http.get(C+`/api/v1/tasks/${e}/data`)}getVideoFileDetails(e){return this.http.get(C+`/api/v1/tasks/${e}/videos`)}getDanmakuFileDetails(e){return this.http.get(C+`/api/v1/tasks/${e}/danmakus`)}getTaskParam(e){return this.http.get(C+`/api/v1/tasks/${e}/param`)}getMetadata(e){return this.http.get(C+`/api/v1/tasks/${e}/metadata`)}getStreamProfile(e){return this.http.get(C+`/api/v1/tasks/${e}/profile`)}updateAllTaskInfos(){return this.http.post(C+"/api/v1/tasks/info",null)}updateTaskInfo(e){return this.http.post(C+`/api/v1/tasks/${e}/info`,null)}addTask(e){return this.http.post(C+`/api/v1/tasks/${e}`,null)}removeTask(e){return this.http.delete(C+`/api/v1/tasks/${e}`)}removeAllTasks(){return this.http.delete(C+"/api/v1/tasks")}startTask(e){return this.http.post(C+`/api/v1/tasks/${e}/start`,null)}startAllTasks(){return this.http.post(C+"/api/v1/tasks/start",null)}stopTask(e,i=!1,s=!1){return this.http.post(C+`/api/v1/tasks/${e}/stop`,{force:i,background:s})}stopAllTasks(e=!1,i=!1){return this.http.post(C+"/api/v1/tasks/stop",{force:e,background:i})}enableTaskMonitor(e){return this.http.post(C+`/api/v1/tasks/${e}/monitor/enable`,null)}enableAllMonitors(){return this.http.post(C+"/api/v1/tasks/monitor/enable",null)}disableTaskMonitor(e,i=!1){return this.http.post(C+`/api/v1/tasks/${e}/monitor/disable`,{background:i})}disableAllMonitors(e=!1){return this.http.post(C+"/api/v1/tasks/monitor/disable",{background:e})}enableTaskRecorder(e){return this.http.post(C+`/api/v1/tasks/${e}/recorder/enable`,null)}enableAllRecorders(){return this.http.post(C+"/api/v1/tasks/recorder/enable",null)}disableTaskRecorder(e,i=!1,s=!1){return this.http.post(C+`/api/v1/tasks/${e}/recorder/disable`,{force:i,background:s})}disableAllRecorders(e=!1,i=!1){return this.http.post(C+"/api/v1/tasks/recorder/disable",{force:e,background:i})}cutStream(e){return this.http.post(C+`/api/v1/tasks/${e}/cut`,null)}}return n.\u0275fac=function(e){return new(e||n)(t.LFG(Ji.eN))},n.\u0275prov=t.Yz7({token:n,factory:n.\u0275fac,providedIn:"root"}),n})();var Qi=l(7512),Ui=l(5545);let qi=(()=>{class n{constructor(){this.loading=!0}ngOnInit(){}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-user-info-detail"]],inputs:{loading:"loading",userInfo:"userInfo"},decls:12,vars:6,consts:[["nzTitle","\u4e3b\u64ad\u4fe1\u606f",3,"nzLoading"],["nzTitle",""],["nzTitle","\u6635\u79f0"],["nzTitle","\u6027\u522b"],["nzTitle","UID"],["nzTitle","\u7b49\u7ea7"],["nzTitle","\u7b7e\u540d"]],template:function(e,i){1&e&&(t.TgZ(0,"nz-card",0),t.TgZ(1,"nz-descriptions",1),t.TgZ(2,"nz-descriptions-item",2),t._uU(3),t.qZA(),t.TgZ(4,"nz-descriptions-item",3),t._uU(5),t.qZA(),t.TgZ(6,"nz-descriptions-item",4),t._uU(7),t.qZA(),t.TgZ(8,"nz-descriptions-item",5),t._uU(9),t.qZA(),t.TgZ(10,"nz-descriptions-item",6),t._uU(11),t.qZA(),t.qZA(),t.qZA()),2&e&&(t.Q6J("nzLoading",i.loading),t.xp6(3),t.Oqu(i.userInfo.name),t.xp6(2),t.Oqu(i.userInfo.gender),t.xp6(2),t.Oqu(i.userInfo.uid),t.xp6(2),t.Oqu(i.userInfo.level),t.xp6(2),t.hij(" ",i.userInfo.sign," "))},directives:[D.bd,le,Nt],styles:[""],changeDetection:0}),n})();function Ri(n,o){if(1&n&&(t.TgZ(0,"span",18),t._uU(1),t.qZA()),2&n){const e=t.oxw();t.xp6(1),t.hij("",e.roomInfo.short_room_id," ")}}function Li(n,o){1&n&&(t.ynx(0),t._uU(1,"\u95f2\u7f6e"),t.BQk())}function Yi(n,o){1&n&&(t.ynx(0),t._uU(1,"\u76f4\u64ad\u4e2d"),t.BQk())}function Gi(n,o){1&n&&(t.ynx(0),t._uU(1,"\u8f6e\u64ad\u4e2d"),t.BQk())}function $i(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.ALo(2,"date"),t.BQk()),2&n){const e=t.oxw();t.xp6(1),t.hij(" ",t.Dn7(2,1,1e3*e.roomInfo.live_start_time,"YYYY-MM-dd HH:mm:ss","+8")," ")}}function Vi(n,o){if(1&n&&(t.TgZ(0,"nz-tag"),t._uU(1),t.qZA()),2&n){const e=o.$implicit;t.xp6(1),t.hij(" ",e," ")}}function ji(n,o){if(1&n&&(t.TgZ(0,"p"),t._uU(1),t.qZA()),2&n){const e=o.$implicit;t.xp6(1),t.Oqu(e)}}let Hi=(()=>{class n{constructor(){this.loading=!0}ngOnInit(){}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-room-info-detail"]],inputs:{loading:"loading",roomInfo:"roomInfo"},decls:24,vars:13,consts:[["nzTitle","\u76f4\u64ad\u95f4\u4fe1\u606f",3,"nzLoading"],["nzTitle",""],["nzTitle","\u6807\u9898"],["nzTitle","\u5206\u533a"],["nzTitle","\u623f\u95f4\u53f7"],[1,"room-id-wrapper"],["class","short-room-id",4,"ngIf"],[1,"real-room-id"],["nzTitle","\u72b6\u6001"],[3,"ngSwitch"],[4,"ngSwitchCase"],["nzTitle","\u5f00\u64ad\u65f6\u95f4"],[4,"ngIf"],["nzTitle","\u6807\u7b7e"],[1,"tags"],[4,"ngFor","ngForOf"],["nzTitle","\u7b80\u4ecb"],[1,"introduction"],[1,"short-room-id"]],template:function(e,i){1&e&&(t.TgZ(0,"nz-card",0),t.TgZ(1,"nz-descriptions",1),t.TgZ(2,"nz-descriptions-item",2),t._uU(3),t.qZA(),t.TgZ(4,"nz-descriptions-item",3),t._uU(5),t.qZA(),t.TgZ(6,"nz-descriptions-item",4),t.TgZ(7,"span",5),t.YNc(8,Ri,2,1,"span",6),t.TgZ(9,"span",7),t._uU(10),t.qZA(),t.qZA(),t.qZA(),t.TgZ(11,"nz-descriptions-item",8),t.ynx(12,9),t.YNc(13,Li,2,0,"ng-container",10),t.YNc(14,Yi,2,0,"ng-container",10),t.YNc(15,Gi,2,0,"ng-container",10),t.BQk(),t.qZA(),t.TgZ(16,"nz-descriptions-item",11),t.YNc(17,$i,3,5,"ng-container",12),t.qZA(),t.TgZ(18,"nz-descriptions-item",13),t.TgZ(19,"div",14),t.YNc(20,Vi,2,1,"nz-tag",15),t.qZA(),t.qZA(),t.TgZ(21,"nz-descriptions-item",16),t.TgZ(22,"div",17),t.YNc(23,ji,2,1,"p",15),t.qZA(),t.qZA(),t.qZA(),t.qZA()),2&e&&(t.Q6J("nzLoading",i.loading),t.xp6(3),t.Oqu(i.roomInfo.title),t.xp6(2),t.AsE(" ",i.roomInfo.parent_area_name," - ",i.roomInfo.area_name," "),t.xp6(3),t.Q6J("ngIf",i.roomInfo.short_room_id),t.xp6(2),t.hij(" ",i.roomInfo.room_id," "),t.xp6(2),t.Q6J("ngSwitch",i.roomInfo.live_status),t.xp6(1),t.Q6J("ngSwitchCase",0),t.xp6(1),t.Q6J("ngSwitchCase",1),t.xp6(1),t.Q6J("ngSwitchCase",2),t.xp6(2),t.Q6J("ngIf",0!==i.roomInfo.live_start_time),t.xp6(3),t.Q6J("ngForOf",i.roomInfo.tags.split(",")),t.xp6(3),t.Q6J("ngForOf",i.roomInfo.description.split("\n")))},directives:[D.bd,le,Nt,u.O5,u.RF,u.n9,u.sg,st],pipes:[u.uU],styles:['.room-id-wrapper[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap}.room-id-wrapper[_ngcontent-%COMP%] .short-room-id[_ngcontent-%COMP%]:after{display:inline-block;width:1em;content:","}.tags[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap;row-gap:.5em}.introduction[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{margin:0;padding:0}'],changeDetection:0}),n})();var j=l(2134);let me=(()=>{class n{transform(e){if(e<0)throw RangeError("the argument totalSeconds must be greater than or equal to 0");const i=Math.floor(e/3600),s=Math.floor(e/60%60),a=Math.floor(e%60);let c="";return i>0&&(c+=i+":"),c+=s<10?"0"+s:s,c+=":",c+=a<10?"0"+a:a,c}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"duration",type:n,pure:!0}),n})(),At=(()=>{class n{transform(e,i){if("string"==typeof e)e=parseFloat(e);else if("number"!=typeof e||isNaN(e))return"N/A";return(i=Object.assign({bitrate:!1,precision:3,spacer:" "},i)).bitrate?(0,j.AX)(e,i.spacer,i.precision):(0,j.N4)(e,i.spacer,i.precision)}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"datarate",type:n,pure:!0}),n})();var Wi=l(855);let Dt=(()=>{class n{transform(e,i){return Wi(e,i)}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"filesize",type:n,pure:!0}),n})();const Xi={2e4:"4K",1e4:"\u539f\u753b",401:"\u84dd\u5149(\u675c\u6bd4)",400:"\u84dd\u5149",250:"\u8d85\u6e05",150:"\u9ad8\u6e05",80:"\u6d41\u7545"};let Qt=(()=>{class n{transform(e){return Xi[e]}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"quality",type:n,pure:!0}),n})();function Ki(n,o){if(1&n&&(t._uU(0),t.ALo(1,"duration")),2&n){const e=t.oxw();t.Oqu(t.lcZ(1,1,e.taskStatus.rec_elapsed))}}function to(n,o){if(1&n&&(t._uU(0),t.ALo(1,"datarate")),2&n){const e=t.oxw();t.Oqu(t.lcZ(1,1,e.taskStatus.rec_rate))}}const eo=function(){return{spacer:" "}};function no(n,o){if(1&n&&(t._uU(0),t.ALo(1,"filesize")),2&n){const e=t.oxw();t.Oqu(t.xi3(1,1,e.taskStatus.rec_total,t.DdM(4,eo)))}}function io(n,o){if(1&n&&(t._uU(0),t.ALo(1,"quality")),2&n){const e=t.oxw();t.Oqu(e.taskStatus.real_quality_number?t.lcZ(1,1,e.taskStatus.real_quality_number)+" ("+e.taskStatus.real_quality_number+")":"")}}let oo=(()=>{class n{constructor(e){this.changeDetector=e,this.loading=!0,this.initialChartOptions={},this.updatedChartOptions={},this.chartData=[],this.initChartOptions()}ngOnChanges(){this.taskStatus.running_status===O.RECORDING&&this.updateChartOptions()}initChartOptions(){const e=Date.now();for(let i=59;i>=0;i--){const s=new Date(e-1e3*i);this.chartData.push({name:s.toLocaleString("zh-CN",{hour12:!1}),value:[s.toISOString(),0]})}this.initialChartOptions={title:{},tooltip:{trigger:"axis",formatter:i=>{const s=i[0];return`\n
\n
\n ${new Date(s.name).toLocaleTimeString("zh-CN",{hour12:!1})}\n
\n
${(0,j.N4)(s.value[1])}
\n
\n `},axisPointer:{animation:!1}},xAxis:{type:"time",name:"\u65f6\u95f4",min:"dataMin",max:"dataMax",splitLine:{show:!0}},yAxis:{type:"value",name:"\u5f55\u5236\u901f\u5ea6",splitLine:{show:!0},axisLabel:{formatter:i=>(0,j.N4)(i)}},series:[{name:"\u5f55\u5236\u901f\u5ea6",type:"line",showSymbol:!1,smooth:!0,lineStyle:{width:1},areaStyle:{opacity:.2},data:this.chartData}]}}updateChartOptions(){const e=new Date;this.chartData.push({name:e.toLocaleString("zh-CN",{hour12:!1}),value:[e.toISOString(),this.taskStatus.rec_rate]}),this.chartData.shift(),this.updatedChartOptions={series:[{data:this.chartData}]},this.changeDetector.markForCheck()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-recording-detail"]],inputs:{loading:"loading",taskStatus:"taskStatus"},features:[t.TTD],decls:17,vars:17,consts:[["nzTitle","\u5f55\u5236\u8be6\u60c5",3,"nzLoading"],[1,"statistics"],[3,"nzTitle","nzValueTemplate"],["recordingElapsed",""],["recordingRate",""],["recordedTotal",""],["recordingQuality",""],[3,"nzTitle","nzValue"],["echarts","",1,"rec-rate-chart",3,"loading","options","merge"]],template:function(e,i){if(1&e&&(t.TgZ(0,"nz-card",0),t.TgZ(1,"div",1),t._UZ(2,"nz-statistic",2),t.YNc(3,Ki,2,3,"ng-template",null,3,t.W1O),t._UZ(5,"nz-statistic",2),t.YNc(6,to,2,3,"ng-template",null,4,t.W1O),t._UZ(8,"nz-statistic",2),t.YNc(9,no,2,5,"ng-template",null,5,t.W1O),t._UZ(11,"nz-statistic",2),t.YNc(12,io,2,3,"ng-template",null,6,t.W1O),t._UZ(14,"nz-statistic",7),t.ALo(15,"number"),t.qZA(),t._UZ(16,"div",8),t.qZA()),2&e){const s=t.MAs(4),a=t.MAs(7),c=t.MAs(10),p=t.MAs(13);t.Q6J("nzLoading",i.loading),t.xp6(2),t.Q6J("nzTitle","\u5f55\u5236\u7528\u65f6")("nzValueTemplate",s),t.xp6(3),t.Q6J("nzTitle","\u5f55\u5236\u901f\u5ea6")("nzValueTemplate",a),t.xp6(3),t.Q6J("nzTitle","\u5f55\u5236\u603b\u8ba1")("nzValueTemplate",c),t.xp6(3),t.Q6J("nzTitle","\u5f55\u5236\u753b\u8d28")("nzValueTemplate",p),t.xp6(3),t.Q6J("nzTitle","\u5f39\u5e55\u603b\u8ba1")("nzValue",t.xi3(15,14,i.taskStatus.danmu_total,"1.0-2")),t.xp6(2),t.Q6J("loading",i.loading)("options",i.initialChartOptions)("merge",i.updatedChartOptions)}},directives:[D.bd,ae,de],pipes:[me,At,Dt,Qt,u.JJ],styles:[".statistics[_ngcontent-%COMP%]{--grid-width: 200px;display:grid;grid-template-columns:repeat(auto-fill,var(--grid-width));grid-gap:1em;gap:1em;justify-content:center;margin:0 auto}@media screen and (max-width: 1024px){.statistics[_ngcontent-%COMP%]{--grid-width: 180px}}@media screen and (max-width: 720px){.statistics[_ngcontent-%COMP%]{--grid-width: 160px}}@media screen and (max-width: 680px){.statistics[_ngcontent-%COMP%]{--grid-width: 140px}}@media screen and (max-width: 480px){.statistics[_ngcontent-%COMP%]{--grid-width: 120px}}.rec-rate-chart[_ngcontent-%COMP%]{width:100%;height:300px;margin:0}"],changeDetection:0}),n})();function so(n,o){if(1&n&&t._uU(0),2&n){const e=t.oxw();t.Oqu(e.taskStatus.stream_host)}}function ao(n,o){if(1&n&&t._uU(0),2&n){const e=t.oxw();t.Oqu(e.taskStatus.real_stream_format)}}const ro=function(){return{bitrate:!0}};function lo(n,o){if(1&n&&(t._uU(0),t.ALo(1,"datarate")),2&n){const e=t.oxw();t.Oqu(t.xi3(1,1,8*e.taskStatus.dl_rate,t.DdM(4,ro)))}}const co=function(){return{spacer:" "}};function uo(n,o){if(1&n&&(t._uU(0),t.ALo(1,"filesize")),2&n){const e=t.oxw();t.Oqu(t.xi3(1,1,e.taskStatus.dl_total,t.DdM(4,co)))}}let po=(()=>{class n{constructor(e){this.changeDetector=e,this.loading=!0,this.initialChartOptions={},this.updatedChartOptions={},this.chartData=[],this.initChartOptions()}ngOnChanges(){this.taskStatus.running_status===O.RECORDING&&this.updateChartOptions()}initChartOptions(){const e=Date.now();for(let i=59;i>=0;i--){const s=new Date(e-1e3*i);this.chartData.push({name:s.toLocaleString("zh-CN",{hour12:!1}),value:[s.toISOString(),0]})}this.initialChartOptions={title:{},tooltip:{trigger:"axis",formatter:i=>{const s=i[0];return`\n
\n
\n ${new Date(s.name).toLocaleTimeString("zh-CN",{hour12:!1})}\n
\n
${(0,j.AX)(s.value[1])}
\n
\n `},axisPointer:{animation:!1}},xAxis:{type:"time",name:"\u65f6\u95f4",min:"dataMin",max:"dataMax",splitLine:{show:!0}},yAxis:{type:"value",name:"\u4e0b\u8f7d\u901f\u5ea6",splitLine:{show:!0},axisLabel:{formatter:function(i){return(0,j.AX)(i)}}},series:[{name:"\u4e0b\u8f7d\u901f\u5ea6",type:"line",showSymbol:!1,smooth:!0,lineStyle:{width:1},areaStyle:{opacity:.2},data:this.chartData}]}}updateChartOptions(){const e=new Date;this.chartData.push({name:e.toLocaleString("zh-CN",{hour12:!1}),value:[e.toISOString(),8*this.taskStatus.dl_rate]}),this.chartData.shift(),this.updatedChartOptions={series:[{data:this.chartData}]},this.changeDetector.markForCheck()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-network-detail"]],inputs:{loading:"loading",taskStatus:"taskStatus"},features:[t.TTD],decls:15,vars:12,consts:[["nzTitle","\u7f51\u7edc\u8be6\u60c5",3,"nzLoading"],[1,"statistics"],[1,"stream-host",3,"nzTitle","nzValueTemplate"],["streamHost",""],[3,"nzTitle","nzValueTemplate"],["realStreamFormat",""],["downloadRate",""],["downloadTotal",""],["echarts","",1,"dl-rate-chart",3,"loading","options","merge"]],template:function(e,i){if(1&e&&(t.TgZ(0,"nz-card",0),t.TgZ(1,"div",1),t._UZ(2,"nz-statistic",2),t.YNc(3,so,1,1,"ng-template",null,3,t.W1O),t._UZ(5,"nz-statistic",4),t.YNc(6,ao,1,1,"ng-template",null,5,t.W1O),t._UZ(8,"nz-statistic",4),t.YNc(9,lo,2,5,"ng-template",null,6,t.W1O),t._UZ(11,"nz-statistic",4),t.YNc(12,uo,2,5,"ng-template",null,7,t.W1O),t.qZA(),t._UZ(14,"div",8),t.qZA()),2&e){const s=t.MAs(4),a=t.MAs(7),c=t.MAs(10),p=t.MAs(13);t.Q6J("nzLoading",i.loading),t.xp6(2),t.Q6J("nzTitle","\u6d41\u4e3b\u673a")("nzValueTemplate",s),t.xp6(3),t.Q6J("nzTitle","\u6d41\u683c\u5f0f")("nzValueTemplate",a),t.xp6(3),t.Q6J("nzTitle","\u4e0b\u8f7d\u901f\u5ea6")("nzValueTemplate",c),t.xp6(3),t.Q6J("nzTitle","\u4e0b\u8f7d\u603b\u8ba1")("nzValueTemplate",p),t.xp6(3),t.Q6J("loading",i.loading)("options",i.initialChartOptions)("merge",i.updatedChartOptions)}},directives:[D.bd,ae,de],pipes:[At,Dt],styles:[".statistics[_ngcontent-%COMP%]{--grid-width: 200px;display:grid;grid-template-columns:repeat(auto-fill,var(--grid-width));grid-gap:1em;gap:1em;justify-content:center;margin:0 auto}@media screen and (max-width: 1024px){.statistics[_ngcontent-%COMP%]{--grid-width: 180px}}@media screen and (max-width: 720px){.statistics[_ngcontent-%COMP%]{--grid-width: 160px}}@media screen and (max-width: 680px){.statistics[_ngcontent-%COMP%]{--grid-width: 140px}}@media screen and (max-width: 480px){.statistics[_ngcontent-%COMP%]{--grid-width: 120px}}.stream-host[_ngcontent-%COMP%]{grid-column:1/3;grid-row:1}.dl-rate-chart[_ngcontent-%COMP%]{width:100%;height:300px;margin:0}"],changeDetection:0}),n})(),Ut=(()=>{class n{transform(e){var i,s;return e?e.startsWith("/")?null!==(i=e.split("/").pop())&&void 0!==i?i:"":null!==(s=e.split("\\").pop())&&void 0!==s?s:"":""}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"filename",type:n,pure:!0}),n})(),_e=(()=>{class n{transform(e){return e&&0!==e.total?Math.round(e.count/e.total*100):0}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"progress",type:n,pure:!0}),n})(),go=(()=>{class n{constructor(){this.loading=!0}ngOnInit(){}get title(){switch(this.taskStatus.postprocessor_status){case ut.INJECTING:return"\u66f4\u65b0 FLV \u5143\u6570\u636e";case ut.REMUXING:return"\u8f6c\u6362 FLV \u4e3a MP4";default:return"\u6587\u4ef6\u5904\u7406"}}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-postprocessing-detail"]],inputs:{loading:"loading",taskStatus:"taskStatus"},decls:6,vars:9,consts:[[3,"nzTitle","nzLoading"],[3,"title"],["nzStatus","active",3,"nzPercent"]],template:function(e,i){if(1&e&&(t.TgZ(0,"nz-card",0),t.TgZ(1,"p",1),t._uU(2),t.ALo(3,"filename"),t.qZA(),t._UZ(4,"nz-progress",2),t.ALo(5,"progress"),t.qZA()),2&e){let s;t.Q6J("nzTitle",i.title)("nzLoading",i.loading),t.xp6(1),t.Q6J("title",i.taskStatus.postprocessing_path),t.xp6(1),t.hij(" ",t.lcZ(3,5,null!==(s=i.taskStatus.postprocessing_path)&&void 0!==s?s:"")," "),t.xp6(2),t.Q6J("nzPercent",null===i.taskStatus.postprocessing_progress?0:t.lcZ(5,7,i.taskStatus.postprocessing_progress))}},directives:[D.bd,oe],pipes:[Ut,_e],styles:["p[_ngcontent-%COMP%]{margin:0}"],changeDetection:0}),n})();const mo=new Map([[x.RECORDING,"\u5f55\u5236\u4e2d"],[x.INJECTING,"\u5904\u7406\u4e2d"],[x.REMUXING,"\u5904\u7406\u4e2d"],[x.COMPLETED,"\u5df2\u5b8c\u6210"],[x.MISSING,"\u4e0d\u5b58\u5728"],[x.BROKEN,"\u5f55\u5236\u4e2d\u65ad"]]);let _o=(()=>{class n{transform(e){var i;return null!==(i=mo.get(e))&&void 0!==i?i:"\uff1f\uff1f\uff1f"}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"filestatus",type:n,pure:!0}),n})();function ho(n,o){if(1&n&&(t.TgZ(0,"th",5),t._uU(1),t.qZA()),2&n){const e=o.$implicit;t.Q6J("nzSortOrder",e.sortOrder)("nzSortFn",e.sortFn)("nzSortDirections",e.sortDirections)("nzFilters",e.listOfFilter)("nzFilterFn",e.filterFn)("nzFilterMultiple",e.filterMultiple)("nzShowFilter",e.listOfFilter.length>0),t.xp6(1),t.hij(" ",e.name," ")}}function fo(n,o){if(1&n&&(t.TgZ(0,"tr"),t.TgZ(1,"td",6),t._uU(2),t.ALo(3,"filename"),t.qZA(),t.TgZ(4,"td",6),t.ALo(5,"number"),t._uU(6),t.ALo(7,"filesize"),t.qZA(),t.TgZ(8,"td",6),t._uU(9),t.ALo(10,"filestatus"),t.qZA(),t.qZA()),2&n){const e=o.$implicit;t.xp6(1),t.s9C("title",e.path),t.xp6(1),t.Oqu(t.lcZ(3,9,e.path)),t.xp6(2),t.s9C("title",t.lcZ(5,11,e.size)),t.xp6(2),t.Oqu(t.lcZ(7,13,e.size)),t.xp6(2),t.Gre("status ",e.status,""),t.s9C("title",e.status),t.xp6(1),t.hij(" ",t.lcZ(10,15,e.status)," ")}}const he=[x.RECORDING,x.INJECTING,x.REMUXING,x.COMPLETED,x.MISSING];let zo=(()=>{class n{constructor(){this.loading=!0,this.videoFileDetails=[],this.danmakuFileDetails=[],this.VideoFileStatus=x,this.fileDetails=[],this.columns=[{name:"\u6587\u4ef6",sortOrder:"ascend",sortFn:(e,i)=>e.path.localeCompare(i.path),sortDirections:["ascend","descend"],filterMultiple:!1,listOfFilter:[{text:"\u89c6\u9891",value:"video"},{text:"\u5f39\u5e55",value:"danmaku"}],filterFn:(e,i)=>{switch(e){case"video":return i.path.endsWith(".flv")||i.path.endsWith(".mp4");case"danmaku":return i.path.endsWith(".xml");default:return!1}}},{name:"\u5927\u5c0f",sortOrder:null,sortFn:(e,i)=>e.size-i.size,sortDirections:["ascend","descend",null],filterMultiple:!0,listOfFilter:[],filterFn:null},{name:"\u72b6\u6001",sortOrder:null,sortFn:(e,i)=>he.indexOf(e.status)-he.indexOf(i.status),sortDirections:["ascend","descend",null],filterMultiple:!0,listOfFilter:[{text:"\u5f55\u5236\u4e2d",value:[x.RECORDING]},{text:"\u5904\u7406\u4e2d",value:[x.INJECTING,x.REMUXING]},{text:"\u5df2\u5b8c\u6210",value:[x.COMPLETED]},{text:"\u4e0d\u5b58\u5728",value:[x.MISSING]}],filterFn:(e,i)=>e.some(s=>s.some(a=>a===i.status))}]}ngOnChanges(){this.fileDetails=[...this.videoFileDetails,...this.danmakuFileDetails]}trackByPath(e,i){return i.path}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-file-detail"]],inputs:{loading:"loading",videoFileDetails:"videoFileDetails",danmakuFileDetails:"danmakuFileDetails"},features:[t.TTD],decls:8,vars:8,consts:[["nzTitle","\u6587\u4ef6\u8be6\u60c5",3,"nzLoading"],[3,"nzLoading","nzData","nzPageSize","nzHideOnSinglePage"],["fileDetailsTable",""],[3,"nzSortOrder","nzSortFn","nzSortDirections","nzFilters","nzFilterFn","nzFilterMultiple","nzShowFilter",4,"ngFor","ngForOf"],[4,"ngFor","ngForOf","ngForTrackBy"],[3,"nzSortOrder","nzSortFn","nzSortDirections","nzFilters","nzFilterFn","nzFilterMultiple","nzShowFilter"],[3,"title"]],template:function(e,i){if(1&e&&(t.TgZ(0,"nz-card",0),t.TgZ(1,"nz-table",1,2),t.TgZ(3,"thead"),t.TgZ(4,"tr"),t.YNc(5,ho,2,8,"th",3),t.qZA(),t.qZA(),t.TgZ(6,"tbody"),t.YNc(7,fo,11,17,"tr",4),t.qZA(),t.qZA(),t.qZA()),2&e){const s=t.MAs(2);t.Q6J("nzLoading",i.loading),t.xp6(1),t.Q6J("nzLoading",i.loading)("nzData",i.fileDetails)("nzPageSize",8)("nzHideOnSinglePage",!0),t.xp6(4),t.Q6J("ngForOf",i.columns),t.xp6(2),t.Q6J("ngForOf",s.data)("ngForTrackBy",i.trackByPath)}},directives:[D.bd,J.N8,J.Om,J.$Z,u.sg,J.Uo,J._C,J.qD,J.p0],pipes:[Ut,u.JJ,Dt,_o],styles:[".status.recording[_ngcontent-%COMP%]{color:red}.status.injecting[_ngcontent-%COMP%], .status.remuxing[_ngcontent-%COMP%]{color:#00f}.status.completed[_ngcontent-%COMP%]{color:green}.status.missing[_ngcontent-%COMP%]{color:gray}.status.broken[_ngcontent-%COMP%]{color:orange}"],changeDetection:0}),n})();function Co(n,o){if(1&n&&t._UZ(0,"app-task-user-info-detail",6),2&n){const e=t.oxw(2);t.Q6J("loading",e.loading)("userInfo",e.taskData.user_info)}}function To(n,o){if(1&n&&t._UZ(0,"app-task-room-info-detail",7),2&n){const e=t.oxw(2);t.Q6J("loading",e.loading)("roomInfo",e.taskData.room_info)}}function xo(n,o){if(1&n&&t._UZ(0,"app-task-recording-detail",8),2&n){const e=t.oxw(2);t.Q6J("loading",e.loading)("taskStatus",e.taskData.task_status)}}function ko(n,o){if(1&n&&t._UZ(0,"app-task-network-detail",8),2&n){const e=t.oxw(2);t.Q6J("loading",e.loading)("taskStatus",e.taskData.task_status)}}function vo(n,o){if(1&n&&t._UZ(0,"app-task-postprocessing-detail",8),2&n){const e=t.oxw(2);t.Q6J("loading",e.loading)("taskStatus",e.taskData.task_status)}}function yo(n,o){if(1&n&&(t.YNc(0,Co,1,2,"app-task-user-info-detail",2),t.YNc(1,To,1,2,"app-task-room-info-detail",3),t.YNc(2,xo,1,2,"app-task-recording-detail",4),t.YNc(3,ko,1,2,"app-task-network-detail",4),t.YNc(4,vo,1,2,"app-task-postprocessing-detail",4),t._UZ(5,"app-task-file-detail",5)),2&n){const e=t.oxw();t.Q6J("ngIf",e.taskData),t.xp6(1),t.Q6J("ngIf",e.taskData),t.xp6(1),t.Q6J("ngIf",e.taskData),t.xp6(1),t.Q6J("ngIf",e.taskData),t.xp6(1),t.Q6J("ngIf",null==e.taskData||null==e.taskData.task_status?null:e.taskData.task_status.postprocessing_path),t.xp6(1),t.Q6J("loading",e.loading)("videoFileDetails",e.videoFileDetails)("danmakuFileDetails",e.danmakuFileDetails)}}const bo=function(){return{"max-width":"unset"}},So=function(){return{"row-gap":"1em"}};let Ao=(()=>{class n{constructor(e,i,s,a,c){this.route=e,this.router=i,this.changeDetector=s,this.notification=a,this.taskService=c,this.videoFileDetails=[],this.danmakuFileDetails=[],this.loading=!0}ngOnInit(){this.route.paramMap.subscribe(e=>{this.roomId=parseInt(e.get("id")),this.syncData()})}ngOnDestroy(){this.desyncData()}syncData(){this.dataSubscription=(0,A.of)((0,A.of)(0),vt(1e3)).pipe((0,Bt.u)(),(0,nt.w)(()=>It(this.taskService.getTaskData(this.roomId),this.taskService.getVideoFileDetails(this.roomId),this.taskService.getDanmakuFileDetails(this.roomId))),(0,it.K)(e=>{throw this.notification.error("\u83b7\u53d6\u4efb\u52a1\u6570\u636e\u51fa\u9519",e.message),e}),(0,bt.X)(10,3e3)).subscribe(([e,i,s])=>{this.loading=!1,this.taskData=e,this.videoFileDetails=i,this.danmakuFileDetails=s,this.changeDetector.markForCheck()},e=>{this.notification.error("\u83b7\u53d6\u4efb\u52a1\u6570\u636e\u51fa\u9519","\u7f51\u7edc\u8fde\u63a5\u5f02\u5e38, \u8bf7\u5f85\u7f51\u7edc\u6b63\u5e38\u540e\u5237\u65b0\u3002",{nzDuration:0})})}desyncData(){var e;null===(e=this.dataSubscription)||void 0===e||e.unsubscribe()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(ct.gz),t.Y36(ct.F0),t.Y36(t.sBO),t.Y36(Jt.zb),t.Y36(St))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-detail"]],decls:2,vars:5,consts:[["pageTitle","\u4efb\u52a1\u8be6\u60c5",3,"loading","pageStyles","contentStyles"],["appSubPageContent",""],[3,"loading","userInfo",4,"ngIf"],[3,"loading","roomInfo",4,"ngIf"],[3,"loading","taskStatus",4,"ngIf"],[3,"loading","videoFileDetails","danmakuFileDetails"],[3,"loading","userInfo"],[3,"loading","roomInfo"],[3,"loading","taskStatus"]],template:function(e,i){1&e&&(t.TgZ(0,"app-sub-page",0),t.YNc(1,yo,6,8,"ng-template",1),t.qZA()),2&e&&t.Q6J("loading",i.loading)("pageStyles",t.DdM(3,bo))("contentStyles",t.DdM(4,So))},directives:[Qi.q,Ui.Y,u.O5,qi,Hi,oo,po,go,zo],styles:[""],changeDetection:0}),n})();var fe=l(2323),Do=l(13),Mo=l(5778),H=l(4850);const pt=["(max-width: 534.98px)","(min-width: 535px) and (max-width: 1199.98px)","(min-width: 1200px)"];var qt=l(9727);let Rt=(()=>{class n{constructor(e,i){this.message=e,this.taskService=i}getAllTaskRoomIds(){return this.taskService.getAllTaskData().pipe((0,H.U)(e=>e.map(i=>i.room_info.room_id)))}updateTaskInfo(e){return this.taskService.updateTaskInfo(e).pipe((0,k.b)(()=>{this.message.success(`[${e}] \u6210\u529f\u5237\u65b0\u4efb\u52a1\u7684\u6570\u636e`)},i=>{this.message.error(`[${e}] \u5237\u65b0\u4efb\u52a1\u7684\u6570\u636e\u51fa\u9519: ${i.message}`)}))}updateAllTaskInfos(){return this.taskService.updateAllTaskInfos().pipe((0,k.b)(()=>{this.message.success("\u6210\u529f\u5237\u65b0\u5168\u90e8\u4efb\u52a1\u7684\u6570\u636e")},e=>{this.message.error(`\u5237\u65b0\u5168\u90e8\u4efb\u52a1\u7684\u6570\u636e\u51fa\u9519: ${e.message}`)}))}addTask(e){return this.taskService.addTask(e).pipe((0,H.U)(i=>({type:"success",message:"\u6210\u529f\u6dfb\u52a0\u4efb\u52a1"})),(0,it.K)(i=>{let s;return s=409==i.status?{type:"error",message:"\u4efb\u52a1\u5df2\u5b58\u5728\uff0c\u4e0d\u80fd\u91cd\u590d\u6dfb\u52a0\u3002"}:403==i.status?{type:"warning",message:"\u4efb\u52a1\u6570\u91cf\u8d85\u8fc7\u9650\u5236\uff0c\u4e0d\u80fd\u6dfb\u52a0\u4efb\u52a1\u3002"}:404==i.status?{type:"error",message:"\u76f4\u64ad\u95f4\u4e0d\u5b58\u5728"}:{type:"error",message:`\u6dfb\u52a0\u4efb\u52a1\u51fa\u9519: ${i.message}`},(0,A.of)(s)}),(0,H.U)(i=>(i.message=`${e}: ${i.message}`,i)),(0,k.b)(i=>{this.message[i.type](i.message)}))}removeTask(e){return this.taskService.removeTask(e).pipe((0,k.b)(()=>{this.message.success(`[${e}] \u4efb\u52a1\u5df2\u5220\u9664`)},i=>{this.message.error(`[${e}] \u5220\u9664\u4efb\u52a1\u51fa\u9519: ${i.message}`)}))}removeAllTasks(){const e=this.message.loading("\u6b63\u5728\u5220\u9664\u5168\u90e8\u4efb\u52a1...",{nzDuration:0}).messageId;return this.taskService.removeAllTasks().pipe((0,k.b)(()=>{this.message.remove(e),this.message.success("\u6210\u529f\u5220\u9664\u5168\u90e8\u4efb\u52a1")},i=>{this.message.remove(e),this.message.error(`\u5220\u9664\u5168\u90e8\u4efb\u52a1\u51fa\u9519: ${i.message}`)}))}startTask(e){const i=this.message.loading(`[${e}] \u6b63\u5728\u8fd0\u884c\u4efb\u52a1...`,{nzDuration:0}).messageId;return this.taskService.startTask(e).pipe((0,k.b)(()=>{this.message.remove(i),this.message.success(`[${e}] \u6210\u529f\u8fd0\u884c\u4efb\u52a1`)},s=>{this.message.remove(i),this.message.error(`[${e}] \u8fd0\u884c\u4efb\u52a1\u51fa\u9519: ${s.message}`)}))}startAllTasks(){const e=this.message.loading("\u6b63\u5728\u8fd0\u884c\u5168\u90e8\u4efb\u52a1...",{nzDuration:0}).messageId;return this.taskService.startAllTasks().pipe((0,k.b)(()=>{this.message.remove(e),this.message.success("\u6210\u529f\u8fd0\u884c\u5168\u90e8\u4efb\u52a1")},i=>{this.message.remove(e),this.message.error(`\u8fd0\u884c\u5168\u90e8\u4efb\u52a1\u51fa\u9519: ${i.message}`)}))}stopTask(e,i=!1){const s=this.message.loading(`[${e}] \u6b63\u5728\u505c\u6b62\u4efb\u52a1...`,{nzDuration:0}).messageId;return this.taskService.stopTask(e,i).pipe((0,k.b)(()=>{this.message.remove(s),this.message.success(`[${e}] \u6210\u529f\u505c\u6b62\u4efb\u52a1`)},a=>{this.message.remove(s),this.message.error(`[${e}] \u505c\u6b62\u4efb\u52a1\u51fa\u9519: ${a.message}`)}))}stopAllTasks(e=!1){const i=this.message.loading("\u6b63\u5728\u505c\u6b62\u5168\u90e8\u4efb\u52a1...",{nzDuration:0}).messageId;return this.taskService.stopAllTasks(e).pipe((0,k.b)(()=>{this.message.remove(i),this.message.success("\u6210\u529f\u505c\u6b62\u5168\u90e8\u4efb\u52a1")},s=>{this.message.remove(i),this.message.error(`\u505c\u6b62\u5168\u90e8\u4efb\u52a1\u51fa\u9519: ${s.message}`)}))}enableRecorder(e){const i=this.message.loading(`[${e}] \u6b63\u5728\u5f00\u542f\u5f55\u5236...`,{nzDuration:0}).messageId;return this.taskService.enableTaskRecorder(e).pipe((0,k.b)(()=>{this.message.remove(i),this.message.success(`[${e}] \u6210\u529f\u5f00\u542f\u5f55\u5236`)},s=>{this.message.remove(i),this.message.error(`[${e}] \u5f00\u542f\u5f55\u5236\u51fa\u9519: ${s.message}`)}))}enableAllRecorders(){const e=this.message.loading("\u6b63\u5728\u5f00\u542f\u5168\u90e8\u4efb\u52a1\u7684\u5f55\u5236...",{nzDuration:0}).messageId;return this.taskService.enableAllRecorders().pipe((0,k.b)(()=>{this.message.remove(e),this.message.success("\u6210\u529f\u5f00\u542f\u5168\u90e8\u4efb\u52a1\u7684\u5f55\u5236")},i=>{this.message.remove(e),this.message.error(`\u5f00\u542f\u5168\u90e8\u4efb\u52a1\u7684\u5f55\u5236\u51fa\u9519: ${i.message}`)}))}disableRecorder(e,i=!1){const s=this.message.loading(`[${e}] \u6b63\u5728\u5173\u95ed\u5f55\u5236...`,{nzDuration:0}).messageId;return this.taskService.disableTaskRecorder(e,i).pipe((0,k.b)(()=>{this.message.remove(s),this.message.success(`[${e}] \u6210\u529f\u5173\u95ed\u5f55\u5236`)},a=>{this.message.remove(s),this.message.error(`[${e}] \u5173\u95ed\u5f55\u5236\u51fa\u9519: ${a.message}`)}))}disableAllRecorders(e=!1){const i=this.message.loading("\u6b63\u5728\u5173\u95ed\u5168\u90e8\u4efb\u52a1\u7684\u5f55\u5236...",{nzDuration:0}).messageId;return this.taskService.disableAllRecorders(e).pipe((0,k.b)(()=>{this.message.remove(i),this.message.success("\u6210\u529f\u5173\u95ed\u5168\u90e8\u4efb\u52a1\u7684\u5f55\u5236")},s=>{this.message.remove(i),this.message.error(`\u5173\u95ed\u5168\u90e8\u4efb\u52a1\u7684\u5f55\u5236\u51fa\u9519: ${s.message}`)}))}cutStream(e){return this.taskService.cutStream(e).pipe((0,k.b)(()=>{this.message.success(`[${e}] \u6587\u4ef6\u5207\u5272\u5df2\u89e6\u53d1`)},i=>{403==i.status?this.message.warning(`[${e}] \u65f6\u957f\u592a\u77ed\u4e0d\u80fd\u5207\u5272\uff0c\u8bf7\u7a0d\u540e\u518d\u8bd5\u3002`):this.message.error(`[${e}] \u5207\u5272\u6587\u4ef6\u51fa\u9519: ${i.message}`)}))}}return n.\u0275fac=function(e){return new(e||n)(t.LFG(qt.dD),t.LFG(St))},n.\u0275prov=t.Yz7({token:n,factory:n.\u0275fac,providedIn:"root"}),n})();var Lt=l(2683),gt=l(4219);function Zo(n,o){if(1&n&&(t.ynx(0),t.GkF(1,8),t._UZ(2,"nz-divider",13),t.GkF(3,8),t._UZ(4,"nz-divider",13),t.GkF(5,8),t._UZ(6,"nz-divider",13),t.GkF(7,8),t.BQk()),2&n){t.oxw();const e=t.MAs(5),i=t.MAs(9),s=t.MAs(11),a=t.MAs(13);t.xp6(1),t.Q6J("ngTemplateOutlet",e),t.xp6(2),t.Q6J("ngTemplateOutlet",i),t.xp6(2),t.Q6J("ngTemplateOutlet",s),t.xp6(2),t.Q6J("ngTemplateOutlet",a)}}function Oo(n,o){if(1&n&&(t.ynx(0),t.GkF(1,8),t._UZ(2,"nz-divider",13),t.GkF(3,8),t._UZ(4,"nz-divider",13),t.GkF(5,8),t._UZ(6,"nz-divider",13),t.GkF(7,8),t.BQk()),2&n){t.oxw();const e=t.MAs(7),i=t.MAs(9),s=t.MAs(11),a=t.MAs(13);t.xp6(1),t.Q6J("ngTemplateOutlet",e),t.xp6(2),t.Q6J("ngTemplateOutlet",i),t.xp6(2),t.Q6J("ngTemplateOutlet",s),t.xp6(2),t.Q6J("ngTemplateOutlet",a)}}function wo(n,o){if(1&n&&(t.ynx(0),t.GkF(1,8),t.GkF(2,8),t.BQk()),2&n){t.oxw();const e=t.MAs(9),i=t.MAs(20);t.xp6(1),t.Q6J("ngTemplateOutlet",e),t.xp6(1),t.Q6J("ngTemplateOutlet",i)}}function Fo(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"label",16),t._uU(2),t.qZA(),t.BQk()),2&n){const e=o.$implicit;t.xp6(1),t.Q6J("nzValue",e.value),t.xp6(1),t.Oqu(e.label)}}function No(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"nz-radio-group",14),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().selection=s})("ngModelChange",function(s){return t.CHM(e),t.oxw().selectionChange.emit(s)}),t.YNc(1,Fo,3,2,"ng-container",15),t.qZA()}if(2&n){const e=t.oxw();t.Q6J("ngModel",e.selection),t.xp6(1),t.Q6J("ngForOf",e.selections)}}function Po(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"nz-select",17),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().selection=s})("ngModelChange",function(s){return t.CHM(e),t.oxw().selectionChange.emit(s)}),t.qZA()}if(2&n){const e=t.oxw();t.Q6J("nzOptions",e.selections)("ngModel",e.selection)}}function Eo(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"i",23),t.NdJ("click",function(){t.CHM(e),t.oxw(2);const s=t.MAs(2),a=t.oxw();return s.value="",a.onFilterInput("")}),t.qZA()}}function Io(n,o){if(1&n&&t.YNc(0,Eo,1,0,"i",22),2&n){t.oxw();const e=t.MAs(2);t.Q6J("ngIf",e.value)}}function Bo(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"nz-input-group",18),t.TgZ(1,"input",19,20),t.NdJ("input",function(){t.CHM(e);const s=t.MAs(2);return t.oxw().onFilterInput(s.value)}),t.qZA(),t.qZA(),t.YNc(3,Io,1,1,"ng-template",null,21,t.W1O)}if(2&n){const e=t.MAs(4);t.Q6J("nzSuffix",e)}}function Jo(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"button",24),t.NdJ("click",function(){return t.CHM(e),t.oxw().toggleReverse()}),t.TgZ(1,"span"),t._uU(2),t.qZA(),t._UZ(3,"i",25),t.qZA()}if(2&n){const e=t.oxw();t.xp6(2),t.Oqu(e.reverse?"\u5012\u5e8f":"\u6b63\u5e8f"),t.xp6(1),t.Q6J("nzType",e.reverse?"swap-left":"swap-right")("nzRotate",90)}}function Qo(n,o){if(1&n&&(t.TgZ(0,"button",26),t._UZ(1,"i",27),t.qZA()),2&n){t.oxw();const e=t.MAs(15);t.Q6J("nzDropdownMenu",e)}}function Uo(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"ul",28),t.TgZ(1,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().startAllTasks()}),t._uU(2,"\u5168\u90e8\u8fd0\u884c"),t.qZA(),t.TgZ(3,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().stopAllTasks()}),t._uU(4,"\u5168\u90e8\u505c\u6b62"),t.qZA(),t.TgZ(5,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().stopAllTasks(!0)}),t._uU(6,"\u5168\u90e8\u5f3a\u5236\u505c\u6b62"),t.qZA(),t._UZ(7,"li",30),t.TgZ(8,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().disableAllRecorders(!1)}),t._uU(9,"\u5168\u90e8\u5173\u95ed\u5f55\u5236"),t.qZA(),t.TgZ(10,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().disableAllRecorders(!0)}),t._uU(11,"\u5168\u90e8\u5f3a\u5236\u5173\u95ed\u5f55\u5236"),t.qZA(),t._UZ(12,"li",30),t.TgZ(13,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().removeAllTasks()}),t._uU(14,"\u5168\u90e8\u5220\u9664"),t.qZA(),t.TgZ(15,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().updateAllTaskInfos()}),t._uU(16,"\u5168\u90e8\u5237\u65b0\u6570\u636e"),t.qZA(),t.TgZ(17,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().copyAllTaskRoomIds()}),t._uU(18,"\u590d\u5236\u5168\u90e8\u623f\u95f4\u53f7"),t.qZA(),t.qZA()}}function qo(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"button",31),t.NdJ("click",function(){return t.CHM(e),t.oxw().drawerVisible=!0}),t._UZ(1,"i",27),t.qZA()}}function Ro(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"div",35),t._UZ(2,"nz-divider",36),t.GkF(3,8),t._UZ(4,"nz-divider",37),t.TgZ(5,"div",38),t.GkF(6,8),t.qZA(),t.qZA(),t.BQk()),2&n){t.oxw(2);const e=t.MAs(5),i=t.MAs(11);t.xp6(3),t.Q6J("ngTemplateOutlet",e),t.xp6(3),t.Q6J("ngTemplateOutlet",i)}}function Lo(n,o){if(1&n){const e=t.EpF();t.ynx(0),t.TgZ(1,"div",39),t.NdJ("click",function(){return t.CHM(e),t.oxw(2).menuDrawerVisible=!1}),t.GkF(2,8),t.qZA(),t.BQk()}if(2&n){t.oxw(2);const e=t.MAs(18);t.xp6(2),t.Q6J("ngTemplateOutlet",e)}}const Yo=function(){return{padding:"0"}};function Go(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"nz-drawer",32),t.NdJ("nzVisibleChange",function(s){return t.CHM(e),t.oxw().drawerVisible=s})("nzOnClose",function(){return t.CHM(e),t.oxw().drawerVisible=!1}),t.YNc(1,Ro,7,2,"ng-container",33),t.TgZ(2,"nz-drawer",34),t.NdJ("nzVisibleChange",function(s){return t.CHM(e),t.oxw().menuDrawerVisible=s})("nzOnClose",function(){return t.CHM(e),t.oxw().menuDrawerVisible=!1}),t.YNc(3,Lo,3,1,"ng-container",33),t.qZA(),t.qZA()}if(2&n){const e=t.oxw(),i=t.MAs(23);t.Q6J("nzTitle",i)("nzClosable",!1)("nzVisible",e.drawerVisible),t.xp6(2),t.Q6J("nzClosable",!1)("nzBodyStyle",t.DdM(6,Yo))("nzVisible",e.menuDrawerVisible)}}function $o(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"div",40),t.TgZ(1,"button",31),t.NdJ("click",function(){return t.CHM(e),t.oxw().menuDrawerVisible=!0}),t._UZ(2,"i",27),t.qZA(),t.qZA()}}let Vo=(()=>{class n{constructor(e,i,s,a,c,p){this.message=s,this.modal=a,this.clipboard=c,this.taskManager=p,this.selectionChange=new t.vpe,this.reverseChange=new t.vpe,this.filterChange=new t.vpe,this.destroyed=new v.xQ,this.useDrawer=!1,this.useSelector=!1,this.useRadioGroup=!0,this.drawerVisible=!1,this.menuDrawerVisible=!1,this.filterTerms=new v.xQ,this.selections=[{label:"\u5168\u90e8",value:Z.ALL},{label:"\u5f55\u5236\u4e2d",value:Z.RECORDING},{label:"\u5f55\u5236\u5f00",value:Z.RECORDER_ENABLED},{label:"\u5f55\u5236\u5173",value:Z.RECORDER_DISABLED},{label:"\u8fd0\u884c",value:Z.MONITOR_ENABLED},{label:"\u505c\u6b62",value:Z.MONITOR_DISABLED},{label:"\u76f4\u64ad",value:Z.LIVING},{label:"\u8f6e\u64ad",value:Z.ROUNDING},{label:"\u95f2\u7f6e",value:Z.PREPARING}],i.observe(pt).pipe((0,y.R)(this.destroyed)).subscribe(d=>{this.useDrawer=d.breakpoints[pt[0]],this.useSelector=d.breakpoints[pt[1]],this.useRadioGroup=d.breakpoints[pt[2]],e.markForCheck()})}ngOnInit(){this.filterTerms.pipe((0,Do.b)(300),(0,Mo.x)()).subscribe(e=>{this.filterChange.emit(e)})}ngOnDestroy(){this.destroyed.next(),this.destroyed.complete()}onFilterInput(e){this.filterTerms.next(e)}toggleReverse(){this.reverse=!this.reverse,this.reverseChange.emit(this.reverse)}removeAllTasks(){this.modal.confirm({nzTitle:"\u786e\u5b9a\u8981\u5220\u9664\u5168\u90e8\u4efb\u52a1\uff1f",nzContent:"\u6b63\u5728\u5f55\u5236\u7684\u4efb\u52a1\u5c06\u88ab\u5f3a\u5236\u505c\u6b62\uff01\u4efb\u52a1\u5220\u9664\u540e\u5c06\u4e0d\u53ef\u6062\u590d\uff01",nzOnOk:()=>new Promise((e,i)=>{this.taskManager.removeAllTasks().subscribe(e,i)})})}startAllTasks(){this.taskManager.startAllTasks().subscribe()}stopAllTasks(e=!1){e?this.modal.confirm({nzTitle:"\u786e\u5b9a\u8981\u5f3a\u5236\u505c\u6b62\u5168\u90e8\u4efb\u52a1\uff1f",nzContent:"\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\u4f1a\u88ab\u5f3a\u884c\u4e2d\u65ad\uff01\u786e\u5b9a\u8981\u653e\u5f03\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\uff1f",nzOnOk:()=>new Promise((i,s)=>{this.taskManager.stopAllTasks(e).subscribe(i,s)})}):this.taskManager.stopAllTasks().subscribe()}disableAllRecorders(e=!1){e?this.modal.confirm({nzTitle:"\u786e\u5b9a\u8981\u5f3a\u5236\u5173\u95ed\u5168\u90e8\u4efb\u52a1\u7684\u5f55\u5236\uff1f",nzContent:"\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\u4f1a\u88ab\u5f3a\u884c\u4e2d\u65ad\uff01\u786e\u5b9a\u8981\u653e\u5f03\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\uff1f",nzOnOk:()=>new Promise((i,s)=>{this.taskManager.disableAllRecorders(e).subscribe(i,s)})}):this.taskManager.disableAllRecorders().subscribe()}updateAllTaskInfos(){this.taskManager.updateAllTaskInfos().subscribe()}copyAllTaskRoomIds(){this.taskManager.getAllTaskRoomIds().pipe((0,H.U)(e=>e.join(" ")),(0,k.b)(e=>{if(!this.clipboard.copy(e))throw Error("Failed to copy text to the clipboard")})).subscribe(()=>{this.message.success("\u5168\u90e8\u623f\u95f4\u53f7\u5df2\u590d\u5236\u5230\u526a\u5207\u677f")},e=>{this.message.error("\u590d\u5236\u5168\u90e8\u623f\u95f4\u53f7\u5230\u526a\u5207\u677f\u51fa\u9519",e)})}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO),t.Y36(U.Yg),t.Y36(qt.dD),t.Y36(V.Sf),t.Y36(q),t.Y36(Rt))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-toolbar"]],inputs:{selection:"selection",reverse:"reverse"},outputs:{selectionChange:"selectionChange",reverseChange:"reverseChange",filterChange:"filterChange"},decls:24,vars:7,consts:[[1,"controls-wrapper"],[4,"ngIf"],["radioGroup",""],["selector",""],["filter",""],["reorderButton",""],["menuButton",""],["dropdownMenu","nzDropdownMenu"],[3,"ngTemplateOutlet"],["menu",""],["drawerButton",""],["nzPlacement","bottom","nzHeight","auto",3,"nzTitle","nzClosable","nzVisible","nzVisibleChange","nzOnClose",4,"ngIf"],["drawerHeader",""],["nzType","vertical"],["nzButtonStyle","solid",1,"radio-group",3,"ngModel","ngModelChange"],[4,"ngFor","ngForOf"],["nz-radio-button","",3,"nzValue"],[1,"selector",3,"nzOptions","ngModel","ngModelChange"],[1,"filter",3,"nzSuffix"],["nz-input","","type","text","maxlength","18","placeholder","\u7528\u6807\u9898\u3001\u5206\u533a\u3001\u4e3b\u64ad\u540d\u3001\u623f\u95f4\u53f7\u7b5b\u9009",3,"input"],["filterInput",""],["inputClearTpl",""],["nz-icon","","class","filter-clear","nzTheme","fill","nzType","close-circle",3,"click",4,"ngIf"],["nz-icon","","nzTheme","fill","nzType","close-circle",1,"filter-clear",3,"click"],["nz-button","","nzType","text","nzSize","default",1,"reverse-button",3,"click"],["nz-icon","",3,"nzType","nzRotate"],["nz-button","","nzType","text","nzSize","default","nz-dropdown","","nzPlacement","bottomRight",1,"more-actions-button",3,"nzDropdownMenu"],["nz-icon","","nzType","more"],["nz-menu","",1,"menu"],["nz-menu-item","",3,"click"],["nz-menu-divider",""],["nz-button","","nzType","text","nzSize","default",1,"more-actions-button",3,"click"],["nzPlacement","bottom","nzHeight","auto",3,"nzTitle","nzClosable","nzVisible","nzVisibleChange","nzOnClose"],[4,"nzDrawerContent"],["nzPlacement","bottom","nzHeight","auto",3,"nzClosable","nzBodyStyle","nzVisible","nzVisibleChange","nzOnClose"],[1,"drawer-content"],["nzText","\u7b5b\u9009"],["nzText","\u6392\u5e8f"],[1,"reorder-button-wrapper"],[1,"drawer-content",3,"click"],[1,"drawer-header"]],template:function(e,i){if(1&e&&(t.TgZ(0,"div",0),t.YNc(1,Zo,8,4,"ng-container",1),t.YNc(2,Oo,8,4,"ng-container",1),t.YNc(3,wo,3,2,"ng-container",1),t.qZA(),t.YNc(4,No,2,2,"ng-template",null,2,t.W1O),t.YNc(6,Po,1,2,"ng-template",null,3,t.W1O),t.YNc(8,Bo,5,1,"ng-template",null,4,t.W1O),t.YNc(10,Jo,4,3,"ng-template",null,5,t.W1O),t.YNc(12,Qo,2,1,"ng-template",null,6,t.W1O),t.TgZ(14,"nz-dropdown-menu",null,7),t.GkF(16,8),t.YNc(17,Uo,19,0,"ng-template",null,9,t.W1O),t.qZA(),t.YNc(19,qo,2,0,"ng-template",null,10,t.W1O),t.YNc(21,Go,4,7,"nz-drawer",11),t.YNc(22,$o,3,0,"ng-template",null,12,t.W1O)),2&e){const s=t.MAs(18);t.ekj("use-drawer",i.useDrawer),t.xp6(1),t.Q6J("ngIf",i.useRadioGroup),t.xp6(1),t.Q6J("ngIf",i.useSelector),t.xp6(1),t.Q6J("ngIf",i.useDrawer),t.xp6(13),t.Q6J("ngTemplateOutlet",s),t.xp6(5),t.Q6J("ngIf",i.useDrawer)}},directives:[u.O5,u.tP,Xt.g,Tt.Dg,m.JJ,m.On,u.sg,Tt.Of,Tt.Bq,wt.Vq,Lt.w,et.gB,et.ke,et.Zp,M.Ls,Ct.ix,tt.wA,tt.cm,tt.RR,gt.wO,gt.r9,gt.YV,lt.Vz,lt.SQ],styles:[".drawer-content[_ngcontent-%COMP%] .menu[_ngcontent-%COMP%]{box-shadow:none;padding:.5em 0}.drawer-content[_ngcontent-%COMP%] .menu[_ngcontent-%COMP%] *[nz-menu-item][_ngcontent-%COMP%]{margin:0;padding:.5em 2em}.controls-wrapper[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap;align-items:center;gap:.2em;width:100%;padding:.2em;background:#f9f9f9;border-left:none;border-right:none}.controls-wrapper[_ngcontent-%COMP%] nz-divider[_ngcontent-%COMP%]{height:1.8em;top:0}.controls-wrapper[_ngcontent-%COMP%]:not(.use-drawer) .filter[_ngcontent-%COMP%]{max-width:18em}.controls-wrapper.use-drawer[_ngcontent-%COMP%] .filter[_ngcontent-%COMP%]{max-width:unset;width:unset;flex:auto}.controls-wrapper[_ngcontent-%COMP%] .selector[_ngcontent-%COMP%]{min-width:6em}.reverse-button[_ngcontent-%COMP%]{padding:0 .5em}.reverse-button[_ngcontent-%COMP%] i[_ngcontent-%COMP%]{margin:0}.more-actions-button[_ngcontent-%COMP%]{margin-left:auto;border:none;background:inherit}.more-actions-button[_ngcontent-%COMP%] i[_ngcontent-%COMP%]{font-size:20px}.menu[_ngcontent-%COMP%] nz-divider[_ngcontent-%COMP%]{margin:0}.drawer-header[_ngcontent-%COMP%]{display:flex}.drawer-content[_ngcontent-%COMP%] .reorder-button-wrapper[_ngcontent-%COMP%], .drawer-content[_ngcontent-%COMP%] .radio-group[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(4,1fr);grid-gap:2vw;gap:2vw}.drawer-content[_ngcontent-%COMP%] nz-divider[_ngcontent-%COMP%]:first-of-type{margin-top:0}.drawer-content[_ngcontent-%COMP%] .radio-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%]{text-align:center;padding:0}"],changeDetection:0}),n})();var jo=l(5136);let Ho=(()=>{class n{constructor(e){this.storage=e}getSettings(e){var i;const s=this.storage.getData(this.getStorageKey(e));return s&&null!==(i=JSON.parse(s))&&void 0!==i?i:{}}updateSettings(e,i){i=Object.assign(this.getSettings(e),i);const s=JSON.stringify(i);this.storage.setData(this.getStorageKey(e),s)}getStorageKey(e){return`app-tasks-${e}`}}return n.\u0275fac=function(e){return new(e||n)(t.LFG(fe.V))},n.\u0275prov=t.Yz7({token:n,factory:n.\u0275fac,providedIn:"root"}),n})(),Wo=(()=>{class n{constructor(e){this.changeDetector=e,this.value=0,this.width=200,this.height=16,this.stroke="white",this.data=[],this.points=[];for(let i=0;i<=this.width;i+=2)this.data.push(0),this.points.push({x:i,y:this.height})}get polylinePoints(){return this.points.map(e=>`${e.x},${e.y}`).join(" ")}ngOnInit(){this.subscription=vt(1e3).subscribe(()=>{this.data.push(this.value||0),this.data.shift();let e=Math.max(...this.data);this.points=this.data.map((i,s)=>({x:Math.min(2*s,this.width),y:(1-i/(e||1))*this.height})),this.changeDetector.markForCheck()})}ngOnDestroy(){var e;null===(e=this.subscription)||void 0===e||e.unsubscribe()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-wave-graph"]],inputs:{value:"value",width:"width",height:"height",stroke:"stroke"},decls:2,vars:4,consts:[["fill","none"]],template:function(e,i){1&e&&(t.O4$(),t.TgZ(0,"svg"),t._UZ(1,"polyline",0),t.qZA()),2&e&&(t.uIk("width",i.width)("height",i.height),t.xp6(1),t.uIk("stroke",i.stroke)("points",i.polylinePoints))},styles:["[_nghost-%COMP%]{position:relative;top:2px}"],changeDetection:0}),n})();function Xo(n,o){1&n&&(t.ynx(0),t._uU(1,", bluray"),t.BQk())}function Ko(n,o){if(1&n&&(t.TgZ(0,"li",4),t.TgZ(1,"span",5),t._uU(2,"\u6d41\u7f16\u7801\u5668"),t.qZA(),t.TgZ(3,"span",6),t._uU(4),t.qZA(),t.qZA()),2&n){const e=t.oxw(2);t.xp6(4),t.Oqu(null==e.profile.streams[0]||null==e.profile.streams[0].tags?null:e.profile.streams[0].tags.encoder)}}const Yt=function(){return{bitrate:!0}};function ts(n,o){if(1&n&&(t.TgZ(0,"ul",3),t.TgZ(1,"li",4),t.TgZ(2,"span",5),t._uU(3,"\u89c6\u9891\u4fe1\u606f"),t.qZA(),t.TgZ(4,"span",6),t.TgZ(5,"span"),t._uU(6),t.qZA(),t.TgZ(7,"span"),t._uU(8),t.qZA(),t.TgZ(9,"span"),t._uU(10),t.qZA(),t.TgZ(11,"span"),t._uU(12),t.ALo(13,"datarate"),t.qZA(),t.qZA(),t.qZA(),t.TgZ(14,"li",4),t.TgZ(15,"span",5),t._uU(16,"\u97f3\u9891\u4fe1\u606f"),t.qZA(),t.TgZ(17,"span",6),t.TgZ(18,"span"),t._uU(19),t.qZA(),t.TgZ(20,"span"),t._uU(21),t.qZA(),t.TgZ(22,"span"),t._uU(23),t.qZA(),t.TgZ(24,"span"),t._uU(25),t.ALo(26,"datarate"),t.qZA(),t.qZA(),t.qZA(),t.TgZ(27,"li",4),t.TgZ(28,"span",5),t._uU(29,"\u683c\u5f0f\u753b\u8d28"),t.qZA(),t.TgZ(30,"span",6),t.TgZ(31,"span"),t._uU(32),t.qZA(),t.TgZ(33,"span"),t._uU(34),t.ALo(35,"quality"),t.YNc(36,Xo,2,0,"ng-container",7),t._uU(37,") "),t.qZA(),t.qZA(),t.qZA(),t.YNc(38,Ko,5,1,"li",8),t.TgZ(39,"li",4),t.TgZ(40,"span",5),t._uU(41,"\u6d41\u4e3b\u673a\u540d"),t.qZA(),t.TgZ(42,"span",6),t._uU(43),t.qZA(),t.qZA(),t.TgZ(44,"li",4),t.TgZ(45,"span",5),t._uU(46,"\u4e0b\u8f7d\u901f\u5ea6"),t.qZA(),t._UZ(47,"app-wave-graph",9),t.TgZ(48,"span",6),t._uU(49),t.ALo(50,"datarate"),t.qZA(),t.qZA(),t.TgZ(51,"li",4),t.TgZ(52,"span",5),t._uU(53,"\u5f55\u5236\u901f\u5ea6"),t.qZA(),t._UZ(54,"app-wave-graph",9),t.TgZ(55,"span",6),t._uU(56),t.ALo(57,"datarate"),t.qZA(),t.qZA(),t.qZA()),2&n){const e=t.oxw();let i;t.xp6(6),t.hij(" ",null==e.profile.streams[0]?null:e.profile.streams[0].codec_name," "),t.xp6(2),t.AsE(" ",null==e.profile.streams[0]?null:e.profile.streams[0].width,"x",null==e.profile.streams[0]?null:e.profile.streams[0].height," "),t.xp6(2),t.hij(" ",(null==e.profile.streams[0]?null:e.profile.streams[0].r_frame_rate).split("/")[0]," fps"),t.xp6(2),t.hij(" ",t.xi3(13,19,1e3*e.metadata.videodatarate,t.DdM(32,Yt))," "),t.xp6(7),t.hij(" ",null==e.profile.streams[1]?null:e.profile.streams[1].codec_name," "),t.xp6(2),t.hij(" ",null==e.profile.streams[1]?null:e.profile.streams[1].sample_rate," HZ"),t.xp6(2),t.hij(" ",null==e.profile.streams[1]?null:e.profile.streams[1].channel_layout," "),t.xp6(2),t.hij(" ",t.xi3(26,22,1e3*e.metadata.audiodatarate,t.DdM(33,Yt))," "),t.xp6(7),t.hij(" ",e.data.task_status.real_stream_format?e.data.task_status.real_stream_format:"N/A"," "),t.xp6(2),t.AsE(" ",e.data.task_status.real_quality_number?t.lcZ(35,25,e.data.task_status.real_quality_number):"N/A"," (",null!==(i=e.data.task_status.real_quality_number)&&void 0!==i?i:"N/A",""),t.xp6(2),t.Q6J("ngIf",e.isBlurayStreamQuality()),t.xp6(2),t.Q6J("ngIf",null==e.profile.streams[0]||null==e.profile.streams[0].tags?null:e.profile.streams[0].tags.encoder),t.xp6(5),t.hij(" ",e.data.task_status.stream_host," "),t.xp6(4),t.Q6J("value",e.data.task_status.dl_rate),t.xp6(2),t.hij(" ",t.xi3(50,27,8*e.data.task_status.dl_rate,t.DdM(34,Yt))," "),t.xp6(5),t.Q6J("value",e.data.task_status.rec_rate),t.xp6(2),t.hij(" ",t.lcZ(57,30,e.data.task_status.rec_rate)," ")}}let es=(()=>{class n{constructor(e,i,s){this.changeDetector=e,this.notification=i,this.taskService=s,this.metadata=null,this.close=new t.vpe,this.RunningStatus=O}ngOnInit(){this.syncData()}ngOnDestroy(){this.desyncData()}isBlurayStreamQuality(){return/_bluray/.test(this.data.task_status.stream_url)}closePanel(e){e.preventDefault(),e.stopPropagation(),this.close.emit()}syncData(){this.dataSubscription=(0,A.of)((0,A.of)(0),vt(1e3)).pipe((0,Bt.u)(),(0,nt.w)(()=>It(this.taskService.getStreamProfile(this.data.room_info.room_id),this.taskService.getMetadata(this.data.room_info.room_id))),(0,it.K)(e=>{throw this.notification.error("\u83b7\u53d6\u6570\u636e\u51fa\u9519",e.message),e}),(0,bt.X)(3,1e3)).subscribe(([e,i])=>{this.profile=e,this.metadata=i,this.changeDetector.markForCheck()},e=>{this.notification.error("\u83b7\u53d6\u6570\u636e\u51fa\u9519","\u7f51\u7edc\u8fde\u63a5\u5f02\u5e38, \u8bf7\u5f85\u7f51\u7edc\u6b63\u5e38\u540e\u5237\u65b0\u3002",{nzDuration:0})})}desyncData(){var e;null===(e=this.dataSubscription)||void 0===e||e.unsubscribe()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO),t.Y36(Jt.zb),t.Y36(St))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-info-panel"]],inputs:{data:"data",profile:"profile",metadata:"metadata"},outputs:{close:"close"},decls:4,vars:1,consts:[[1,"info-panel"],["title","\u5173\u95ed",1,"close-panel",3,"click"],["class","info-list",4,"ngIf"],[1,"info-list"],[1,"info-item"],[1,"label"],[1,"value"],[4,"ngIf"],["class","info-item",4,"ngIf"],[3,"value"]],template:function(e,i){1&e&&(t.TgZ(0,"div",0),t.TgZ(1,"button",1),t.NdJ("click",function(a){return i.closePanel(a)}),t._uU(2," [x] "),t.qZA(),t.YNc(3,ts,58,35,"ul",2),t.qZA()),2&e&&(t.xp6(3),t.Q6J("ngIf",i.data.task_status.running_status===i.RunningStatus.RECORDING&&i.profile&&i.profile.streams&&i.profile.format&&i.metadata))},directives:[u.O5,Wo],pipes:[At,Qt],styles:['@charset "UTF-8";.info-panel[_ngcontent-%COMP%]{color:#fff;text-shadow:1px 1px 2px black;margin:0;padding:0 .5rem;background:rgba(0,0,0,.32)}.info-panel[_ngcontent-%COMP%]{position:absolute;top:2.55rem;bottom:2rem;left:0rem;right:0rem;width:100%;font-size:1rem;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;overflow:auto}.info-panel[_ngcontent-%COMP%]::-webkit-scrollbar{background-color:transparent;width:4px}.info-panel[_ngcontent-%COMP%]::-webkit-scrollbar-track{background:transparent}.info-panel[_ngcontent-%COMP%]::-webkit-scrollbar-thumb{background:#eee;border-radius:2px}.info-panel[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover{background:#fff}.info-panel[_ngcontent-%COMP%] .close-panel[_ngcontent-%COMP%]{position:absolute;top:0rem;right:0rem;width:2rem;height:2rem;padding:0;color:#fff;background:transparent;border:none;font-size:1rem;display:flex;align-items:center;justify-content:center;cursor:pointer}.info-panel[_ngcontent-%COMP%] .info-list[_ngcontent-%COMP%]{margin:0;padding:0;list-style:none;width:100%;height:100%}.info-panel[_ngcontent-%COMP%] .info-list[_ngcontent-%COMP%] .info-item[_ngcontent-%COMP%] .label[_ngcontent-%COMP%]{display:inline-block;margin:0;width:5rem;text-align:right}.info-panel[_ngcontent-%COMP%] .info-list[_ngcontent-%COMP%] .info-item[_ngcontent-%COMP%] .label[_ngcontent-%COMP%]:after{content:"\\ff1a"}.info-panel[_ngcontent-%COMP%] .info-list[_ngcontent-%COMP%] .info-item[_ngcontent-%COMP%] .value[_ngcontent-%COMP%]{display:inline-block;margin:0;text-align:left}.info-panel[_ngcontent-%COMP%] .info-list[_ngcontent-%COMP%] .info-item[_ngcontent-%COMP%] .value[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:not(:first-child):before{content:", "}app-wave-graph[_ngcontent-%COMP%]{margin-right:1rem}'],changeDetection:0}),n})();const ze=function(){return{spacer:""}};function ns(n,o){if(1&n&&(t.TgZ(0,"div",2),t.TgZ(1,"p",3),t.TgZ(2,"span",4),t._UZ(3,"i"),t.qZA(),t.TgZ(4,"span",5),t._uU(5),t.ALo(6,"duration"),t.qZA(),t.TgZ(7,"span",6),t._uU(8),t.ALo(9,"datarate"),t.qZA(),t.TgZ(10,"span",7),t._uU(11),t.ALo(12,"filesize"),t.qZA(),t.TgZ(13,"span",8),t.ALo(14,"number"),t._uU(15),t.ALo(16,"number"),t.qZA(),t.TgZ(17,"span",9),t._uU(18),t.ALo(19,"quality"),t.qZA(),t.qZA(),t.qZA()),2&n){const e=t.oxw();t.xp6(5),t.hij(" ",t.lcZ(6,6,e.status.rec_elapsed)," "),t.xp6(3),t.hij(" ",t.xi3(9,8,e.status.rec_rate,t.DdM(22,ze))," "),t.xp6(3),t.hij(" ",t.xi3(12,11,e.status.rec_total,t.DdM(23,ze))," "),t.xp6(2),t.MGl("nzTooltipTitle","\u5f39\u5e55\u603b\u8ba1\uff1a",t.xi3(14,14,e.status.danmu_total,"1.0-0"),""),t.xp6(2),t.hij(" ",t.xi3(16,17,e.status.danmu_total,"1.0-0")," "),t.xp6(3),t.hij(" ",e.status.real_quality_number?t.lcZ(19,20,e.status.real_quality_number):""," ")}}function is(n,o){if(1&n&&(t.TgZ(0,"div",2),t.TgZ(1,"p",10),t.ALo(2,"filename"),t._uU(3),t.ALo(4,"filename"),t.qZA(),t._UZ(5,"nz-progress",11),t.ALo(6,"progress"),t.qZA()),2&n){const e=t.oxw();let i,s;t.xp6(1),t.MGl("nzTooltipTitle","\u6b63\u5728\u6dfb\u52a0\u5143\u6570\u636e\uff1a",t.lcZ(2,7,null!==(i=e.status.postprocessing_path)&&void 0!==i?i:""),""),t.xp6(2),t.hij(" ",t.lcZ(4,9,null!==(s=e.status.postprocessing_path)&&void 0!==s?s:"")," "),t.xp6(2),t.Q6J("nzType","line")("nzShowInfo",!1)("nzStrokeLinecap","square")("nzStrokeWidth",2)("nzPercent",null===e.status.postprocessing_progress?0:t.lcZ(6,11,e.status.postprocessing_progress))}}function os(n,o){if(1&n&&(t.TgZ(0,"div",2),t.TgZ(1,"p",12),t.ALo(2,"filename"),t._uU(3),t.ALo(4,"filename"),t.qZA(),t._UZ(5,"nz-progress",11),t.ALo(6,"progress"),t.qZA()),2&n){const e=t.oxw();let i,s;t.xp6(1),t.MGl("nzTooltipTitle","\u6b63\u5728\u8f6c\u5c01\u88c5\uff1a",t.lcZ(2,7,null!==(i=e.status.postprocessing_path)&&void 0!==i?i:""),""),t.xp6(2),t.hij(" ",t.lcZ(4,9,null!==(s=e.status.postprocessing_path)&&void 0!==s?s:"")," "),t.xp6(2),t.Q6J("nzType","line")("nzShowInfo",!1)("nzStrokeLinecap","square")("nzStrokeWidth",2)("nzPercent",null===e.status.postprocessing_progress?0:t.lcZ(6,11,e.status.postprocessing_progress))}}let ss=(()=>{class n{constructor(){this.RunningStatus=O}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-status-display"]],inputs:{status:"status"},decls:4,vars:4,consts:[[3,"ngSwitch"],["class","status-display",4,"ngSwitchCase"],[1,"status-display"],[1,"status-bar","recording"],["nz-tooltip","","nzTooltipTitle","\u6b63\u5728\u5f55\u5236","nzTooltipPlacement","top",1,"status-indicator"],["nz-tooltip","","nzTooltipTitle","\u5f55\u5236\u7528\u65f6","nzTooltipPlacement","top",1,"time-elapsed"],["nz-tooltip","","nzTooltipTitle","\u5f55\u5236\u901f\u5ea6","nzTooltipPlacement","top",1,"data-rate"],["nz-tooltip","","nzTooltipTitle","\u5f55\u5236\u603b\u8ba1","nzTooltipPlacement","top",1,"data-count"],["nz-tooltip","","nzTooltipPlacement","top",1,"danmu-count",3,"nzTooltipTitle"],["nz-tooltip","","nzTooltipTitle","\u5f55\u5236\u753b\u8d28","nzTooltipPlacement","leftTop",1,"quality"],["nz-tooltip","","nzTooltipPlacement","top",1,"status-bar","injecting",3,"nzTooltipTitle"],[3,"nzType","nzShowInfo","nzStrokeLinecap","nzStrokeWidth","nzPercent"],["nz-tooltip","","nzTooltipPlacement","top",1,"status-bar","remuxing",3,"nzTooltipTitle"]],template:function(e,i){1&e&&(t.ynx(0,0),t.YNc(1,ns,20,24,"div",1),t.YNc(2,is,7,13,"div",1),t.YNc(3,os,7,13,"div",1),t.BQk()),2&e&&(t.Q6J("ngSwitch",i.status.running_status),t.xp6(1),t.Q6J("ngSwitchCase",i.RunningStatus.RECORDING),t.xp6(1),t.Q6J("ngSwitchCase",i.RunningStatus.INJECTING),t.xp6(1),t.Q6J("ngSwitchCase",i.RunningStatus.REMUXING))},directives:[u.RF,u.n9,rt.SY,oe],pipes:[me,At,Dt,u.JJ,Qt,Ut,_e],styles:[".status-bar[_ngcontent-%COMP%]{color:#fff;text-shadow:1px 1px 2px black;margin:0;padding:0 .5rem;background:rgba(0,0,0,.32)}.status-display[_ngcontent-%COMP%]{position:absolute;bottom:0;left:0;width:100%}.status-bar[_ngcontent-%COMP%]{display:flex;gap:1rem;font-size:1rem;line-height:1.8}.status-bar.recording[_ngcontent-%COMP%] .status-indicator[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:center}.status-bar.recording[_ngcontent-%COMP%] .status-indicator[_ngcontent-%COMP%] i[_ngcontent-%COMP%]{width:1rem;height:1rem;border-radius:.5rem;color:red;background:red;animation:blinker 1s cubic-bezier(1,0,0,1) infinite}@keyframes blinker{0%{opacity:0}to{opacity:1}}.status-bar.injecting[_ngcontent-%COMP%], .status-bar.remuxing[_ngcontent-%COMP%], .status-bar[_ngcontent-%COMP%] .danmu-count[_ngcontent-%COMP%]{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.status-bar[_ngcontent-%COMP%] .quality[_ngcontent-%COMP%]{flex:none;margin-left:auto}nz-progress[_ngcontent-%COMP%]{display:flex}nz-progress[_ngcontent-%COMP%] .ant-progress-outer{display:flex}"],changeDetection:0}),n})();var F=l(3523),w=l(8737);function as(n,o){1&n&&(t.ynx(0),t._uU(1," \u8bf7\u8f93\u5165\u8def\u5f84\u6a21\u677f "),t.BQk())}function rs(n,o){1&n&&(t.ynx(0),t._uU(1," \u8def\u5f84\u6a21\u677f\u6709\u9519\u8bef "),t.BQk())}function ls(n,o){if(1&n&&(t.YNc(0,as,2,0,"ng-container",66),t.YNc(1,rs,2,0,"ng-container",66)),2&n){const e=o.$implicit;t.Q6J("ngIf",e.hasError("required")),t.xp6(1),t.Q6J("ngIf",e.hasError("pattern"))}}function cs(n,o){1&n&&(t.TgZ(0,"p"),t._uU(1," \u9009\u62e9\u8981\u5f55\u5236\u7684\u76f4\u64ad\u6d41\u683c\u5f0f "),t._UZ(2,"br"),t._uU(3," FLV: \u7f51\u7edc\u4e0d\u7a33\u5b9a\u5bb9\u6613\u4e2d\u65ad\u4e22\u5931\u6570\u636e\u6216\u5f55\u5236\u5230\u4e8c\u538b\u753b\u8d28 "),t._UZ(4,"br"),t._uU(5," HLS (fmp4): \u57fa\u672c\u4e0d\u53d7\u7f51\u7edc\u6ce2\u52a8\u5f71\u54cd\uff0c\u4f46\u53ea\u6709\u90e8\u5206\u76f4\u64ad\u95f4\u652f\u6301\u3002 "),t._UZ(6,"br"),t._uU(7," P.S. "),t._UZ(8,"br"),t._uU(9," \u5f55\u5236 HLS \u6d41\u9700\u8981 ffmpeg "),t._UZ(10,"br"),t._uU(11," \u5728\u8bbe\u5b9a\u65f6\u95f4\u5185\u6ca1\u6709 fmp4 \u6d41\u4f1a\u81ea\u52a8\u5207\u6362\u5f55\u5236 flv \u6d41 "),t._UZ(12,"br"),t._uU(13," WEB \u7aef\u76f4\u64ad\u64ad\u653e\u5668\u662f Hls7Player \u7684\u76f4\u64ad\u95f4\u652f\u6301\u5f55\u5236 fmp4 \u6d41, fMp4Player \u5219\u4e0d\u652f\u6301\u3002 "),t.qZA())}function us(n,o){1&n&&(t.TgZ(0,"p"),t._uU(1," \u5982\u679c\u8d85\u8fc7\u6240\u8bbe\u7f6e\u7684\u7b49\u5f85\u65f6\u95f4 fmp4 \u6d41\u8fd8\u6ca1\u6709\u5c31\u5207\u6362\u4e3a\u5f55\u5236 flv \u6d41 "),t._UZ(2,"br"),t._uU(3," fmp4 \u6d41\u5728\u521a\u63a8\u6d41\u662f\u6ca1\u6709\u7684\uff0c\u8981\u8fc7\u4e00\u4f1a\u624d\u6709\u3002 "),t._UZ(4,"br"),t._uU(5," fmp4 \u6d41\u51fa\u73b0\u7684\u65f6\u95f4\u548c\u76f4\u64ad\u5ef6\u8fdf\u6709\u5173\uff0c\u4e00\u822c\u90fd\u5728 10 \u79d2\u5185\uff0c\u4f46\u4e5f\u6709\u5ef6\u8fdf\u6bd4\u8f83\u5927\u8d85\u8fc7 1 \u5206\u949f\u7684\u3002 "),t._UZ(6,"br"),t._uU(7," \u63a8\u8350\u5168\u5c40\u8bbe\u7f6e\u4e3a 10 \u79d2\uff0c\u4e2a\u522b\u5ef6\u8fdf\u6bd4\u8f83\u5927\u7684\u76f4\u64ad\u95f4\u5355\u72ec\u8bbe\u7f6e\u3002 "),t.qZA())}function ps(n,o){1&n&&(t.TgZ(0,"p"),t._uU(1," \u9ed8\u8ba4: \u6bcf\u4e2a\u5206\u5272\u7684\u5f55\u64ad\u6587\u4ef6\u5bf9\u5e94\u4fdd\u5b58\u4e00\u4e2a\u5c01\u9762\u6587\u4ef6\uff0c\u4e0d\u7ba1\u5c01\u9762\u662f\u5426\u76f8\u540c\u3002"),t._UZ(2,"br"),t._uU(3," \u53bb\u91cd: \u76f8\u540c\u7684\u5c01\u9762\u53ea\u4fdd\u5b58\u4e00\u6b21"),t._UZ(4,"br"),t._uU(5," P.S. "),t._UZ(6,"br"),t._uU(7," \u5224\u65ad\u662f\u5426\u76f8\u540c\u662f\u4f9d\u636e\u5c01\u9762\u6570\u636e\u7684 sha1\uff0c\u53ea\u5728\u5355\u6b21\u5f55\u5236\u5185\u6709\u6548\u3002 "),t.qZA())}function gs(n,o){1&n&&(t.TgZ(0,"p"),t._uU(1," \u81ea\u52a8: \u6ca1\u51fa\u9519\u5c31\u5220\u9664\u6e90\u6587\u4ef6"),t._UZ(2,"br"),t._uU(3," \u8c28\u614e: \u6ca1\u51fa\u9519\u4e14\u6ca1\u8b66\u544a\u624d\u5220\u9664\u6e90\u6587\u4ef6"),t._UZ(4,"br"),t._uU(5," \u4ece\u4e0d: \u603b\u662f\u4fdd\u7559\u6e90\u6587\u4ef6"),t._UZ(6,"br"),t.qZA())}function ds(n,o){1&n&&(t.ynx(0),t._uU(1," \u8bf7\u8f93\u5165 User Agent "),t.BQk())}function ms(n,o){1&n&&t.YNc(0,ds,2,0,"ng-container",66),2&n&&t.Q6J("ngIf",o.$implicit.hasError("required"))}function _s(n,o){if(1&n){const e=t.EpF();t.ynx(0),t.TgZ(1,"form",2),t.TgZ(2,"div",3),t.TgZ(3,"h2"),t._uU(4,"\u6587\u4ef6"),t.qZA(),t.TgZ(5,"nz-form-item",4),t.TgZ(6,"nz-form-label",5),t._uU(7,"\u8def\u5f84\u6a21\u677f"),t.qZA(),t.TgZ(8,"nz-form-control",6),t.TgZ(9,"input",7),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.output.pathTemplate=s}),t.qZA(),t.YNc(10,ls,2,2,"ng-template",null,8,t.W1O),t.qZA(),t.TgZ(12,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.output.pathTemplate=s?a.globalSettings.output.pathTemplate:null}),t._uU(13,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(14,"nz-form-item",10),t.TgZ(15,"nz-form-label",11),t._uU(16,"\u5927\u5c0f\u9650\u5236"),t.qZA(),t.TgZ(17,"nz-form-control",12),t.TgZ(18,"nz-select",13),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.output.filesizeLimit=s}),t.qZA(),t.qZA(),t.TgZ(19,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.output.filesizeLimit=s?a.globalSettings.output.filesizeLimit:null}),t._uU(20,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(21,"nz-form-item",10),t.TgZ(22,"nz-form-label",11),t._uU(23,"\u65f6\u957f\u9650\u5236"),t.qZA(),t.TgZ(24,"nz-form-control",12),t.TgZ(25,"nz-select",14),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.output.durationLimit=s}),t.qZA(),t.qZA(),t.TgZ(26,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.output.durationLimit=s?a.globalSettings.output.durationLimit:null}),t._uU(27,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.qZA(),t.TgZ(28,"div",15),t.TgZ(29,"h2"),t._uU(30,"\u5f55\u5236"),t.qZA(),t.TgZ(31,"nz-form-item",10),t.TgZ(32,"nz-form-label",11),t._uU(33,"\u76f4\u64ad\u6d41\u683c\u5f0f"),t.qZA(),t.YNc(34,cs,14,0,"ng-template",null,16,t.W1O),t.TgZ(36,"nz-form-control",12),t.TgZ(37,"nz-select",17),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.streamFormat=s}),t.qZA(),t.qZA(),t.TgZ(38,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.streamFormat=s?a.globalSettings.recorder.streamFormat:null}),t._uU(39,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(40,"nz-form-item",10),t.TgZ(41,"nz-form-label",11),t._uU(42,"fmp4 \u6d41\u7b49\u5f85\u65f6\u95f4"),t.qZA(),t.YNc(43,us,8,0,"ng-template",null,18,t.W1O),t.TgZ(45,"nz-form-control",12),t.TgZ(46,"nz-select",19,20),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.fmp4StreamTimeout=s}),t.qZA(),t.qZA(),t.TgZ(48,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.fmp4StreamTimeout=s?a.globalSettings.recorder.fmp4StreamTimeout:null}),t._uU(49,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(50,"nz-form-item",10),t.TgZ(51,"nz-form-label",21),t._uU(52,"\u753b\u8d28"),t.qZA(),t.TgZ(53,"nz-form-control",12),t.TgZ(54,"nz-select",22),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.qualityNumber=s}),t.qZA(),t.qZA(),t.TgZ(55,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.qualityNumber=s?a.globalSettings.recorder.qualityNumber:null}),t._uU(56,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(57,"nz-form-item",10),t.TgZ(58,"nz-form-label",23),t._uU(59,"\u4fdd\u5b58\u5c01\u9762"),t.qZA(),t.TgZ(60,"nz-form-control",24),t.TgZ(61,"nz-switch",25),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.saveCover=s}),t.qZA(),t.qZA(),t.TgZ(62,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.saveCover=s?a.globalSettings.recorder.saveCover:null}),t._uU(63,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(64,"nz-form-item",10),t.TgZ(65,"nz-form-label",11),t._uU(66,"\u5c01\u9762\u4fdd\u5b58\u7b56\u7565"),t.qZA(),t.YNc(67,ps,8,0,"ng-template",null,26,t.W1O),t.TgZ(69,"nz-form-control",12),t.TgZ(70,"nz-select",27),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.coverSaveStrategy=s}),t.qZA(),t.qZA(),t.TgZ(71,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.coverSaveStrategy=s?a.globalSettings.recorder.coverSaveStrategy:null}),t._uU(72,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(73,"nz-form-item",10),t.TgZ(74,"nz-form-label",28),t._uU(75,"\u6570\u636e\u8bfb\u53d6\u8d85\u65f6"),t.qZA(),t.TgZ(76,"nz-form-control",29),t.TgZ(77,"nz-select",30,31),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.readTimeout=s}),t.qZA(),t.qZA(),t.TgZ(79,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.readTimeout=s?a.globalSettings.recorder.readTimeout:null}),t._uU(80,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(81,"nz-form-item",10),t.TgZ(82,"nz-form-label",32),t._uU(83,"\u65ad\u7f51\u7b49\u5f85\u65f6\u95f4"),t.qZA(),t.TgZ(84,"nz-form-control",12),t.TgZ(85,"nz-select",33),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.disconnectionTimeout=s}),t.qZA(),t.qZA(),t.TgZ(86,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.bufferSize=s?a.globalSettings.recorder.bufferSize:null}),t._uU(87,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(88,"nz-form-item",10),t.TgZ(89,"nz-form-label",34),t._uU(90,"\u786c\u76d8\u5199\u5165\u7f13\u51b2"),t.qZA(),t.TgZ(91,"nz-form-control",12),t.TgZ(92,"nz-select",35),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.bufferSize=s}),t.qZA(),t.qZA(),t.TgZ(93,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.bufferSize=s?a.globalSettings.recorder.bufferSize:null}),t._uU(94,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.qZA(),t.TgZ(95,"div",36),t.TgZ(96,"h2"),t._uU(97,"\u5f39\u5e55"),t.qZA(),t.TgZ(98,"nz-form-item",10),t.TgZ(99,"nz-form-label",37),t._uU(100,"\u8bb0\u5f55\u793c\u7269"),t.qZA(),t.TgZ(101,"nz-form-control",24),t.TgZ(102,"nz-switch",38),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.danmaku.recordGiftSend=s}),t.qZA(),t.qZA(),t.TgZ(103,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.danmaku.recordGiftSend=s?a.globalSettings.danmaku.recordGiftSend:null}),t._uU(104,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(105,"nz-form-item",10),t.TgZ(106,"nz-form-label",39),t._uU(107,"\u8bb0\u5f55\u514d\u8d39\u793c\u7269"),t.qZA(),t.TgZ(108,"nz-form-control",24),t.TgZ(109,"nz-switch",40),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.danmaku.recordFreeGifts=s}),t.qZA(),t.qZA(),t.TgZ(110,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.danmaku.recordFreeGifts=s?a.globalSettings.danmaku.recordFreeGifts:null}),t._uU(111,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(112,"nz-form-item",10),t.TgZ(113,"nz-form-label",41),t._uU(114,"\u8bb0\u5f55\u4e0a\u8230"),t.qZA(),t.TgZ(115,"nz-form-control",24),t.TgZ(116,"nz-switch",42),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.danmaku.recordGuardBuy=s}),t.qZA(),t.qZA(),t.TgZ(117,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.danmaku.recordGuardBuy=s?a.globalSettings.danmaku.recordGuardBuy:null}),t._uU(118,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(119,"nz-form-item",10),t.TgZ(120,"nz-form-label",43),t._uU(121,"\u8bb0\u5f55 Super Chat"),t.qZA(),t.TgZ(122,"nz-form-control",24),t.TgZ(123,"nz-switch",44),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.danmaku.recordSuperChat=s}),t.qZA(),t.qZA(),t.TgZ(124,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.danmaku.recordSuperChat=s?a.globalSettings.danmaku.recordSuperChat:null}),t._uU(125,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(126,"nz-form-item",10),t.TgZ(127,"nz-form-label",45),t._uU(128,"\u5f39\u5e55\u524d\u52a0\u7528\u6237\u540d"),t.qZA(),t.TgZ(129,"nz-form-control",24),t.TgZ(130,"nz-switch",46),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.danmaku.danmuUname=s}),t.qZA(),t.qZA(),t.TgZ(131,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.danmaku.danmuUname=s?a.globalSettings.danmaku.danmuUname:null}),t._uU(132,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(133,"nz-form-item",10),t.TgZ(134,"nz-form-label",47),t._uU(135,"\u4fdd\u5b58\u539f\u59cb\u5f39\u5e55"),t.qZA(),t.TgZ(136,"nz-form-control",24),t.TgZ(137,"nz-switch",48),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.danmaku.saveRawDanmaku=s}),t.qZA(),t.qZA(),t.TgZ(138,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.danmaku.saveRawDanmaku=s?a.globalSettings.danmaku.saveRawDanmaku:null}),t._uU(139,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.qZA(),t.TgZ(140,"div",49),t.TgZ(141,"h2"),t._uU(142,"\u6587\u4ef6\u5904\u7406"),t.qZA(),t.TgZ(143,"nz-form-item",10),t.TgZ(144,"nz-form-label",50),t._uU(145,"flv \u6dfb\u52a0\u5143\u6570\u636e"),t.qZA(),t.TgZ(146,"nz-form-control",24),t.TgZ(147,"nz-switch",51),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.postprocessing.injectExtraMetadata=s}),t.qZA(),t.qZA(),t.TgZ(148,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.postprocessing.injectExtraMetadata=s?a.globalSettings.postprocessing.injectExtraMetadata:null}),t._uU(149,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(150,"nz-form-item",10),t.TgZ(151,"nz-form-label",52),t._uU(152,"flv \u8f6c\u5c01\u88c5\u4e3a mp4"),t.qZA(),t.TgZ(153,"nz-form-control",24),t.TgZ(154,"nz-switch",53),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.postprocessing.remuxToMp4=s}),t.qZA(),t.qZA(),t.TgZ(155,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.postprocessing.remuxToMp4=s?a.globalSettings.postprocessing.remuxToMp4:null}),t._uU(156,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(157,"nz-form-item",10),t.TgZ(158,"nz-form-label",11),t._uU(159,"\u6e90\u6587\u4ef6\u5220\u9664\u7b56\u7565"),t.qZA(),t.YNc(160,gs,7,0,"ng-template",null,54,t.W1O),t.TgZ(162,"nz-form-control",12),t.TgZ(163,"nz-select",55),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.postprocessing.deleteSource=s}),t.qZA(),t.qZA(),t.TgZ(164,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.postprocessing.deleteSource=s?a.globalSettings.postprocessing.deleteSource:null}),t._uU(165,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.qZA(),t.TgZ(166,"div",56),t.TgZ(167,"h2"),t._uU(168,"\u7f51\u7edc\u8bf7\u6c42"),t.qZA(),t.TgZ(169,"nz-form-item",57),t.TgZ(170,"nz-form-label",58),t._uU(171,"User Agent"),t.qZA(),t.TgZ(172,"nz-form-control",59),t.TgZ(173,"textarea",60,61),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.header.userAgent=s}),t.qZA(),t.qZA(),t.YNc(175,ms,1,1,"ng-template",null,8,t.W1O),t.TgZ(177,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.header.userAgent=s?a.globalSettings.header.userAgent:null}),t._uU(178,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(179,"nz-form-item",57),t.TgZ(180,"nz-form-label",62),t._uU(181,"Cookie"),t.qZA(),t.TgZ(182,"nz-form-control",63),t.TgZ(183,"textarea",64,65),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.header.cookie=s}),t.qZA(),t.qZA(),t.TgZ(185,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.header.cookie=s?a.globalSettings.header.cookie:null}),t._uU(186,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.qZA(),t.qZA(),t.BQk()}if(2&n){const e=t.MAs(11),i=t.MAs(35),s=t.MAs(44),a=t.MAs(68),c=t.MAs(78),p=t.MAs(161),d=t.MAs(174),f=t.MAs(184),r=t.oxw();t.xp6(8),t.Q6J("nzErrorTip",e),t.xp6(1),t.Q6J("pattern",r.pathTemplatePattern)("ngModel",r.model.output.pathTemplate)("disabled",null===r.options.output.pathTemplate),t.xp6(3),t.Q6J("nzChecked",null!==r.options.output.pathTemplate),t.xp6(3),t.Q6J("nzTooltipTitle",r.splitFileTip),t.xp6(3),t.Q6J("ngModel",r.model.output.filesizeLimit)("disabled",null===r.options.output.filesizeLimit)("nzOptions",r.filesizeLimitOptions),t.xp6(1),t.Q6J("nzChecked",null!==r.options.output.filesizeLimit),t.xp6(3),t.Q6J("nzTooltipTitle",r.splitFileTip),t.xp6(3),t.Q6J("ngModel",r.model.output.durationLimit)("disabled",null===r.options.output.durationLimit)("nzOptions",r.durationLimitOptions),t.xp6(1),t.Q6J("nzChecked",null!==r.options.output.durationLimit),t.xp6(6),t.Q6J("nzTooltipTitle",i),t.xp6(5),t.Q6J("ngModel",r.model.recorder.streamFormat)("disabled",null===r.options.recorder.streamFormat)("nzOptions",r.streamFormatOptions),t.xp6(1),t.Q6J("nzChecked",null!==r.options.recorder.streamFormat),t.xp6(3),t.Q6J("nzTooltipTitle",s),t.xp6(5),t.Q6J("ngModel",r.model.recorder.fmp4StreamTimeout)("disabled",null===r.options.recorder.fmp4StreamTimeout)("nzOptions",r.fmp4StreamTimeoutOptions),t.xp6(2),t.Q6J("nzChecked",null!==r.options.recorder.fmp4StreamTimeout),t.xp6(6),t.Q6J("ngModel",r.model.recorder.qualityNumber)("disabled",null===r.options.recorder.qualityNumber)("nzOptions",r.qualityOptions),t.xp6(1),t.Q6J("nzChecked",null!==r.options.recorder.qualityNumber),t.xp6(6),t.Q6J("ngModel",r.model.recorder.saveCover)("disabled",null===r.options.recorder.saveCover),t.xp6(1),t.Q6J("nzChecked",null!==r.options.recorder.saveCover),t.xp6(3),t.Q6J("nzTooltipTitle",a),t.xp6(5),t.Q6J("ngModel",r.model.recorder.coverSaveStrategy)("disabled",null===r.options.recorder.coverSaveStrategy||!r.options.recorder.saveCover)("nzOptions",r.coverSaveStrategies),t.xp6(1),t.Q6J("nzChecked",null!==r.options.recorder.coverSaveStrategy),t.xp6(5),t.Q6J("nzValidateStatus",c.value>3?"warning":c),t.xp6(1),t.Q6J("ngModel",r.model.recorder.readTimeout)("disabled",null===r.options.recorder.readTimeout)("nzOptions",r.readTimeoutOptions),t.xp6(2),t.Q6J("nzChecked",null!==r.options.recorder.readTimeout),t.xp6(6),t.Q6J("ngModel",r.model.recorder.disconnectionTimeout)("disabled",null===r.options.recorder.disconnectionTimeout)("nzOptions",r.disconnectionTimeoutOptions)("nzOptionOverflowSize",6),t.xp6(1),t.Q6J("nzChecked",null!==r.options.recorder.bufferSize),t.xp6(6),t.Q6J("ngModel",r.model.recorder.bufferSize)("disabled",null===r.options.recorder.bufferSize)("nzOptions",r.bufferOptions)("nzOptionOverflowSize",6),t.xp6(1),t.Q6J("nzChecked",null!==r.options.recorder.bufferSize),t.xp6(9),t.Q6J("ngModel",r.model.danmaku.recordGiftSend)("disabled",null===r.options.danmaku.recordGiftSend),t.xp6(1),t.Q6J("nzChecked",null!==r.options.danmaku.recordGiftSend),t.xp6(6),t.Q6J("ngModel",r.model.danmaku.recordFreeGifts)("disabled",null===r.options.danmaku.recordFreeGifts),t.xp6(1),t.Q6J("nzChecked",null!==r.options.danmaku.recordFreeGifts),t.xp6(6),t.Q6J("ngModel",r.model.danmaku.recordGuardBuy)("disabled",null===r.options.danmaku.recordGuardBuy),t.xp6(1),t.Q6J("nzChecked",null!==r.options.danmaku.recordGuardBuy),t.xp6(6),t.Q6J("ngModel",r.model.danmaku.recordSuperChat)("disabled",null===r.options.danmaku.recordSuperChat),t.xp6(1),t.Q6J("nzChecked",null!==r.options.danmaku.recordSuperChat),t.xp6(6),t.Q6J("ngModel",r.model.danmaku.danmuUname)("disabled",null===r.options.danmaku.danmuUname),t.xp6(1),t.Q6J("nzChecked",null!==r.options.danmaku.danmuUname),t.xp6(6),t.Q6J("ngModel",r.model.danmaku.saveRawDanmaku)("disabled",null===r.options.danmaku.saveRawDanmaku),t.xp6(1),t.Q6J("nzChecked",null!==r.options.danmaku.saveRawDanmaku),t.xp6(9),t.Q6J("ngModel",r.model.postprocessing.injectExtraMetadata)("disabled",null===r.options.postprocessing.injectExtraMetadata||!!r.options.postprocessing.remuxToMp4),t.xp6(1),t.Q6J("nzChecked",null!==r.options.postprocessing.injectExtraMetadata),t.xp6(6),t.Q6J("ngModel",r.model.postprocessing.remuxToMp4)("disabled",null===r.options.postprocessing.remuxToMp4),t.xp6(1),t.Q6J("nzChecked",null!==r.options.postprocessing.remuxToMp4),t.xp6(3),t.Q6J("nzTooltipTitle",p),t.xp6(5),t.Q6J("ngModel",r.model.postprocessing.deleteSource)("disabled",null===r.options.postprocessing.deleteSource||!r.options.postprocessing.remuxToMp4)("nzOptions",r.deleteStrategies),t.xp6(1),t.Q6J("nzChecked",null!==r.options.postprocessing.deleteSource),t.xp6(8),t.Q6J("nzWarningTip",r.warningTip)("nzValidateStatus",d.valid&&r.options.header.userAgent!==r.taskOptions.header.userAgent&&r.options.header.userAgent!==r.globalSettings.header.userAgent?"warning":d)("nzErrorTip",e),t.xp6(1),t.Q6J("rows",3)("ngModel",r.model.header.userAgent)("disabled",null===r.options.header.userAgent),t.xp6(4),t.Q6J("nzChecked",null!==r.options.header.userAgent),t.xp6(5),t.Q6J("nzWarningTip",r.warningTip)("nzValidateStatus",f.valid&&r.options.header.cookie!==r.taskOptions.header.cookie&&r.options.header.cookie!==r.globalSettings.header.cookie?"warning":f),t.xp6(1),t.Q6J("rows",3)("ngModel",r.model.header.cookie)("disabled",null===r.options.header.cookie),t.xp6(2),t.Q6J("nzChecked",null!==r.options.header.cookie)}}let hs=(()=>{class n{constructor(e){this.changeDetector=e,this.visible=!1,this.visibleChange=new t.vpe,this.cancel=new t.vpe,this.confirm=new t.vpe,this.afterOpen=new t.vpe,this.afterClose=new t.vpe,this.warningTip="\u9700\u8981\u91cd\u542f\u5f39\u5e55\u5ba2\u6237\u7aef\u624d\u80fd\u751f\u6548\uff0c\u5982\u679c\u4efb\u52a1\u6b63\u5728\u5f55\u5236\u53ef\u80fd\u4f1a\u4e22\u5931\u5f39\u5e55\uff01",this.splitFileTip=w.Uk,this.pathTemplatePattern=w._m,this.filesizeLimitOptions=(0,F.Z)(w.Pu),this.durationLimitOptions=(0,F.Z)(w.Fg),this.streamFormatOptions=(0,F.Z)(w.tp),this.fmp4StreamTimeoutOptions=(0,F.Z)(w.D4),this.qualityOptions=(0,F.Z)(w.O6),this.readTimeoutOptions=(0,F.Z)(w.D4),this.disconnectionTimeoutOptions=(0,F.Z)(w.$w),this.bufferOptions=(0,F.Z)(w.Rc),this.deleteStrategies=(0,F.Z)(w.rc),this.coverSaveStrategies=(0,F.Z)(w.J_)}ngOnChanges(){this.options=(0,F.Z)(this.taskOptions),this.setupModel(),this.changeDetector.markForCheck()}close(){this.setVisible(!1)}setVisible(e){this.visible=e,this.visibleChange.emit(e),this.changeDetector.markForCheck()}handleCancel(){this.cancel.emit(),this.close()}handleConfirm(){this.confirm.emit((0,j.e5)(this.options,this.taskOptions)),this.close()}setupModel(){const e={};for(const i of Object.keys(this.options)){const c=this.globalSettings[i];Reflect.set(e,i,new Proxy(this.options[i],{get:(p,d)=>{var f;return null!==(f=Reflect.get(p,d))&&void 0!==f?f:Reflect.get(c,d)},set:(p,d,f)=>Reflect.set(p,d,f)}))}this.model=e}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-settings-dialog"]],viewQuery:function(e,i){if(1&e&&t.Gf(m.F,5),2&e){let s;t.iGM(s=t.CRH())&&(i.ngForm=s.first)}},inputs:{taskOptions:"taskOptions",globalSettings:"globalSettings",visible:"visible"},outputs:{visibleChange:"visibleChange",cancel:"cancel",confirm:"confirm",afterOpen:"afterOpen",afterClose:"afterClose"},features:[t.TTD],decls:2,vars:2,consts:[["nzTitle","\u4efb\u52a1\u8bbe\u7f6e","nzCentered","",3,"nzVisible","nzOkDisabled","nzOnOk","nzOnCancel","nzAfterOpen","nzAfterClose"],[4,"nzModalContent"],["nz-form","","ngForm",""],["ngModelGroup","output",1,"form-group","output"],[1,"setting-item","input"],["nzNoColon","","nzTooltipTitle","\u53d8\u91cf\u8bf4\u660e\u8bf7\u67e5\u770b\u5bf9\u5e94\u5168\u5c40\u8bbe\u7f6e",1,"setting-label"],[1,"setting-control","input",3,"nzErrorTip"],["type","text","required","","nz-input","","name","pathTemplate",3,"pattern","ngModel","disabled","ngModelChange"],["errorTip",""],["nz-checkbox","",3,"nzChecked","nzCheckedChange"],[1,"setting-item"],["nzNoColon","",1,"setting-label",3,"nzTooltipTitle"],[1,"setting-control","select"],["name","filesizeLimit",3,"ngModel","disabled","nzOptions","ngModelChange"],["name","durationLimit",3,"ngModel","disabled","nzOptions","ngModelChange"],["ngModelGroup","recorder",1,"form-group","recorder"],["streamFormatTip",""],["name","streamFormat",3,"ngModel","disabled","nzOptions","ngModelChange"],["fmp4StreamTimeoutTip",""],["name","fmp4StreamTimeout",3,"ngModel","disabled","nzOptions","ngModelChange"],["fmp4StreamTimeout","ngModel"],["nzNoColon","","nzTooltipTitle","\u6240\u9009\u753b\u8d28\u4e0d\u5b58\u5728\u5c06\u4ee5\u539f\u753b\u4ee3\u66ff",1,"setting-label"],["name","qualityNumber",3,"ngModel","disabled","nzOptions","ngModelChange"],["nzNoColon","","nzTooltipTitle","\u5f55\u64ad\u6587\u4ef6\u5b8c\u6210\u65f6\u4fdd\u5b58\u5f53\u524d\u76f4\u64ad\u95f4\u7684\u5c01\u9762",1,"setting-label"],[1,"setting-control","switch"],["name","saveCover",3,"ngModel","disabled","ngModelChange"],["coverSaveStrategyTip",""],["name","coverSaveStrategy",3,"ngModel","disabled","nzOptions","ngModelChange"],["nzNoColon","","nzTooltipTitle","\u8d85\u65f6\u65f6\u95f4\u8bbe\u7f6e\u5f97\u6bd4\u8f83\u957f\u76f8\u5bf9\u4e0d\u5bb9\u6613\u56e0\u7f51\u7edc\u4e0d\u7a33\u5b9a\u800c\u51fa\u73b0\u6d41\u4e2d\u65ad\uff0c\u4f46\u662f\u4e00\u65e6\u51fa\u73b0\u4e2d\u65ad\u5c31\u65e0\u6cd5\u5b9e\u73b0\u65e0\u7f1d\u62fc\u63a5\u4e14\u6f0f\u5f55\u8f83\u591a\u3002",1,"setting-label"],["nzWarningTip","\u65e0\u7f1d\u62fc\u63a5\u4f1a\u5931\u6548\uff01",1,"setting-control","select",3,"nzValidateStatus"],["name","readTimeout",3,"ngModel","disabled","nzOptions","ngModelChange"],["readTimeout","ngModel"],["nzNoColon","","nzTooltipTitle","\u65ad\u7f51\u8d85\u8fc7\u7b49\u5f85\u65f6\u95f4\u5c31\u7ed3\u675f\u5f55\u5236\uff0c\u5982\u679c\u7f51\u7edc\u6062\u590d\u540e\u4ecd\u672a\u4e0b\u64ad\u4f1a\u81ea\u52a8\u91cd\u65b0\u5f00\u59cb\u5f55\u5236\u3002",1,"setting-label"],["name","disconnectionTimeout",3,"ngModel","disabled","nzOptions","nzOptionOverflowSize","ngModelChange"],["nzNoColon","","nzTooltipTitle","\u786c\u76d8\u5199\u5165\u7f13\u51b2\u8bbe\u7f6e\u5f97\u6bd4\u8f83\u5927\u53ef\u4ee5\u51cf\u5c11\u5bf9\u786c\u76d8\u7684\u5199\u5165\uff0c\u4f46\u9700\u8981\u5360\u7528\u66f4\u591a\u7684\u5185\u5b58\u3002",1,"setting-label"],["name","bufferSize",3,"ngModel","disabled","nzOptions","nzOptionOverflowSize","ngModelChange"],["ngModelGroup","danmaku",1,"form-group","danmaku"],["nzFor","recordGiftSend","nzNoColon","","nzTooltipTitle","\u8bb0\u5f55\u793c\u7269\u4fe1\u606f\u5230\u5f39\u5e55\u6587\u4ef6\u91cc",1,"setting-label"],["id","recordGiftSend","name","recordGiftSend",3,"ngModel","disabled","ngModelChange"],["nzFor","recordFreeGifts","nzNoColon","","nzTooltipTitle","\u8bb0\u5f55\u514d\u8d39\u793c\u7269\u4fe1\u606f\u5230\u5f39\u5e55\u6587\u4ef6\u91cc",1,"setting-label"],["id","recordFreeGifts","name","recordFreeGifts",3,"ngModel","disabled","ngModelChange"],["nzFor","recordGuardBuy","nzNoColon","","nzTooltipTitle","\u8bb0\u5f55\u4e0a\u8230\u4fe1\u606f\u5230\u5f39\u5e55\u6587\u4ef6\u91cc",1,"setting-label"],["id","recordGuardBuy","name","recordGuardBuy",3,"ngModel","disabled","ngModelChange"],["nzFor","recordSuperChat","nzNoColon","","nzTooltipTitle","\u8bb0\u5f55 Super Chat \u4fe1\u606f\u5230\u5f39\u5e55\u6587\u4ef6\u91cc",1,"setting-label"],["id","recordSuperChat","name","recordSuperChat",3,"ngModel","disabled","ngModelChange"],["nzFor","danmuUname","nzNoColon","","nzTooltipTitle","\u53d1\u9001\u8005: \u5f39\u5e55\u5185\u5bb9",1,"setting-label"],["id","danmuUname","name","danmuUname",3,"ngModel","disabled","ngModelChange"],["nzFor","saveRawDanmaku","nzNoColon","","nzTooltipTitle","\u4fdd\u5b58\u539f\u59cb\u5f39\u5e55\u5230 JSON lines \u6587\u4ef6\uff0c\u4e3b\u8981\u7528\u4e8e\u5206\u6790\u8c03\u8bd5\u3002",1,"setting-label"],["id","saveRawDanmaku","name","saveRawDanmaku",3,"ngModel","disabled","ngModelChange"],["ngModelGroup","postprocessing",1,"form-group","postprocessing"],["nzNoColon","","nzTooltipTitle","\u6dfb\u52a0\u5173\u952e\u5e27\u7b49\u5143\u6570\u636e\u4f7f\u5b9a\u4f4d\u64ad\u653e\u548c\u62d6\u8fdb\u5ea6\u6761\u4e0d\u4f1a\u5361\u987f",1,"setting-label"],["name","injectExtraMetadata",3,"ngModel","disabled","ngModelChange"],["nzNoColon","","nzTooltipTitle","\u8c03\u7528 ffmpeg \u8fdb\u884c\u8f6c\u6362\uff0c\u9700\u8981\u5b89\u88c5 ffmpeg \u3002",1,"setting-label"],["name","remuxToMp4",3,"ngModel","disabled","ngModelChange"],["deleteSourceTip",""],["name","deleteSource",3,"ngModel","disabled","nzOptions","ngModelChange"],["ngModelGroup","header",1,"form-group","header"],[1,"setting-item","textarea"],["nzFor","userAgent","nzNoColon","",1,"setting-label"],[1,"setting-control","textarea",3,"nzWarningTip","nzValidateStatus","nzErrorTip"],["nz-input","","required","","id","userAgent","name","userAgent",3,"rows","ngModel","disabled","ngModelChange"],["userAgent","ngModel"],["nzFor","cookie","nzNoColon","",1,"setting-label"],[1,"setting-control","textarea",3,"nzWarningTip","nzValidateStatus"],["nz-input","","id","cookie","name","cookie",3,"rows","ngModel","disabled","ngModelChange"],["cookie","ngModel"],[4,"ngIf"]],template:function(e,i){1&e&&(t.TgZ(0,"nz-modal",0),t.NdJ("nzOnOk",function(){return i.handleConfirm()})("nzOnCancel",function(){return i.handleCancel()})("nzAfterOpen",function(){return i.afterOpen.emit()})("nzAfterClose",function(){return i.afterClose.emit()}),t.YNc(1,_s,187,94,"ng-container",1),t.qZA()),2&e&&t.Q6J("nzVisible",i.visible)("nzOkDisabled",null==i.ngForm||null==i.ngForm.form?null:i.ngForm.form.invalid)},directives:[V.du,V.Hf,m._Y,m.JL,m.F,B.Lr,m.Mq,P.SK,B.Nx,P.t3,B.iK,B.Fd,et.Zp,m.Fj,m.Q7,m.c5,m.JJ,m.On,u.O5,Wt.Ie,wt.Vq,Ot.i],styles:[".settings-page[_ngcontent-%COMP%]{max-width:680px;margin:0 auto}.setting-item[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap;align-items:center;margin:0;padding:1em 2em;border-top:1px solid rgba(0,0,0,.06)}.setting-item[_ngcontent-%COMP%]:first-child{border-top:none}.settings-page-content[_ngcontent-%COMP%]{display:flex;flex-direction:column;width:100%}.form-group[_ngcontent-%COMP%] h2[_ngcontent-%COMP%]{margin:0;font-weight:700;font-size:1.2em;border-bottom:1px solid rgba(0,0,0,.1)}.setting-item[_ngcontent-%COMP%]{gap:.5em}.setting-item.actionable[_ngcontent-%COMP%]{cursor:pointer}.setting-item.actionable[_ngcontent-%COMP%]:hover{background-color:#2021241a}a.setting-item[_ngcontent-%COMP%]{outline:none;text-decoration:none;color:inherit;cursor:pointer}a.setting-item[_ngcontent-%COMP%]:hover{background-color:#2021241a}.setting-label[_ngcontent-%COMP%]{padding:0!important;max-width:-moz-fit-content!important;max-width:fit-content!important;text-align:left}.setting-label.align-required[_ngcontent-%COMP%]{position:relative;left:11px}.setting-control[_ngcontent-%COMP%], .setting-value[_ngcontent-%COMP%]{max-width:-moz-fit-content!important;max-width:fit-content!important;margin-left:auto!important}.setting-control.input[_ngcontent-%COMP%], .setting-value.input[_ngcontent-%COMP%]{max-width:100%!important}.setting-control.select[_ngcontent-%COMP%], .setting-value.select[_ngcontent-%COMP%]{max-width:8em!important;width:8em!important}.setting-control.switch[_ngcontent-%COMP%], .setting-value.switch[_ngcontent-%COMP%]{flex-direction:row-reverse;align-items:center}.setting-control.checkbox[_ngcontent-%COMP%], .setting-value.checkbox[_ngcontent-%COMP%]{max-width:100%!important;width:100%!important;margin-left:0}.setting-control.checkbox[_ngcontent-%COMP%] label[_ngcontent-%COMP%], .setting-value.checkbox[_ngcontent-%COMP%] label[_ngcontent-%COMP%]{width:100%}.setting-control.textarea[_ngcontent-%COMP%], .setting-value.textarea[_ngcontent-%COMP%]{max-width:100%!important;width:100%!important;margin-left:0}@media screen and (max-width: 332px){.setting-control.radio[_ngcontent-%COMP%], .setting-value.radio[_ngcontent-%COMP%]{margin-left:0!important}}@media screen and (max-width: 319px){.setting-control.select[_ngcontent-%COMP%], .setting-value.select[_ngcontent-%COMP%]{margin-left:0!important}}.setting-value[_ngcontent-%COMP%]{color:#5f6368;font-weight:400}nz-divider[_ngcontent-%COMP%]{margin:0!important}.form-group[_ngcontent-%COMP%]:last-child .setting-item[_ngcontent-%COMP%]:last-child{padding-bottom:0}.setting-item[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(3,1fr);align-items:center;padding:1em 0;grid-gap:1em;gap:1em;border:none}.setting-item[_ngcontent-%COMP%] > *[_ngcontent-%COMP%]{margin:0!important}.setting-item[_ngcontent-%COMP%] .setting-label[_ngcontent-%COMP%]{justify-self:start}.setting-item[_ngcontent-%COMP%] .setting-control[_ngcontent-%COMP%]{justify-self:center}.setting-item[_ngcontent-%COMP%] label[nz-checkbox][_ngcontent-%COMP%]{justify-self:end}.setting-item[_ngcontent-%COMP%] label[nz-checkbox][_ngcontent-%COMP%] span:last-of-type{padding-right:0}.setting-item.input[_ngcontent-%COMP%], .setting-item.textarea[_ngcontent-%COMP%]{grid-template-columns:repeat(2,1fr)}.setting-item.input[_ngcontent-%COMP%] .setting-label[_ngcontent-%COMP%], .setting-item.textarea[_ngcontent-%COMP%] .setting-label[_ngcontent-%COMP%]{grid-row:1/2;grid-column:1/2;justify-self:center}.setting-item.input[_ngcontent-%COMP%] .setting-control[_ngcontent-%COMP%], .setting-item.textarea[_ngcontent-%COMP%] .setting-control[_ngcontent-%COMP%]{grid-row:2/3;grid-column:1/-1;justify-self:stretch}.setting-item.input[_ngcontent-%COMP%] label[nz-checkbox][_ngcontent-%COMP%], .setting-item.textarea[_ngcontent-%COMP%] label[nz-checkbox][_ngcontent-%COMP%]{grid-row:1/2;grid-column:2/3;justify-self:center}@media screen and (max-width: 450px){.setting-item[_ngcontent-%COMP%]{grid-template-columns:repeat(2,1fr)}.setting-item[_ngcontent-%COMP%] .setting-label[_ngcontent-%COMP%]{grid-column:1/-1;justify-self:center}.setting-item[_ngcontent-%COMP%] label[nz-checkbox][_ngcontent-%COMP%]{justify-self:end}}"],changeDetection:0}),n})();function Ce(n,o,e,i,s,a,c){try{var p=n[a](c),d=p.value}catch(f){return void e(f)}p.done?o(d):Promise.resolve(d).then(i,s)}var Gt=l(5254),zs=l(3753),Cs=l(2313);const $t=new Map,Vt=new Map;let Ts=(()=>{class n{constructor(e){this.domSanitizer=e}transform(e,i="object"){return"object"===i?Vt.has(e)?(0,A.of)(Vt.get(e)):(0,Gt.D)(this.fetchImage(e)).pipe((0,H.U)(s=>URL.createObjectURL(s)),(0,H.U)(s=>this.domSanitizer.bypassSecurityTrustUrl(s)),(0,k.b)(s=>Vt.set(e,s)),(0,it.K)(()=>(0,A.of)(this.domSanitizer.bypassSecurityTrustUrl("")))):$t.has(e)?(0,A.of)($t.get(e)):(0,Gt.D)(this.fetchImage(e)).pipe((0,nt.w)(s=>this.createDataURL(s)),(0,k.b)(s=>$t.set(e,s)),(0,it.K)(()=>(0,A.of)(this.domSanitizer.bypassSecurityTrustUrl(""))))}fetchImage(e){return function fs(n){return function(){var o=this,e=arguments;return new Promise(function(i,s){var a=n.apply(o,e);function c(d){Ce(a,i,s,c,p,"next",d)}function p(d){Ce(a,i,s,c,p,"throw",d)}c(void 0)})}}(function*(){return yield(yield fetch(e,{referrer:""})).blob()})()}createDataURL(e){const i=new FileReader,s=(0,zs.R)(i,"load").pipe((0,H.U)(()=>this.domSanitizer.bypassSecurityTrustUrl(i.result)));return i.readAsDataURL(e),s}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(Cs.H7,16))},n.\u0275pipe=t.Yjl({name:"dataurl",type:n,pure:!0}),n})();function xs(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"app-info-panel",21),t.NdJ("close",function(){return t.CHM(e),t.oxw(2).showInfoPanel=!1}),t.qZA()}if(2&n){const e=t.oxw(2);t.Q6J("data",e.data)}}const ks=function(n){return[n,"detail"]};function vs(n,o){if(1&n&&(t.TgZ(0,"a",15),t.TgZ(1,"div",16),t._UZ(2,"img",17),t.ALo(3,"async"),t.ALo(4,"dataurl"),t.TgZ(5,"h2",18),t._uU(6),t.qZA(),t.YNc(7,xs,1,1,"app-info-panel",19),t._UZ(8,"app-status-display",20),t.qZA(),t.qZA()),2&n){const e=t.oxw();t.Q6J("routerLink",t.VKq(10,ks,e.data.room_info.room_id)),t.xp6(2),t.Q6J("src",t.lcZ(3,6,t.lcZ(4,8,e.data.room_info.cover)),t.LSH),t.xp6(3),t.Q6J("nzTooltipTitle","\u76f4\u64ad\u95f4\u6807\u9898\uff1a"+e.data.room_info.title),t.xp6(1),t.hij(" ",e.data.room_info.title," "),t.xp6(1),t.Q6J("ngIf",e.showInfoPanel),t.xp6(1),t.Q6J("status",e.data.task_status)}}function ys(n,o){if(1&n&&(t._UZ(0,"nz-avatar",22),t.ALo(1,"async"),t.ALo(2,"dataurl")),2&n){const e=t.oxw();t.Q6J("nzShape","square")("nzSize",54)("nzSrc",t.lcZ(1,3,t.lcZ(2,5,e.data.user_info.face)))}}function bs(n,o){1&n&&(t.TgZ(0,"nz-tag",31),t._UZ(1,"i",32),t.TgZ(2,"span"),t._uU(3,"\u672a\u5f00\u64ad"),t.qZA(),t.qZA())}function Ss(n,o){1&n&&(t.TgZ(0,"nz-tag",33),t._UZ(1,"i",34),t.TgZ(2,"span"),t._uU(3,"\u76f4\u64ad\u4e2d"),t.qZA(),t.qZA())}function As(n,o){1&n&&(t.TgZ(0,"nz-tag",35),t._UZ(1,"i",36),t.TgZ(2,"span"),t._uU(3,"\u8f6e\u64ad\u4e2d"),t.qZA(),t.qZA())}function Ds(n,o){if(1&n&&(t.TgZ(0,"p",23),t.TgZ(1,"span",24),t.TgZ(2,"a",25),t._uU(3),t.qZA(),t.qZA(),t.TgZ(4,"span",26),t.ynx(5,27),t.YNc(6,bs,4,0,"nz-tag",28),t.YNc(7,Ss,4,0,"nz-tag",29),t.YNc(8,As,4,0,"nz-tag",30),t.BQk(),t.qZA(),t.qZA()),2&n){const e=t.oxw();t.xp6(2),t.MGl("href","https://space.bilibili.com/",e.data.user_info.uid,"",t.LSH),t.xp6(1),t.hij(" ",e.data.user_info.name," "),t.xp6(2),t.Q6J("ngSwitch",e.data.room_info.live_status),t.xp6(1),t.Q6J("ngSwitchCase",0),t.xp6(1),t.Q6J("ngSwitchCase",1),t.xp6(1),t.Q6J("ngSwitchCase",2)}}function Ms(n,o){if(1&n&&(t.TgZ(0,"span",44),t.TgZ(1,"a",25),t._uU(2),t.qZA(),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.MGl("href","https://live.bilibili.com/",e.data.room_info.short_room_id,"",t.LSH),t.xp6(1),t.hij(" ",e.data.room_info.short_room_id,"")}}function Zs(n,o){if(1&n&&(t.TgZ(0,"p",37),t.TgZ(1,"span",38),t.TgZ(2,"span",39),t._uU(3,"\u623f\u95f4\u53f7\uff1a"),t.qZA(),t.YNc(4,Ms,3,2,"span",40),t.TgZ(5,"span",41),t.TgZ(6,"a",25),t._uU(7),t.qZA(),t.qZA(),t.qZA(),t.TgZ(8,"span",42),t.TgZ(9,"a",25),t.TgZ(10,"nz-tag",43),t._uU(11),t.qZA(),t.qZA(),t.qZA(),t.qZA()),2&n){const e=t.oxw();t.xp6(4),t.Q6J("ngIf",e.data.room_info.short_room_id),t.xp6(2),t.MGl("href","https://live.bilibili.com/",e.data.room_info.room_id,"",t.LSH),t.xp6(1),t.Oqu(e.data.room_info.room_id),t.xp6(2),t.hYB("href","https://live.bilibili.com/p/eden/area-tags?parentAreaId=",e.data.room_info.parent_area_id,"&areaId=",e.data.room_info.area_id,"",t.LSH),t.xp6(1),t.Q6J("nzColor","#23ade5"),t.xp6(1),t.hij(" ",e.data.room_info.area_name," ")}}function Os(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"nz-switch",45),t.NdJ("click",function(){return t.CHM(e),t.oxw().toggleRecorder()}),t.qZA()}if(2&n){const e=t.oxw();t.Q6J("nzDisabled",e.toggleRecorderForbidden)("ngModel",e.data.task_status.recorder_enabled)("nzControl",!0)("nzLoading",e.switchPending)}}function ws(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"div",46),t.NdJ("click",function(){return t.CHM(e),t.oxw().cutStream()}),t._UZ(1,"i",47),t.qZA()}if(2&n){const e=t.oxw();t.ekj("not-allowed",e.data.task_status.running_status!==e.RunningStatus.RECORDING)}}function Fs(n,o){if(1&n){const e=t.EpF();t.ynx(0),t.TgZ(1,"app-task-settings-dialog",51),t.NdJ("visibleChange",function(s){return t.CHM(e),t.oxw(2).settingsDialogVisible=s})("confirm",function(s){return t.CHM(e),t.oxw(2).changeTaskOptions(s)})("afterClose",function(){return t.CHM(e),t.oxw(2).cleanSettingsData()}),t.qZA(),t.BQk()}if(2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("taskOptions",e.taskOptions)("globalSettings",e.globalSettings)("visible",e.settingsDialogVisible)}}function Ns(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"div",48),t.NdJ("click",function(){return t.CHM(e),t.oxw().openSettingsDialog()}),t._UZ(1,"i",49),t.qZA(),t.YNc(2,Fs,2,3,"ng-container",50)}if(2&n){const e=t.oxw();t.xp6(2),t.Q6J("ngIf",e.taskOptions&&e.globalSettings)}}function Ps(n,o){if(1&n&&(t.TgZ(0,"div",54),t._UZ(1,"i",55),t.qZA()),2&n){t.oxw(2);const e=t.MAs(20);t.Q6J("nzDropdownMenu",e)}}function Es(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"div",56),t.NdJ("click",function(){return t.CHM(e),t.oxw(2).menuDrawerVisible=!0}),t._UZ(1,"i",55),t.qZA()}}function Is(n,o){if(1&n&&(t.YNc(0,Ps,2,1,"div",52),t.YNc(1,Es,2,0,"div",53)),2&n){const e=t.oxw();t.Q6J("ngIf",!e.useDrawer),t.xp6(1),t.Q6J("ngIf",e.useDrawer)}}function Bs(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"ul",57),t.TgZ(1,"li",58),t.NdJ("click",function(){return t.CHM(e),t.oxw().startTask()}),t._uU(2,"\u8fd0\u884c\u4efb\u52a1"),t.qZA(),t.TgZ(3,"li",58),t.NdJ("click",function(){return t.CHM(e),t.oxw().stopTask()}),t._uU(4,"\u505c\u6b62\u4efb\u52a1"),t.qZA(),t.TgZ(5,"li",58),t.NdJ("click",function(){return t.CHM(e),t.oxw().removeTask()}),t._uU(6,"\u5220\u9664\u4efb\u52a1"),t.qZA(),t.TgZ(7,"li",58),t.NdJ("click",function(){return t.CHM(e),t.oxw().stopTask(!0)}),t._uU(8,"\u5f3a\u5236\u505c\u6b62\u4efb\u52a1"),t.qZA(),t.TgZ(9,"li",58),t.NdJ("click",function(){return t.CHM(e),t.oxw().disableRecorder(!0)}),t._uU(10,"\u5f3a\u5236\u5173\u95ed\u5f55\u5236"),t.qZA(),t.TgZ(11,"li",58),t.NdJ("click",function(){return t.CHM(e),t.oxw().updateTaskInfo()}),t._uU(12,"\u5237\u65b0\u6570\u636e"),t.qZA(),t.TgZ(13,"li",58),t.NdJ("click",function(){return t.CHM(e),t.oxw().showInfoPanel=!0}),t._uU(14,"\u663e\u793a\u5f55\u5236\u4fe1\u606f"),t.qZA(),t.qZA()}}function Js(n,o){if(1&n){const e=t.EpF();t.ynx(0),t.TgZ(1,"div",61),t.NdJ("click",function(){return t.CHM(e),t.oxw(2).menuDrawerVisible=!1}),t.GkF(2,12),t.qZA(),t.BQk()}if(2&n){t.oxw(2);const e=t.MAs(23);t.xp6(2),t.Q6J("ngTemplateOutlet",e)}}const Qs=function(){return{padding:"0"}};function Us(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"nz-drawer",59),t.NdJ("nzVisibleChange",function(s){return t.CHM(e),t.oxw().menuDrawerVisible=s})("nzOnClose",function(){return t.CHM(e),t.oxw().menuDrawerVisible=!1}),t.YNc(1,Js,3,1,"ng-container",60),t.qZA()}if(2&n){const e=t.oxw();t.Q6J("nzClosable",!1)("nzBodyStyle",t.DdM(3,Qs))("nzVisible",e.menuDrawerVisible)}}const qs=function(n,o,e,i){return[n,o,e,i]},Rs=function(){return{padding:"0.5rem"}},Ls=function(){return{size:"large"}};let Ys=(()=>{class n{constructor(e,i,s,a,c,p,d){this.changeDetector=i,this.message=s,this.modal=a,this.settingService=c,this.taskManager=p,this.appTaskSettings=d,this.stopped=!1,this.destroyed=new v.xQ,this.useDrawer=!1,this.menuDrawerVisible=!1,this.switchPending=!1,this.settingsDialogVisible=!1,this.RunningStatus=O,e.observe(pt[0]).pipe((0,y.R)(this.destroyed)).subscribe(f=>{this.useDrawer=f.matches,i.markForCheck()})}get roomId(){return this.data.room_info.room_id}get toggleRecorderForbidden(){return!this.data.task_status.monitor_enabled}get showInfoPanel(){return Boolean(this.appTaskSettings.getSettings(this.roomId).showInfoPanel)}set showInfoPanel(e){this.appTaskSettings.updateSettings(this.roomId,{showInfoPanel:e})}ngOnChanges(e){console.debug("[ngOnChanges]",this.roomId,e),this.stopped=this.data.task_status.running_status===O.STOPPED}ngOnDestroy(){this.destroyed.next(),this.destroyed.complete()}updateTaskInfo(){this.taskManager.updateTaskInfo(this.roomId).subscribe()}toggleRecorder(){this.toggleRecorderForbidden||this.switchPending||(this.switchPending=!0,this.data.task_status.recorder_enabled?this.taskManager.disableRecorder(this.roomId).subscribe(()=>this.switchPending=!1):this.taskManager.enableRecorder(this.roomId).subscribe(()=>this.switchPending=!1))}removeTask(){this.taskManager.removeTask(this.roomId).subscribe()}startTask(){this.data.task_status.running_status===O.STOPPED?this.taskManager.startTask(this.roomId).subscribe():this.message.warning("\u4efb\u52a1\u8fd0\u884c\u4e2d\uff0c\u5ffd\u7565\u64cd\u4f5c\u3002")}stopTask(e=!1){this.data.task_status.running_status!==O.STOPPED?e&&this.data.task_status.running_status==O.RECORDING?this.modal.confirm({nzTitle:"\u786e\u5b9a\u8981\u5f3a\u5236\u505c\u6b62\u4efb\u52a1\uff1f",nzContent:"\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\u4f1a\u88ab\u5f3a\u884c\u4e2d\u65ad\uff01\u786e\u5b9a\u8981\u653e\u5f03\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\uff1f",nzOnOk:()=>new Promise((i,s)=>{this.taskManager.stopTask(this.roomId,e).subscribe(i,s)})}):this.taskManager.stopTask(this.roomId).subscribe():this.message.warning("\u4efb\u52a1\u5904\u4e8e\u505c\u6b62\u72b6\u6001\uff0c\u5ffd\u7565\u64cd\u4f5c\u3002")}disableRecorder(e=!1){this.data.task_status.recorder_enabled?e&&this.data.task_status.running_status==O.RECORDING?this.modal.confirm({nzTitle:"\u786e\u5b9a\u8981\u5f3a\u5236\u505c\u6b62\u5f55\u5236\uff1f",nzContent:"\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\u4f1a\u88ab\u5f3a\u884c\u4e2d\u65ad\uff01\u786e\u5b9a\u8981\u653e\u5f03\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\uff1f",nzOnOk:()=>new Promise((i,s)=>{this.taskManager.disableRecorder(this.roomId,e).subscribe(i,s)})}):this.taskManager.disableRecorder(this.roomId).subscribe():this.message.warning("\u5f55\u5236\u5904\u4e8e\u5173\u95ed\u72b6\u6001\uff0c\u5ffd\u7565\u64cd\u4f5c\u3002")}openSettingsDialog(){It(this.settingService.getTaskOptions(this.roomId),this.settingService.getSettings(["output","header","danmaku","recorder","postprocessing"])).subscribe(([e,i])=>{this.taskOptions=e,this.globalSettings=i,this.settingsDialogVisible=!0,this.changeDetector.markForCheck()},e=>{this.message.error(`\u83b7\u53d6\u4efb\u52a1\u8bbe\u7f6e\u51fa\u9519: ${e.message}`)})}cleanSettingsData(){delete this.taskOptions,delete this.globalSettings,this.changeDetector.markForCheck()}changeTaskOptions(e){this.settingService.changeTaskOptions(this.roomId,e).pipe((0,bt.X)(3,300)).subscribe(i=>{this.message.success("\u4fee\u6539\u4efb\u52a1\u8bbe\u7f6e\u6210\u529f")},i=>{this.message.error(`\u4fee\u6539\u4efb\u52a1\u8bbe\u7f6e\u51fa\u9519: ${i.message}`)})}cutStream(){this.data.task_status.running_status===O.RECORDING&&this.taskManager.cutStream(this.roomId).subscribe()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(U.Yg),t.Y36(t.sBO),t.Y36(qt.dD),t.Y36(V.Sf),t.Y36(jo.R),t.Y36(Rt),t.Y36(Ho))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-item"]],hostVars:2,hostBindings:function(e,i){2&e&&t.ekj("stopped",i.stopped)},inputs:{data:"data"},features:[t.TTD],decls:25,vars:19,consts:[[3,"nzCover","nzHoverable","nzActions","nzBodyStyle"],[3,"nzActive","nzLoading","nzAvatar"],[3,"nzAvatar","nzTitle","nzDescription"],["coverTemplate",""],["avatarTemplate",""],["titleTemplate",""],["descTemplate",""],["actionSwitch",""],["actionDelete",""],["actionSetting",""],["actionMore",""],["dropdownMenu","nzDropdownMenu"],[3,"ngTemplateOutlet"],["menu",""],["nzPlacement","bottom","nzHeight","auto",3,"nzClosable","nzBodyStyle","nzVisible","nzVisibleChange","nzOnClose",4,"ngIf"],[3,"routerLink"],[1,"cover-wrapper"],["alt","\u76f4\u64ad\u95f4\u5c01\u9762",1,"cover",3,"src"],["nz-tooltip","","nzTooltipPlacement","bottomLeft",1,"title",3,"nzTooltipTitle"],[3,"data","close",4,"ngIf"],[3,"status"],[3,"data","close"],[3,"nzShape","nzSize","nzSrc"],[1,"meta-title"],["nz-tooltip","","nzTooltipTitle","\u6253\u5f00\u4e3b\u64ad\u4e2a\u4eba\u7a7a\u95f4\u9875\u9762","nzTooltipPlacement","right",1,"user-name"],["target","_blank",3,"href"],[1,"live-status"],[3,"ngSwitch"],["nzColor","default",4,"ngSwitchCase"],["nzColor","red",4,"ngSwitchCase"],["nzColor","green",4,"ngSwitchCase"],["nzColor","default"],["nz-icon","","nzType","frown"],["nzColor","red"],["nz-icon","","nzType","fire"],["nzColor","green"],["nz-icon","","nzType","sync","nzSpin",""],[1,"meta-desc"],[1,"room-id-wrapper"],[1,"room-id-label"],["class","short-room-id","nz-tooltip","","nzTooltipTitle","\u6253\u5f00\u76f4\u64ad\u95f4\u9875\u9762","nzTooltipPlacement","bottom",4,"ngIf"],["nz-tooltip","","nzTooltipTitle","\u6253\u5f00\u76f4\u64ad\u95f4\u9875\u9762","nzTooltipPlacement","bottom",1,"real-room-id"],["nz-tooltip","","nzTooltipTitle","\u6253\u5f00\u76f4\u64ad\u5206\u533a\u9875\u9762","nzTooltipPlacement","leftTop",1,"area-name"],[3,"nzColor"],["nz-tooltip","","nzTooltipTitle","\u6253\u5f00\u76f4\u64ad\u95f4\u9875\u9762","nzTooltipPlacement","bottom",1,"short-room-id"],["nz-tooltip","","nzTooltipTitle","\u5f55\u5236\u5f00\u5173",3,"nzDisabled","ngModel","nzControl","nzLoading","click"],["nz-tooltip","","nzTooltipTitle","\u5207\u5272\u6587\u4ef6",3,"click"],["nz-icon","","nzType","scissor",1,"action-icon"],["nz-tooltip","","nzTooltipTitle","\u4efb\u52a1\u8bbe\u7f6e",3,"click"],["nz-icon","","nzType","setting",1,"action-icon"],[4,"ngIf"],[3,"taskOptions","globalSettings","visible","visibleChange","confirm","afterClose"],["nz-dropdown","","nzPlacement","topRight",3,"nzDropdownMenu",4,"ngIf"],[3,"click",4,"ngIf"],["nz-dropdown","","nzPlacement","topRight",3,"nzDropdownMenu"],["nz-icon","","nzType","more",1,"action-icon"],[3,"click"],["nz-menu","",1,"menu"],["nz-menu-item","",3,"click"],["nzPlacement","bottom","nzHeight","auto",3,"nzClosable","nzBodyStyle","nzVisible","nzVisibleChange","nzOnClose"],[4,"nzDrawerContent"],[1,"drawer-content",3,"click"]],template:function(e,i){if(1&e&&(t.TgZ(0,"nz-card",0),t.TgZ(1,"nz-skeleton",1),t._UZ(2,"nz-card-meta",2),t.qZA(),t.qZA(),t.YNc(3,vs,9,12,"ng-template",null,3,t.W1O),t.YNc(5,ys,3,7,"ng-template",null,4,t.W1O),t.YNc(7,Ds,9,6,"ng-template",null,5,t.W1O),t.YNc(9,Zs,12,7,"ng-template",null,6,t.W1O),t.YNc(11,Os,1,4,"ng-template",null,7,t.W1O),t.YNc(13,ws,2,2,"ng-template",null,8,t.W1O),t.YNc(15,Ns,3,1,"ng-template",null,9,t.W1O),t.YNc(17,Is,2,2,"ng-template",null,10,t.W1O),t.TgZ(19,"nz-dropdown-menu",null,11),t.GkF(21,12),t.YNc(22,Bs,15,0,"ng-template",null,13,t.W1O),t.qZA(),t.YNc(24,Us,2,4,"nz-drawer",14)),2&e){const s=t.MAs(4),a=t.MAs(6),c=t.MAs(8),p=t.MAs(10),d=t.MAs(12),f=t.MAs(14),r=t.MAs(16),N=t.MAs(18),I=t.MAs(23);t.Q6J("nzCover",s)("nzHoverable",!0)("nzActions",t.l5B(12,qs,f,r,d,N))("nzBodyStyle",t.DdM(17,Rs)),t.xp6(1),t.Q6J("nzActive",!0)("nzLoading",!i.data)("nzAvatar",t.DdM(18,Ls)),t.xp6(1),t.Q6J("nzAvatar",a)("nzTitle",c)("nzDescription",p),t.xp6(19),t.Q6J("ngTemplateOutlet",I),t.xp6(3),t.Q6J("ngIf",i.useDrawer)}},directives:[D.bd,ye,D.l7,ct.yS,rt.SY,u.O5,es,ss,at.Dz,u.RF,u.n9,st,M.Ls,Lt.w,Ot.i,m.JJ,m.On,hs,tt.cm,tt.RR,u.tP,gt.wO,gt.r9,lt.Vz,lt.SQ],pipes:[u.Ov,Ts],styles:['.cover-wrapper[_ngcontent-%COMP%] .title[_ngcontent-%COMP%]{color:#fff;text-shadow:1px 1px 2px black;margin:0;padding:0 .5rem;background:rgba(0,0,0,.32)}.drawer-content[_ngcontent-%COMP%] .menu[_ngcontent-%COMP%]{box-shadow:none;padding:.5em 0}.drawer-content[_ngcontent-%COMP%] .menu[_ngcontent-%COMP%] *[nz-menu-item][_ngcontent-%COMP%]{margin:0;padding:.5em 2em}.stopped[_nghost-%COMP%]{filter:grayscale(100%)}a[_ngcontent-%COMP%]{color:inherit}a[_ngcontent-%COMP%]:hover{color:#1890ff}a[_ngcontent-%COMP%]:focus-visible{outline:-webkit-focus-ring-color auto 1px}.cover-wrapper[_ngcontent-%COMP%]{--cover-ratio: 264 / 470;--cover-height: calc(var(--card-width) * var(--cover-ratio));position:relative;width:var(--card-width);height:var(--cover-height)}.cover-wrapper[_ngcontent-%COMP%] .cover[_ngcontent-%COMP%]{width:100%;max-height:var(--cover-height);object-fit:cover}.cover-wrapper[_ngcontent-%COMP%] .title[_ngcontent-%COMP%]{position:absolute;top:.5rem;left:.5rem;font-size:1.2rem;width:-moz-fit-content;width:fit-content;max-width:calc(100% - 1em);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}nz-card-meta[_ngcontent-%COMP%]{margin:0}.meta-title[_ngcontent-%COMP%]{margin:0;display:flex;column-gap:1em}.meta-title[_ngcontent-%COMP%] .user-name[_ngcontent-%COMP%]{color:#fb7299;font-size:1rem;font-weight:700;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.meta-title[_ngcontent-%COMP%] .live-status[_ngcontent-%COMP%] nz-tag[_ngcontent-%COMP%]{margin:0;position:relative;bottom:1px}.meta-desc[_ngcontent-%COMP%]{margin:0;display:flex}.meta-desc[_ngcontent-%COMP%] .room-id-wrapper[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap}.meta-desc[_ngcontent-%COMP%] .room-id-wrapper[_ngcontent-%COMP%] .short-room-id[_ngcontent-%COMP%]:after{display:inline-block;width:1em;content:","}@media screen and (max-width: 320px){.meta-desc[_ngcontent-%COMP%] .room-id-wrapper[_ngcontent-%COMP%] .room-id-label[_ngcontent-%COMP%]{display:none}}.meta-desc[_ngcontent-%COMP%] .area-name[_ngcontent-%COMP%]{margin-left:auto}.meta-desc[_ngcontent-%COMP%] .area-name[_ngcontent-%COMP%] nz-tag[_ngcontent-%COMP%]{margin:0;border-radius:30px;padding:0 1em}.action-icon[_ngcontent-%COMP%]{font-size:16px}.not-allowed[_ngcontent-%COMP%]{cursor:not-allowed}'],changeDetection:0}),n})();function Gs(n,o){1&n&&(t.TgZ(0,"div",2),t._UZ(1,"nz-empty"),t.qZA())}function $s(n,o){1&n&&t._UZ(0,"app-task-item",6),2&n&&t.Q6J("data",o.$implicit)}function Vs(n,o){if(1&n&&(t.TgZ(0,"div",3,4),t.YNc(2,$s,1,1,"app-task-item",5),t.qZA()),2&n){const e=t.oxw();t.xp6(2),t.Q6J("ngForOf",e.dataList)("ngForTrackBy",e.trackByRoomId)}}let js=(()=>{class n{constructor(){this.dataList=[]}trackByRoomId(e,i){return i.room_info.room_id}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-list"]],inputs:{dataList:"dataList"},decls:3,vars:2,consts:[["class","empty-container",4,"ngIf","ngIfElse"],["elseBlock",""],[1,"empty-container"],[1,"tasks-container"],["tasks",""],[3,"data",4,"ngFor","ngForOf","ngForTrackBy"],[3,"data"]],template:function(e,i){if(1&e&&(t.YNc(0,Gs,2,0,"div",0),t.YNc(1,Vs,3,2,"ng-template",null,1,t.W1O)),2&e){const s=t.MAs(2);t.Q6J("ngIf",0===i.dataList.length)("ngIfElse",s)}},directives:[u.O5,Kt.p9,u.sg,Ys],styles:["[_nghost-%COMP%]{height:100%;width:100%;position:relative;display:block;margin:0;padding:1rem;background:#f1f1f1;overflow:auto}[_nghost-%COMP%]{--card-width: 400px;--grid-gutter: 12px;padding:var(--grid-gutter)}@media screen and (max-width: 400px){[_nghost-%COMP%]{--card-width: 100%;padding:var(--grid-gutter) 0}}.tasks-container[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(auto-fill,var(--card-width));grid-gap:var(--grid-gutter);gap:var(--grid-gutter);justify-content:center;max-width:100%;margin:0 auto}.empty-container[_ngcontent-%COMP%]{height:100%;width:100%;display:flex;align-items:center;justify-content:center}"],changeDetection:0}),n})();var Hs=l(2643),Ws=l(1406);function Xs(n,o){1&n&&(t.ynx(0),t._uU(1," \u8bf7\u8f93\u5165\u76f4\u64ad\u95f4\u53f7\u6216 URL "),t.BQk())}function Ks(n,o){1&n&&(t.ynx(0),t._uU(1," \u8f93\u5165\u6709\u9519\u8bef "),t.BQk())}function ta(n,o){if(1&n&&(t.YNc(0,Xs,2,0,"ng-container",8),t.YNc(1,Ks,2,0,"ng-container",8)),2&n){const e=o.$implicit;t.Q6J("ngIf",e.hasError("required")),t.xp6(1),t.Q6J("ngIf",e.hasError("pattern"))}}function ea(n,o){if(1&n&&(t.ynx(0),t._UZ(1,"nz-alert",9),t.BQk()),2&n){const e=o.$implicit;t.xp6(1),t.Q6J("nzType",e.type)("nzMessage",e.message)}}function na(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"form",2),t.TgZ(2,"nz-form-item"),t.TgZ(3,"nz-form-control",3),t._UZ(4,"input",4),t.YNc(5,ta,2,2,"ng-template",null,5,t.W1O),t.qZA(),t.qZA(),t.qZA(),t.TgZ(7,"div",6),t.YNc(8,ea,2,2,"ng-container",7),t.qZA(),t.BQk()),2&n){const e=t.MAs(6),i=t.oxw();t.xp6(1),t.Q6J("formGroup",i.formGroup),t.xp6(2),t.Q6J("nzErrorTip",e),t.xp6(1),t.Q6J("pattern",i.pattern),t.xp6(4),t.Q6J("ngForOf",i.resultMessages)}}const ia=/^https?:\/\/live\.bilibili\.com\/(\d+).*$/,oa=/^\s*(?:\d+(?:[ ]+\d+)*|https?:\/\/live\.bilibili\.com\/\d+.*)\s*$/;let sa=(()=>{class n{constructor(e,i,s){this.changeDetector=i,this.taskManager=s,this.visible=!1,this.visibleChange=new t.vpe,this.pending=!1,this.resultMessages=[],this.pattern=oa,this.formGroup=e.group({input:["",[m.kI.required,m.kI.pattern(this.pattern)]]})}get inputControl(){return this.formGroup.get("input")}open(){this.setVisible(!0)}close(){this.resultMessages=[],this.reset(),this.setVisible(!1)}setVisible(e){this.visible=e,this.visibleChange.emit(e),this.changeDetector.markForCheck()}reset(){this.pending=!1,this.formGroup.reset(),this.changeDetector.markForCheck()}handleCancel(){this.close()}handleConfirm(){this.pending=!0;const e=this.inputControl.value.trim();let i;i=e.startsWith("http")?[parseInt(ia.exec(e)[1])]:new Set(e.split(/\s+/).map(s=>parseInt(s))),(0,Gt.D)(i).pipe((0,Ws.b)(s=>this.taskManager.addTask(s)),(0,k.b)(s=>{this.resultMessages.push(s),this.changeDetector.markForCheck()})).subscribe({complete:()=>{this.resultMessages.every(s=>"success"===s.type)?this.close():this.reset()}})}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(m.qu),t.Y36(t.sBO),t.Y36(Rt))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-add-task-dialog"]],inputs:{visible:"visible"},outputs:{visibleChange:"visibleChange"},decls:2,vars:6,consts:[["nzTitle","\u6dfb\u52a0\u4efb\u52a1","nzCentered","","nzOkText","\u6dfb\u52a0",3,"nzVisible","nzOkLoading","nzOkDisabled","nzCancelDisabled","nzClosable","nzMaskClosable","nzOnOk","nzOnCancel"],[4,"nzModalContent"],["nz-form","",3,"formGroup"],["nzHasFeedback","",3,"nzErrorTip"],["nz-input","","required","","placeholder","\u76f4\u64ad\u95f4 URL \u6216\u623f\u95f4\u53f7\uff08\u652f\u6301\u591a\u4e2a\u623f\u95f4\u53f7\u7528\u7a7a\u683c\u9694\u5f00\uff09","formControlName","input",3,"pattern"],["errorTip",""],[1,"result-messages-container"],[4,"ngFor","ngForOf"],[4,"ngIf"],["nzShowIcon","",3,"nzType","nzMessage"]],template:function(e,i){1&e&&(t.TgZ(0,"nz-modal",0),t.NdJ("nzOnOk",function(){return i.handleConfirm()})("nzOnCancel",function(){return i.handleCancel()}),t.YNc(1,na,9,4,"ng-container",1),t.qZA()),2&e&&t.Q6J("nzVisible",i.visible)("nzOkLoading",i.pending)("nzOkDisabled",i.formGroup.invalid)("nzCancelDisabled",i.pending)("nzClosable",!i.pending)("nzMaskClosable",!i.pending)},directives:[V.du,V.Hf,m._Y,m.JL,B.Lr,m.sg,P.SK,B.Nx,P.t3,B.Fd,et.Zp,m.Fj,m.Q7,m.JJ,m.u,m.c5,u.O5,u.sg,He],styles:[".result-messages-container[_ngcontent-%COMP%]{width:100%;max-height:200px;overflow-y:auto}"],changeDetection:0}),n})(),ra=(()=>{class n{transform(e,i=""){return console.debug("filter tasks by '%s'",i),[...this.filterByTerm(e,i)]}filterByTerm(e,i){return function*aa(n,o){for(const e of n)o(e)&&(yield e)}(e,s=>""===(i=i.trim())||s.user_info.name.includes(i)||s.room_info.title.toString().includes(i)||s.room_info.area_name.toString().includes(i)||s.room_info.room_id.toString().includes(i)||s.room_info.short_room_id.toString().includes(i))}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"filterTasks",type:n,pure:!0}),n})();function la(n,o){if(1&n&&t._UZ(0,"nz-spin",6),2&n){const e=t.oxw();t.Q6J("nzSize","large")("nzSpinning",e.loading)}}function ca(n,o){if(1&n&&(t._UZ(0,"app-task-list",7),t.ALo(1,"filterTasks")),2&n){const e=t.oxw();t.Q6J("dataList",t.xi3(1,1,e.dataList,e.filterTerm))}}const Te="app-tasks-selection",xe="app-tasks-reverse",ua=[{path:":id/detail",component:Ao},{path:"",component:(()=>{class n{constructor(e,i,s,a){this.changeDetector=e,this.notification=i,this.storage=s,this.taskService=a,this.loading=!0,this.dataList=[],this.filterTerm="",this.selection=this.retrieveSelection(),this.reverse=this.retrieveReverse()}ngOnInit(){this.syncTaskData()}ngOnDestroy(){this.desyncTaskData()}onSelectionChanged(e){this.selection=e,this.storeSelection(e),this.desyncTaskData(),this.syncTaskData()}onReverseChanged(e){this.reverse=e,this.storeReverse(e),e&&(this.dataList=[...this.dataList.reverse()],this.changeDetector.markForCheck())}retrieveSelection(){const e=this.storage.getData(Te);return null!==e?e:Z.ALL}retrieveReverse(){return"true"===this.storage.getData(xe)}storeSelection(e){this.storage.setData(Te,e)}storeReverse(e){this.storage.setData(xe,e.toString())}syncTaskData(){this.dataSubscription=(0,A.of)((0,A.of)(0),vt(1e3)).pipe((0,Bt.u)(),(0,nt.w)(()=>this.taskService.getAllTaskData(this.selection)),(0,it.K)(e=>{throw this.notification.error("\u83b7\u53d6\u4efb\u52a1\u6570\u636e\u51fa\u9519",e.message),e}),(0,bt.X)(10,3e3)).subscribe(e=>{this.loading=!1,this.dataList=this.reverse?e.reverse():e,this.changeDetector.markForCheck()},e=>{this.notification.error("\u83b7\u53d6\u4efb\u52a1\u6570\u636e\u51fa\u9519","\u7f51\u7edc\u8fde\u63a5\u5f02\u5e38, \u8bf7\u5f85\u7f51\u7edc\u6b63\u5e38\u540e\u5237\u65b0\u3002",{nzDuration:0})})}desyncTaskData(){var e;null===(e=this.dataSubscription)||void 0===e||e.unsubscribe()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO),t.Y36(Jt.zb),t.Y36(fe.V),t.Y36(St))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-tasks"]],decls:8,vars:4,consts:[[3,"selection","reverse","selectionChange","reverseChange","filterChange"],["class","spinner",3,"nzSize","nzSpinning",4,"ngIf","ngIfElse"],["elseBlock",""],["nz-button","","nzType","primary","nzSize","large","nzShape","circle","nz-tooltip","","nzTooltipTitle","\u6dfb\u52a0\u4efb\u52a1",1,"add-task-button",3,"click"],["nz-icon","","nzType","plus"],["addTaskDialog",""],[1,"spinner",3,"nzSize","nzSpinning"],[3,"dataList"]],template:function(e,i){if(1&e){const s=t.EpF();t.TgZ(0,"app-toolbar",0),t.NdJ("selectionChange",function(c){return i.onSelectionChanged(c)})("reverseChange",function(c){return i.onReverseChanged(c)})("filterChange",function(c){return i.filterTerm=c}),t.qZA(),t.YNc(1,la,1,2,"nz-spin",1),t.YNc(2,ca,2,4,"ng-template",null,2,t.W1O),t.TgZ(4,"button",3),t.NdJ("click",function(){return t.CHM(s),t.MAs(7).open()}),t._UZ(5,"i",4),t.qZA(),t._UZ(6,"app-add-task-dialog",null,5)}if(2&e){const s=t.MAs(3);t.Q6J("selection",i.selection)("reverse",i.reverse),t.xp6(1),t.Q6J("ngIf",i.loading)("ngIfElse",s)}},directives:[Vo,u.O5,te.W,js,Ct.ix,Hs.dQ,Lt.w,rt.SY,M.Ls,sa],pipes:[ra],styles:["[_nghost-%COMP%]{height:100%;width:100%;position:relative;display:block;margin:0;padding:1rem;background:#f1f1f1;overflow:auto}.spinner[_ngcontent-%COMP%]{height:100%;width:100%}[_nghost-%COMP%]{display:flex;flex-direction:column;padding:0;overflow:hidden}.add-task-button[_ngcontent-%COMP%]{position:fixed;bottom:5vh;right:5vw}"],changeDetection:0}),n})()}];let pa=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[ct.Bz.forChild(ua)],ct.Bz]}),n})(),ga=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[u.ez,m.u5,m.UX,U.xu,dt,P.Jb,D.vh,Ht,at.Rt,M.PV,Ht,rt.cg,G,Ot.m,tt.b1,Ct.sL,V.Qp,B.U5,et.o7,Wt.Wr,Ie,Tt.aF,Xt.S,Kt.Xo,te.j,We,lt.BL,wt.LV,bn,J.HQ,Bn,Ti,Ai.forRoot({echarts:()=>l.e(45).then(l.bind(l,8045))}),pa,Di.m]]}),n})()},855:function(jt){jt.exports=function(){"use strict";var ot=/^(b|B)$/,l={iec:{bits:["b","Kib","Mib","Gib","Tib","Pib","Eib","Zib","Yib"],bytes:["B","KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"]},jedec:{bits:["b","Kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb"],bytes:["B","KB","MB","GB","TB","PB","EB","ZB","YB"]}},u={iec:["","kibi","mebi","gibi","tebi","pebi","exbi","zebi","yobi"],jedec:["","kilo","mega","giga","tera","peta","exa","zetta","yotta"]},m={floor:Math.floor,ceil:Math.ceil};function U(t){var h,q,R,W,dt,P,D,M,_,v,y,L,T,b,Y,X,st,G,at,Mt,$,z=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},g=[],K=0;if(isNaN(t))throw new TypeError("Invalid number");if(R=!0===z.bits,Y=!0===z.unix,L=!0===z.pad,T=void 0!==z.round?z.round:Y?1:2,D=void 0!==z.locale?z.locale:"",M=z.localeOptions||{},X=void 0!==z.separator?z.separator:"",st=void 0!==z.spacer?z.spacer:Y?"":" ",at=z.symbols||{},G=2===(q=z.base||2)&&z.standard||"jedec",y=z.output||"string",dt=!0===z.fullform,P=z.fullforms instanceof Array?z.fullforms:[],h=void 0!==z.exponent?z.exponent:-1,Mt=m[z.roundingMethod]||Math.round,_=(v=Number(t))<0,W=q>2?1e3:1024,$=!1===isNaN(z.precision)?parseInt(z.precision,10):0,_&&(v=-v),(-1===h||isNaN(h))&&(h=Math.floor(Math.log(v)/Math.log(W)))<0&&(h=0),h>8&&($>0&&($+=8-h),h=8),"exponent"===y)return h;if(0===v)g[0]=0,b=g[1]=Y?"":l[G][R?"bits":"bytes"][h];else{K=v/(2===q?Math.pow(2,10*h):Math.pow(1e3,h)),R&&(K*=8)>=W&&h<8&&(K/=W,h++);var mt=Math.pow(10,h>0?T:0);g[0]=Mt(K*mt)/mt,g[0]===W&&h<8&&void 0===z.exponent&&(g[0]=1,h++),b=g[1]=10===q&&1===h?R?"kb":"kB":l[G][R?"bits":"bytes"][h],Y&&(g[1]="jedec"===G?g[1].charAt(0):h>0?g[1].replace(/B$/,""):g[1],ot.test(g[1])&&(g[0]=Math.floor(g[0]),g[1]=""))}if(_&&(g[0]=-g[0]),$>0&&(g[0]=g[0].toPrecision($)),g[1]=at[g[1]]||g[1],!0===D?g[0]=g[0].toLocaleString():D.length>0?g[0]=g[0].toLocaleString(D,M):X.length>0&&(g[0]=g[0].toString().replace(".",X)),L&&!1===Number.isInteger(g[0])&&T>0){var _t=X||".",ht=g[0].toString().split(_t),ft=ht[1]||"",zt=ft.length,Zt=T-zt;g[0]="".concat(ht[0]).concat(_t).concat(ft.padEnd(zt+Zt,"0"))}return dt&&(g[1]=P[h]?P[h]:u[G][h]+(R?"bit":"byte")+(1===g[0]?"":"s")),"array"===y?g:"object"===y?{value:g[0],symbol:g[1],exponent:h,unit:b}:g.join(st)}return U.partial=function(t){return function(h){return U(h,t)}},U}()}}]); \ No newline at end of file diff --git a/src/blrec/data/webapp/869.42b1fd9a88732b97.js b/src/blrec/data/webapp/869.42b1fd9a88732b97.js deleted file mode 100644 index adc02f5..0000000 --- a/src/blrec/data/webapp/869.42b1fd9a88732b97.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunkblrec=self.webpackChunkblrec||[]).push([[869],{5869:(jt,ot,l)=>{"use strict";l.r(ot),l.d(ot,{TasksModule:()=>ga});var u=l(9808),m=l(4182),U=l(5113),t=l(5e3);class h{constructor(o,e){this._document=e;const i=this._textarea=this._document.createElement("textarea"),s=i.style;s.position="fixed",s.top=s.opacity="0",s.left="-999em",i.setAttribute("aria-hidden","true"),i.value=o,this._document.body.appendChild(i)}copy(){const o=this._textarea;let e=!1;try{if(o){const i=this._document.activeElement;o.select(),o.setSelectionRange(0,o.value.length),e=this._document.execCommand("copy"),i&&i.focus()}}catch(i){}return e}destroy(){const o=this._textarea;o&&(o.remove(),this._textarea=void 0)}}let q=(()=>{class n{constructor(e){this._document=e}copy(e){const i=this.beginCopy(e),s=i.copy();return i.destroy(),s}beginCopy(e){return new h(e,this._document)}}return n.\u0275fac=function(e){return new(e||n)(t.LFG(u.K0))},n.\u0275prov=t.Yz7({token:n,factory:n.\u0275fac,providedIn:"root"}),n})(),dt=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({}),n})();var P=l(1894),D=l(7484),M=l(647),_=l(655),v=l(8929),y=l(7625),L=l(8693),T=l(1721),b=l(226);function Y(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"i",1),t.NdJ("click",function(s){return t.CHM(e),t.oxw().closeTag(s)}),t.qZA()}}const X=["*"];let st=(()=>{class n{constructor(e,i,s,a){this.cdr=e,this.renderer=i,this.elementRef=s,this.directionality=a,this.isPresetColor=!1,this.nzMode="default",this.nzChecked=!1,this.nzOnClose=new t.vpe,this.nzCheckedChange=new t.vpe,this.dir="ltr",this.destroy$=new v.xQ}updateCheckedStatus(){"checkable"===this.nzMode&&(this.nzChecked=!this.nzChecked,this.nzCheckedChange.emit(this.nzChecked))}closeTag(e){this.nzOnClose.emit(e),e.defaultPrevented||this.renderer.removeChild(this.renderer.parentNode(this.elementRef.nativeElement),this.elementRef.nativeElement)}clearPresetColor(){const e=this.elementRef.nativeElement,i=new RegExp(`(ant-tag-(?:${[...L.uf,...L.Bh].join("|")}))`,"g"),s=e.classList.toString(),a=[];let c=i.exec(s);for(;null!==c;)a.push(c[1]),c=i.exec(s);e.classList.remove(...a)}setPresetColor(){const e=this.elementRef.nativeElement;this.clearPresetColor(),this.isPresetColor=!!this.nzColor&&((0,L.o2)(this.nzColor)||(0,L.M8)(this.nzColor)),this.isPresetColor&&e.classList.add(`ant-tag-${this.nzColor}`)}ngOnInit(){var e;null===(e=this.directionality.change)||void 0===e||e.pipe((0,y.R)(this.destroy$)).subscribe(i=>{this.dir=i,this.cdr.detectChanges()}),this.dir=this.directionality.value}ngOnChanges(e){const{nzColor:i}=e;i&&this.setPresetColor()}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO),t.Y36(t.Qsj),t.Y36(t.SBq),t.Y36(b.Is,8))},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-tag"]],hostAttrs:[1,"ant-tag"],hostVars:10,hostBindings:function(e,i){1&e&&t.NdJ("click",function(){return i.updateCheckedStatus()}),2&e&&(t.Udp("background-color",i.isPresetColor?"":i.nzColor),t.ekj("ant-tag-has-color",i.nzColor&&!i.isPresetColor)("ant-tag-checkable","checkable"===i.nzMode)("ant-tag-checkable-checked",i.nzChecked)("ant-tag-rtl","rtl"===i.dir))},inputs:{nzMode:"nzMode",nzColor:"nzColor",nzChecked:"nzChecked"},outputs:{nzOnClose:"nzOnClose",nzCheckedChange:"nzCheckedChange"},exportAs:["nzTag"],features:[t.TTD],ngContentSelectors:X,decls:2,vars:1,consts:[["nz-icon","","nzType","close","class","ant-tag-close-icon","tabindex","-1",3,"click",4,"ngIf"],["nz-icon","","nzType","close","tabindex","-1",1,"ant-tag-close-icon",3,"click"]],template:function(e,i){1&e&&(t.F$t(),t.Hsn(0),t.YNc(1,Y,1,0,"i",0)),2&e&&(t.xp6(1),t.Q6J("ngIf","closeable"===i.nzMode))},directives:[u.O5,M.Ls],encapsulation:2,changeDetection:0}),(0,_.gn)([(0,T.yF)()],n.prototype,"nzChecked",void 0),n})(),G=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[b.vT,u.ez,m.u5,M.PV]]}),n})();var at=l(6699);const $=["nzType","avatar"];function K(n,o){if(1&n&&(t.TgZ(0,"div",5),t._UZ(1,"nz-skeleton-element",6),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("nzSize",e.avatar.size||"default")("nzShape",e.avatar.shape||"circle")}}function mt(n,o){if(1&n&&t._UZ(0,"h3",7),2&n){const e=t.oxw(2);t.Udp("width",e.toCSSUnit(e.title.width))}}function _t(n,o){if(1&n&&t._UZ(0,"li"),2&n){const e=o.index,i=t.oxw(3);t.Udp("width",i.toCSSUnit(i.widthList[e]))}}function ht(n,o){if(1&n&&(t.TgZ(0,"ul",8),t.YNc(1,_t,1,2,"li",9),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("ngForOf",e.rowsList)}}function ft(n,o){if(1&n&&(t.ynx(0),t.YNc(1,K,2,2,"div",1),t.TgZ(2,"div",2),t.YNc(3,mt,1,2,"h3",3),t.YNc(4,ht,2,1,"ul",4),t.qZA(),t.BQk()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("ngIf",!!e.nzAvatar),t.xp6(2),t.Q6J("ngIf",!!e.nzTitle),t.xp6(1),t.Q6J("ngIf",!!e.nzParagraph)}}function zt(n,o){1&n&&(t.ynx(0),t.Hsn(1),t.BQk())}const Zt=["*"];let ke=(()=>{class n{constructor(){this.nzActive=!1,this.nzBlock=!1}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275dir=t.lG2({type:n,selectors:[["nz-skeleton-element"]],hostAttrs:[1,"ant-skeleton","ant-skeleton-element"],hostVars:4,hostBindings:function(e,i){2&e&&t.ekj("ant-skeleton-active",i.nzActive)("ant-skeleton-block",i.nzBlock)},inputs:{nzActive:"nzActive",nzType:"nzType",nzBlock:"nzBlock"}}),(0,_.gn)([(0,T.yF)()],n.prototype,"nzBlock",void 0),n})(),ve=(()=>{class n{constructor(){this.nzShape="circle",this.nzSize="default",this.styleMap={}}ngOnChanges(e){if(e.nzSize&&"number"==typeof this.nzSize){const i=`${this.nzSize}px`;this.styleMap={width:i,height:i,"line-height":i}}else this.styleMap={}}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-skeleton-element","nzType","avatar"]],inputs:{nzShape:"nzShape",nzSize:"nzSize"},features:[t.TTD],attrs:$,decls:1,vars:9,consts:[[1,"ant-skeleton-avatar",3,"ngStyle"]],template:function(e,i){1&e&&t._UZ(0,"span",0),2&e&&(t.ekj("ant-skeleton-avatar-square","square"===i.nzShape)("ant-skeleton-avatar-circle","circle"===i.nzShape)("ant-skeleton-avatar-lg","large"===i.nzSize)("ant-skeleton-avatar-sm","small"===i.nzSize),t.Q6J("ngStyle",i.styleMap))},directives:[u.PC],encapsulation:2,changeDetection:0}),n})(),ye=(()=>{class n{constructor(e,i,s){this.cdr=e,this.nzActive=!1,this.nzLoading=!0,this.nzRound=!1,this.nzTitle=!0,this.nzAvatar=!1,this.nzParagraph=!0,this.rowsList=[],this.widthList=[],i.addClass(s.nativeElement,"ant-skeleton")}toCSSUnit(e=""){return(0,T.WX)(e)}getTitleProps(){const e=!!this.nzAvatar,i=!!this.nzParagraph;let s="";return!e&&i?s="38%":e&&i&&(s="50%"),Object.assign({width:s},this.getProps(this.nzTitle))}getAvatarProps(){return Object.assign({shape:this.nzTitle&&!this.nzParagraph?"square":"circle",size:"large"},this.getProps(this.nzAvatar))}getParagraphProps(){const e=!!this.nzAvatar,i=!!this.nzTitle,s={};return(!e||!i)&&(s.width="61%"),s.rows=!e&&i?3:2,Object.assign(Object.assign({},s),this.getProps(this.nzParagraph))}getProps(e){return e&&"object"==typeof e?e:{}}getWidthList(){const{width:e,rows:i}=this.paragraph;let s=[];return e&&Array.isArray(e)?s=e:e&&!Array.isArray(e)&&(s=[],s[i-1]=e),s}updateProps(){this.title=this.getTitleProps(),this.avatar=this.getAvatarProps(),this.paragraph=this.getParagraphProps(),this.rowsList=[...Array(this.paragraph.rows)],this.widthList=this.getWidthList(),this.cdr.markForCheck()}ngOnInit(){this.updateProps()}ngOnChanges(e){(e.nzTitle||e.nzAvatar||e.nzParagraph)&&this.updateProps()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO),t.Y36(t.Qsj),t.Y36(t.SBq))},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-skeleton"]],hostVars:6,hostBindings:function(e,i){2&e&&t.ekj("ant-skeleton-with-avatar",!!i.nzAvatar)("ant-skeleton-active",i.nzActive)("ant-skeleton-round",!!i.nzRound)},inputs:{nzActive:"nzActive",nzLoading:"nzLoading",nzRound:"nzRound",nzTitle:"nzTitle",nzAvatar:"nzAvatar",nzParagraph:"nzParagraph"},exportAs:["nzSkeleton"],features:[t.TTD],ngContentSelectors:Zt,decls:2,vars:2,consts:[[4,"ngIf"],["class","ant-skeleton-header",4,"ngIf"],[1,"ant-skeleton-content"],["class","ant-skeleton-title",3,"width",4,"ngIf"],["class","ant-skeleton-paragraph",4,"ngIf"],[1,"ant-skeleton-header"],["nzType","avatar",3,"nzSize","nzShape"],[1,"ant-skeleton-title"],[1,"ant-skeleton-paragraph"],[3,"width",4,"ngFor","ngForOf"]],template:function(e,i){1&e&&(t.F$t(),t.YNc(0,ft,5,3,"ng-container",0),t.YNc(1,zt,2,0,"ng-container",0)),2&e&&(t.Q6J("ngIf",i.nzLoading),t.xp6(1),t.Q6J("ngIf",!i.nzLoading))},directives:[ve,u.O5,ke,u.sg],encapsulation:2,changeDetection:0}),n})(),Ht=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[b.vT,u.ez]]}),n})();var rt=l(404),Ot=l(6462),tt=l(3677),Ct=l(6042),V=l(7957),B=l(4546),et=l(1047),Wt=l(6114),be=l(4832),Se=l(2845),Ae=l(6950),De=l(5664),E=l(969),Me=l(4170);let Ie=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[b.vT,u.ez,Ct.sL,Se.U8,Me.YI,M.PV,E.T,Ae.e4,be.g,rt.cg,De.rt]]}),n})();var Tt=l(3868),Xt=l(5737),Kt=l(685),te=l(7525),Be=l(8076),S=l(9439);function Je(n,o){if(1&n&&(t.ynx(0),t._UZ(1,"i",5),t.BQk()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("nzType",e.nzIconType||e.inferredIconType)("nzTheme",e.iconTheme)}}function Qe(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(4);t.xp6(1),t.Oqu(e.nzMessage)}}function Ue(n,o){if(1&n&&(t.TgZ(0,"span",9),t.YNc(1,Qe,2,1,"ng-container",10),t.qZA()),2&n){const e=t.oxw(3);t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.nzMessage)}}function qe(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(4);t.xp6(1),t.Oqu(e.nzDescription)}}function Re(n,o){if(1&n&&(t.TgZ(0,"span",11),t.YNc(1,qe,2,1,"ng-container",10),t.qZA()),2&n){const e=t.oxw(3);t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.nzDescription)}}function Le(n,o){if(1&n&&(t.TgZ(0,"div",6),t.YNc(1,Ue,2,1,"span",7),t.YNc(2,Re,2,1,"span",8),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("ngIf",e.nzMessage),t.xp6(1),t.Q6J("ngIf",e.nzDescription)}}function Ye(n,o){1&n&&t._UZ(0,"i",15)}function Ge(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"span",16),t._uU(2),t.qZA(),t.BQk()),2&n){const e=t.oxw(4);t.xp6(2),t.Oqu(e.nzCloseText)}}function $e(n,o){if(1&n&&(t.ynx(0),t.YNc(1,Ge,3,1,"ng-container",10),t.BQk()),2&n){const e=t.oxw(3);t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.nzCloseText)}}function Ve(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"button",12),t.NdJ("click",function(){return t.CHM(e),t.oxw(2).closeAlert()}),t.YNc(1,Ye,1,0,"ng-template",null,13,t.W1O),t.YNc(3,$e,2,1,"ng-container",14),t.qZA()}if(2&n){const e=t.MAs(2),i=t.oxw(2);t.xp6(3),t.Q6J("ngIf",i.nzCloseText)("ngIfElse",e)}}function je(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"div",1),t.NdJ("@slideAlertMotion.done",function(){return t.CHM(e),t.oxw().onFadeAnimationDone()}),t.YNc(1,Je,2,2,"ng-container",2),t.YNc(2,Le,3,2,"div",3),t.YNc(3,Ve,4,2,"button",4),t.qZA()}if(2&n){const e=t.oxw();t.ekj("ant-alert-rtl","rtl"===e.dir)("ant-alert-success","success"===e.nzType)("ant-alert-info","info"===e.nzType)("ant-alert-warning","warning"===e.nzType)("ant-alert-error","error"===e.nzType)("ant-alert-no-icon",!e.nzShowIcon)("ant-alert-banner",e.nzBanner)("ant-alert-closable",e.nzCloseable)("ant-alert-with-description",!!e.nzDescription),t.Q6J("@.disabled",e.nzNoAnimation)("@slideAlertMotion",void 0),t.xp6(1),t.Q6J("ngIf",e.nzShowIcon),t.xp6(1),t.Q6J("ngIf",e.nzMessage||e.nzDescription),t.xp6(1),t.Q6J("ngIf",e.nzCloseable||e.nzCloseText)}}let He=(()=>{class n{constructor(e,i,s){this.nzConfigService=e,this.cdr=i,this.directionality=s,this._nzModuleName="alert",this.nzCloseText=null,this.nzIconType=null,this.nzMessage=null,this.nzDescription=null,this.nzType="info",this.nzCloseable=!1,this.nzShowIcon=!1,this.nzBanner=!1,this.nzNoAnimation=!1,this.nzOnClose=new t.vpe,this.closed=!1,this.iconTheme="fill",this.inferredIconType="info-circle",this.dir="ltr",this.isTypeSet=!1,this.isShowIconSet=!1,this.destroy$=new v.xQ,this.nzConfigService.getConfigChangeEventForComponent("alert").pipe((0,y.R)(this.destroy$)).subscribe(()=>{this.cdr.markForCheck()})}ngOnInit(){var e;null===(e=this.directionality.change)||void 0===e||e.pipe((0,y.R)(this.destroy$)).subscribe(i=>{this.dir=i,this.cdr.detectChanges()}),this.dir=this.directionality.value}closeAlert(){this.closed=!0}onFadeAnimationDone(){this.closed&&this.nzOnClose.emit(!0)}ngOnChanges(e){const{nzShowIcon:i,nzDescription:s,nzType:a,nzBanner:c}=e;if(i&&(this.isShowIconSet=!0),a)switch(this.isTypeSet=!0,this.nzType){case"error":this.inferredIconType="close-circle";break;case"success":this.inferredIconType="check-circle";break;case"info":this.inferredIconType="info-circle";break;case"warning":this.inferredIconType="exclamation-circle"}s&&(this.iconTheme=this.nzDescription?"outline":"fill"),c&&(this.isTypeSet||(this.nzType="warning"),this.isShowIconSet||(this.nzShowIcon=!0))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(S.jY),t.Y36(t.sBO),t.Y36(b.Is,8))},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-alert"]],inputs:{nzCloseText:"nzCloseText",nzIconType:"nzIconType",nzMessage:"nzMessage",nzDescription:"nzDescription",nzType:"nzType",nzCloseable:"nzCloseable",nzShowIcon:"nzShowIcon",nzBanner:"nzBanner",nzNoAnimation:"nzNoAnimation"},outputs:{nzOnClose:"nzOnClose"},exportAs:["nzAlert"],features:[t.TTD],decls:1,vars:1,consts:[["class","ant-alert",3,"ant-alert-rtl","ant-alert-success","ant-alert-info","ant-alert-warning","ant-alert-error","ant-alert-no-icon","ant-alert-banner","ant-alert-closable","ant-alert-with-description",4,"ngIf"],[1,"ant-alert"],[4,"ngIf"],["class","ant-alert-content",4,"ngIf"],["type","button","tabindex","0","class","ant-alert-close-icon",3,"click",4,"ngIf"],["nz-icon","",1,"ant-alert-icon",3,"nzType","nzTheme"],[1,"ant-alert-content"],["class","ant-alert-message",4,"ngIf"],["class","ant-alert-description",4,"ngIf"],[1,"ant-alert-message"],[4,"nzStringTemplateOutlet"],[1,"ant-alert-description"],["type","button","tabindex","0",1,"ant-alert-close-icon",3,"click"],["closeDefaultTemplate",""],[4,"ngIf","ngIfElse"],["nz-icon","","nzType","close"],[1,"ant-alert-close-text"]],template:function(e,i){1&e&&t.YNc(0,je,4,23,"div",0),2&e&&t.Q6J("ngIf",!i.closed)},directives:[u.O5,M.Ls,E.f],encapsulation:2,data:{animation:[Be.Rq]},changeDetection:0}),(0,_.gn)([(0,S.oS)(),(0,T.yF)()],n.prototype,"nzCloseable",void 0),(0,_.gn)([(0,S.oS)(),(0,T.yF)()],n.prototype,"nzShowIcon",void 0),(0,_.gn)([(0,T.yF)()],n.prototype,"nzBanner",void 0),(0,_.gn)([(0,T.yF)()],n.prototype,"nzNoAnimation",void 0),n})(),We=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[b.vT,u.ez,M.PV,E.T]]}),n})();var lt=l(4147),wt=l(5197);function Xe(n,o){if(1&n&&(t.ynx(0),t._UZ(1,"i",8),t.BQk()),2&n){const e=t.oxw(3);t.xp6(1),t.Q6J("nzType",e.icon)}}function Ke(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=o.$implicit,i=t.oxw(4);t.xp6(1),t.hij(" ",e(i.nzPercent)," ")}}const tn=function(n){return{$implicit:n}};function en(n,o){if(1&n&&t.YNc(0,Ke,2,1,"ng-container",9),2&n){const e=t.oxw(3);t.Q6J("nzStringTemplateOutlet",e.formatter)("nzStringTemplateOutletContext",t.VKq(2,tn,e.nzPercent))}}function nn(n,o){if(1&n&&(t.TgZ(0,"span",5),t.YNc(1,Xe,2,1,"ng-container",6),t.YNc(2,en,1,4,"ng-template",null,7,t.W1O),t.qZA()),2&n){const e=t.MAs(3),i=t.oxw(2);t.xp6(1),t.Q6J("ngIf",("exception"===i.status||"success"===i.status)&&!i.nzFormat)("ngIfElse",e)}}function on(n,o){if(1&n&&t.YNc(0,nn,4,2,"span",4),2&n){const e=t.oxw();t.Q6J("ngIf",e.nzShowInfo)}}function sn(n,o){if(1&n&&t._UZ(0,"div",17),2&n){const e=t.oxw(4);t.Udp("width",e.nzSuccessPercent,"%")("border-radius","round"===e.nzStrokeLinecap?"100px":"0")("height",e.strokeWidth,"px")}}function an(n,o){if(1&n&&(t.TgZ(0,"div",13),t.TgZ(1,"div",14),t._UZ(2,"div",15),t.YNc(3,sn,1,6,"div",16),t.qZA(),t.qZA()),2&n){const e=t.oxw(3);t.xp6(2),t.Udp("width",e.nzPercent,"%")("border-radius","round"===e.nzStrokeLinecap?"100px":"0")("background",e.isGradient?null:e.nzStrokeColor)("background-image",e.isGradient?e.lineGradient:null)("height",e.strokeWidth,"px"),t.xp6(1),t.Q6J("ngIf",e.nzSuccessPercent||0===e.nzSuccessPercent)}}function rn(n,o){}function ln(n,o){if(1&n&&(t.ynx(0),t.YNc(1,an,4,11,"div",11),t.YNc(2,rn,0,0,"ng-template",12),t.BQk()),2&n){const e=t.oxw(2),i=t.MAs(1);t.xp6(1),t.Q6J("ngIf",!e.isSteps),t.xp6(1),t.Q6J("ngTemplateOutlet",i)}}function cn(n,o){1&n&&t._UZ(0,"div",20),2&n&&t.Q6J("ngStyle",o.$implicit)}function un(n,o){}function pn(n,o){if(1&n&&(t.TgZ(0,"div",18),t.YNc(1,cn,1,1,"div",19),t.YNc(2,un,0,0,"ng-template",12),t.qZA()),2&n){const e=t.oxw(2),i=t.MAs(1);t.xp6(1),t.Q6J("ngForOf",e.steps),t.xp6(1),t.Q6J("ngTemplateOutlet",i)}}function gn(n,o){if(1&n&&(t.TgZ(0,"div"),t.YNc(1,ln,3,2,"ng-container",2),t.YNc(2,pn,3,2,"div",10),t.qZA()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("ngIf",!e.isSteps),t.xp6(1),t.Q6J("ngIf",e.isSteps)}}function dn(n,o){if(1&n&&(t.O4$(),t._UZ(0,"stop")),2&n){const e=o.$implicit;t.uIk("offset",e.offset)("stop-color",e.color)}}function mn(n,o){if(1&n&&(t.O4$(),t.TgZ(0,"defs"),t.TgZ(1,"linearGradient",24),t.YNc(2,dn,1,2,"stop",25),t.qZA(),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("id","gradient-"+e.gradientId),t.xp6(1),t.Q6J("ngForOf",e.circleGradient)}}function _n(n,o){if(1&n&&(t.O4$(),t._UZ(0,"path",26)),2&n){const e=o.$implicit,i=t.oxw(2);t.Q6J("ngStyle",e.strokePathStyle),t.uIk("d",i.pathString)("stroke-linecap",i.nzStrokeLinecap)("stroke",e.stroke)("stroke-width",i.nzPercent?i.strokeWidth:0)}}function hn(n,o){1&n&&t.O4$()}function fn(n,o){if(1&n&&(t.TgZ(0,"div",14),t.O4$(),t.TgZ(1,"svg",21),t.YNc(2,mn,3,2,"defs",2),t._UZ(3,"path",22),t.YNc(4,_n,1,5,"path",23),t.qZA(),t.YNc(5,hn,0,0,"ng-template",12),t.qZA()),2&n){const e=t.oxw(),i=t.MAs(1);t.Udp("width",e.nzWidth,"px")("height",e.nzWidth,"px")("font-size",.15*e.nzWidth+6,"px"),t.ekj("ant-progress-circle-gradient",e.isGradient),t.xp6(2),t.Q6J("ngIf",e.isGradient),t.xp6(1),t.Q6J("ngStyle",e.trailPathStyle),t.uIk("stroke-width",e.strokeWidth)("d",e.pathString),t.xp6(1),t.Q6J("ngForOf",e.progressCirclePath)("ngForTrackBy",e.trackByFn),t.xp6(1),t.Q6J("ngTemplateOutlet",i)}}const ne=n=>{let o=[];return Object.keys(n).forEach(e=>{const i=n[e],s=function zn(n){return+n.replace("%","")}(e);isNaN(s)||o.push({key:s,value:i})}),o=o.sort((e,i)=>e.key-i.key),o};let xn=0;const ie="progress",kn=new Map([["success","check"],["exception","close"]]),vn=new Map([["normal","#108ee9"],["exception","#ff5500"],["success","#87d068"]]),yn=n=>`${n}%`;let oe=(()=>{class n{constructor(e,i,s){this.cdr=e,this.nzConfigService=i,this.directionality=s,this._nzModuleName=ie,this.nzShowInfo=!0,this.nzWidth=132,this.nzStrokeColor=void 0,this.nzSize="default",this.nzPercent=0,this.nzStrokeWidth=void 0,this.nzGapDegree=void 0,this.nzType="line",this.nzGapPosition="top",this.nzStrokeLinecap="round",this.nzSteps=0,this.steps=[],this.lineGradient=null,this.isGradient=!1,this.isSteps=!1,this.gradientId=xn++,this.progressCirclePath=[],this.trailPathStyle=null,this.dir="ltr",this.trackByFn=a=>`${a}`,this.cachedStatus="normal",this.inferredStatus="normal",this.destroy$=new v.xQ}get formatter(){return this.nzFormat||yn}get status(){return this.nzStatus||this.inferredStatus}get strokeWidth(){return this.nzStrokeWidth||("line"===this.nzType&&"small"!==this.nzSize?8:6)}get isCircleStyle(){return"circle"===this.nzType||"dashboard"===this.nzType}ngOnChanges(e){const{nzSteps:i,nzGapPosition:s,nzStrokeLinecap:a,nzStrokeColor:c,nzGapDegree:p,nzType:d,nzStatus:f,nzPercent:r,nzSuccessPercent:N,nzStrokeWidth:I}=e;f&&(this.cachedStatus=this.nzStatus||this.cachedStatus),(r||N)&&(parseInt(this.nzPercent.toString(),10)>=100?((0,T.DX)(this.nzSuccessPercent)&&this.nzSuccessPercent>=100||void 0===this.nzSuccessPercent)&&(this.inferredStatus="success"):this.inferredStatus=this.cachedStatus),(f||r||N||c)&&this.updateIcon(),c&&this.setStrokeColor(),(s||a||p||d||r||c||c)&&this.getCirclePaths(),(r||i||I)&&(this.isSteps=this.nzSteps>0,this.isSteps&&this.getSteps())}ngOnInit(){var e;this.nzConfigService.getConfigChangeEventForComponent(ie).pipe((0,y.R)(this.destroy$)).subscribe(()=>{this.updateIcon(),this.setStrokeColor(),this.getCirclePaths()}),null===(e=this.directionality.change)||void 0===e||e.pipe((0,y.R)(this.destroy$)).subscribe(i=>{this.dir=i,this.cdr.detectChanges()}),this.dir=this.directionality.value}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}updateIcon(){const e=kn.get(this.status);this.icon=e?e+(this.isCircleStyle?"-o":"-circle-fill"):""}getSteps(){const e=Math.floor(this.nzSteps*(this.nzPercent/100)),i="small"===this.nzSize?2:14,s=[];for(let a=0;a{const Q=2===e.length&&0===I;return{stroke:this.isGradient&&!Q?`url(#gradient-${this.gradientId})`:null,strokePathStyle:{stroke:this.isGradient?null:Q?vn.get("success"):this.nzStrokeColor,transition:"stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s",strokeDasharray:`${(N||0)/100*(a-c)}px ${a}px`,strokeDashoffset:`-${c/2}px`}}}).reverse()}setStrokeColor(){const e=this.nzStrokeColor,i=this.isGradient=!!e&&"string"!=typeof e;i&&!this.isCircleStyle?this.lineGradient=(n=>{const{from:o="#1890ff",to:e="#1890ff",direction:i="to right"}=n,s=(0,_._T)(n,["from","to","direction"]);return 0!==Object.keys(s).length?`linear-gradient(${i}, ${ne(s).map(({key:c,value:p})=>`${p} ${c}%`).join(", ")})`:`linear-gradient(${i}, ${o}, ${e})`})(e):i&&this.isCircleStyle?this.circleGradient=(n=>ne(this.nzStrokeColor).map(({key:o,value:e})=>({offset:`${o}%`,color:e})))():(this.lineGradient=null,this.circleGradient=[])}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO),t.Y36(S.jY),t.Y36(b.Is,8))},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-progress"]],inputs:{nzShowInfo:"nzShowInfo",nzWidth:"nzWidth",nzStrokeColor:"nzStrokeColor",nzSize:"nzSize",nzFormat:"nzFormat",nzSuccessPercent:"nzSuccessPercent",nzPercent:"nzPercent",nzStrokeWidth:"nzStrokeWidth",nzGapDegree:"nzGapDegree",nzStatus:"nzStatus",nzType:"nzType",nzGapPosition:"nzGapPosition",nzStrokeLinecap:"nzStrokeLinecap",nzSteps:"nzSteps"},exportAs:["nzProgress"],features:[t.TTD],decls:5,vars:15,consts:[["progressInfoTemplate",""],[3,"ngClass"],[4,"ngIf"],["class","ant-progress-inner",3,"width","height","fontSize","ant-progress-circle-gradient",4,"ngIf"],["class","ant-progress-text",4,"ngIf"],[1,"ant-progress-text"],[4,"ngIf","ngIfElse"],["formatTemplate",""],["nz-icon","",3,"nzType"],[4,"nzStringTemplateOutlet","nzStringTemplateOutletContext"],["class","ant-progress-steps-outer",4,"ngIf"],["class","ant-progress-outer",4,"ngIf"],[3,"ngTemplateOutlet"],[1,"ant-progress-outer"],[1,"ant-progress-inner"],[1,"ant-progress-bg"],["class","ant-progress-success-bg",3,"width","border-radius","height",4,"ngIf"],[1,"ant-progress-success-bg"],[1,"ant-progress-steps-outer"],["class","ant-progress-steps-item",3,"ngStyle",4,"ngFor","ngForOf"],[1,"ant-progress-steps-item",3,"ngStyle"],["viewBox","0 0 100 100",1,"ant-progress-circle"],["stroke","#f3f3f3","fill-opacity","0",1,"ant-progress-circle-trail",3,"ngStyle"],["class","ant-progress-circle-path","fill-opacity","0",3,"ngStyle",4,"ngFor","ngForOf","ngForTrackBy"],["x1","100%","y1","0%","x2","0%","y2","0%",3,"id"],[4,"ngFor","ngForOf"],["fill-opacity","0",1,"ant-progress-circle-path",3,"ngStyle"]],template:function(e,i){1&e&&(t.YNc(0,on,1,1,"ng-template",null,0,t.W1O),t.TgZ(2,"div",1),t.YNc(3,gn,3,2,"div",2),t.YNc(4,fn,6,15,"div",3),t.qZA()),2&e&&(t.xp6(2),t.ekj("ant-progress-line","line"===i.nzType)("ant-progress-small","small"===i.nzSize)("ant-progress-show-info",i.nzShowInfo)("ant-progress-circle",i.isCircleStyle)("ant-progress-steps",i.isSteps)("ant-progress-rtl","rtl"===i.dir),t.Q6J("ngClass","ant-progress ant-progress-status-"+i.status),t.xp6(1),t.Q6J("ngIf","line"===i.nzType),t.xp6(1),t.Q6J("ngIf",i.isCircleStyle))},directives:[u.O5,M.Ls,E.f,u.mk,u.tP,u.sg,u.PC],encapsulation:2,changeDetection:0}),(0,_.gn)([(0,S.oS)()],n.prototype,"nzShowInfo",void 0),(0,_.gn)([(0,S.oS)()],n.prototype,"nzStrokeColor",void 0),(0,_.gn)([(0,S.oS)()],n.prototype,"nzSize",void 0),(0,_.gn)([(0,T.Rn)()],n.prototype,"nzSuccessPercent",void 0),(0,_.gn)([(0,T.Rn)()],n.prototype,"nzPercent",void 0),(0,_.gn)([(0,S.oS)(),(0,T.Rn)()],n.prototype,"nzStrokeWidth",void 0),(0,_.gn)([(0,S.oS)(),(0,T.Rn)()],n.prototype,"nzGapDegree",void 0),(0,_.gn)([(0,S.oS)()],n.prototype,"nzGapPosition",void 0),(0,_.gn)([(0,S.oS)()],n.prototype,"nzStrokeLinecap",void 0),(0,_.gn)([(0,T.Rn)()],n.prototype,"nzSteps",void 0),n})(),bn=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[b.vT,u.ez,M.PV,E.T]]}),n})();var J=l(592),se=l(925);let Sn=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[u.ez]]}),n})();const An=function(n){return{$implicit:n}};function Dn(n,o){if(1&n&&t.GkF(0,3),2&n){const e=t.oxw();t.Q6J("ngTemplateOutlet",e.nzValueTemplate)("ngTemplateOutletContext",t.VKq(2,An,e.nzValue))}}function Mn(n,o){if(1&n&&(t.TgZ(0,"span",6),t._uU(1),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Oqu(e.displayInt)}}function Zn(n,o){if(1&n&&(t.TgZ(0,"span",7),t._uU(1),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Oqu(e.displayDecimal)}}function On(n,o){if(1&n&&(t.ynx(0),t.YNc(1,Mn,2,1,"span",4),t.YNc(2,Zn,2,1,"span",5),t.BQk()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("ngIf",e.displayInt),t.xp6(1),t.Q6J("ngIf",e.displayDecimal)}}function wn(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw();t.xp6(1),t.Oqu(e.nzTitle)}}function Fn(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(2);t.xp6(1),t.Oqu(e.nzPrefix)}}function Nn(n,o){if(1&n&&(t.TgZ(0,"span",7),t.YNc(1,Fn,2,1,"ng-container",2),t.qZA()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.nzPrefix)}}function Pn(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(2);t.xp6(1),t.Oqu(e.nzSuffix)}}function En(n,o){if(1&n&&(t.TgZ(0,"span",8),t.YNc(1,Pn,2,1,"ng-container",2),t.qZA()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.nzSuffix)}}let In=(()=>{class n{constructor(e){this.locale_id=e,this.displayInt="",this.displayDecimal=""}ngOnChanges(){this.formatNumber()}formatNumber(){const e="number"==typeof this.nzValue?".":(0,u.dv)(this.locale_id,u.wE.Decimal),i=String(this.nzValue),[s,a]=i.split(e);this.displayInt=s,this.displayDecimal=a?`${e}${a}`:""}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.soG))},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-statistic-number"]],inputs:{nzValue:"nzValue",nzValueTemplate:"nzValueTemplate"},exportAs:["nzStatisticNumber"],features:[t.TTD],decls:3,vars:2,consts:[[1,"ant-statistic-content-value"],[3,"ngTemplateOutlet","ngTemplateOutletContext",4,"ngIf"],[4,"ngIf"],[3,"ngTemplateOutlet","ngTemplateOutletContext"],["class","ant-statistic-content-value-int",4,"ngIf"],["class","ant-statistic-content-value-decimal",4,"ngIf"],[1,"ant-statistic-content-value-int"],[1,"ant-statistic-content-value-decimal"]],template:function(e,i){1&e&&(t.TgZ(0,"span",0),t.YNc(1,Dn,1,4,"ng-container",1),t.YNc(2,On,3,2,"ng-container",2),t.qZA()),2&e&&(t.xp6(1),t.Q6J("ngIf",i.nzValueTemplate),t.xp6(1),t.Q6J("ngIf",!i.nzValueTemplate))},directives:[u.O5,u.tP],encapsulation:2,changeDetection:0}),n})(),ae=(()=>{class n{constructor(e,i){this.cdr=e,this.directionality=i,this.nzValueStyle={},this.dir="ltr",this.destroy$=new v.xQ}ngOnInit(){var e;null===(e=this.directionality.change)||void 0===e||e.pipe((0,y.R)(this.destroy$)).subscribe(i=>{this.dir=i,this.cdr.detectChanges()}),this.dir=this.directionality.value}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO),t.Y36(b.Is,8))},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-statistic"]],inputs:{nzPrefix:"nzPrefix",nzSuffix:"nzSuffix",nzTitle:"nzTitle",nzValue:"nzValue",nzValueStyle:"nzValueStyle",nzValueTemplate:"nzValueTemplate"},exportAs:["nzStatistic"],decls:7,vars:8,consts:[[1,"ant-statistic"],[1,"ant-statistic-title"],[4,"nzStringTemplateOutlet"],[1,"ant-statistic-content",3,"ngStyle"],["class","ant-statistic-content-prefix",4,"ngIf"],[3,"nzValue","nzValueTemplate"],["class","ant-statistic-content-suffix",4,"ngIf"],[1,"ant-statistic-content-prefix"],[1,"ant-statistic-content-suffix"]],template:function(e,i){1&e&&(t.TgZ(0,"div",0),t.TgZ(1,"div",1),t.YNc(2,wn,2,1,"ng-container",2),t.qZA(),t.TgZ(3,"div",3),t.YNc(4,Nn,2,1,"span",4),t._UZ(5,"nz-statistic-number",5),t.YNc(6,En,2,1,"span",6),t.qZA(),t.qZA()),2&e&&(t.ekj("ant-statistic-rtl","rtl"===i.dir),t.xp6(2),t.Q6J("nzStringTemplateOutlet",i.nzTitle),t.xp6(1),t.Q6J("ngStyle",i.nzValueStyle),t.xp6(1),t.Q6J("ngIf",i.nzPrefix),t.xp6(1),t.Q6J("nzValue",i.nzValue)("nzValueTemplate",i.nzValueTemplate),t.xp6(1),t.Q6J("ngIf",i.nzSuffix))},directives:[In,E.f,u.PC,u.O5],encapsulation:2,changeDetection:0}),n})(),Bn=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[b.vT,u.ez,se.ud,E.T,Sn]]}),n})();var re=l(6787),Jn=l(1059),nt=l(7545),Qn=l(7138),k=l(2994),Un=l(6947),Ft=l(4090);function qn(n,o){1&n&&t.Hsn(0)}const Rn=["*"];function Ln(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(3);t.xp6(1),t.Oqu(e.nzTitle)}}function Yn(n,o){if(1&n&&(t.TgZ(0,"div",6),t.YNc(1,Ln,2,1,"ng-container",7),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.nzTitle)}}function Gn(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(3);t.xp6(1),t.Oqu(e.nzExtra)}}function $n(n,o){if(1&n&&(t.TgZ(0,"div",8),t.YNc(1,Gn,2,1,"ng-container",7),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.nzExtra)}}function Vn(n,o){if(1&n&&(t.TgZ(0,"div",3),t.YNc(1,Yn,2,1,"div",4),t.YNc(2,$n,2,1,"div",5),t.qZA()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("ngIf",e.nzTitle),t.xp6(1),t.Q6J("ngIf",e.nzExtra)}}function jn(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(2).$implicit;t.xp6(1),t.hij(" ",e.title," ")}}function Hn(n,o){}function Wn(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"td",12),t.TgZ(2,"div",13),t.TgZ(3,"span",14),t.YNc(4,jn,2,1,"ng-container",7),t.qZA(),t.TgZ(5,"span",15),t.YNc(6,Hn,0,0,"ng-template",16),t.qZA(),t.qZA(),t.qZA(),t.BQk()),2&n){const e=t.oxw().$implicit,i=t.oxw(3);t.xp6(1),t.Q6J("colSpan",e.span),t.xp6(2),t.ekj("ant-descriptions-item-no-colon",!i.nzColon),t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.title),t.xp6(2),t.Q6J("ngTemplateOutlet",e.content)}}function Xn(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw(3).$implicit;t.xp6(1),t.hij(" ",e.title," ")}}function Kn(n,o){if(1&n&&(t.TgZ(0,"td",14),t.YNc(1,Xn,2,1,"ng-container",7),t.qZA()),2&n){const e=t.oxw(2).$implicit;t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.title)}}function ti(n,o){}function ei(n,o){if(1&n&&(t.ynx(0),t.YNc(1,Kn,2,1,"td",17),t.TgZ(2,"td",18),t.YNc(3,ti,0,0,"ng-template",16),t.qZA(),t.BQk()),2&n){const e=t.oxw().$implicit;t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.title),t.xp6(1),t.Q6J("colSpan",2*e.span-1),t.xp6(1),t.Q6J("ngTemplateOutlet",e.content)}}function ni(n,o){if(1&n&&(t.ynx(0),t.YNc(1,Wn,7,5,"ng-container",2),t.YNc(2,ei,4,3,"ng-container",2),t.BQk()),2&n){const e=t.oxw(3);t.xp6(1),t.Q6J("ngIf",!e.nzBordered),t.xp6(1),t.Q6J("ngIf",e.nzBordered)}}function ii(n,o){if(1&n&&(t.TgZ(0,"tr",10),t.YNc(1,ni,3,2,"ng-container",11),t.qZA()),2&n){const e=o.$implicit;t.xp6(1),t.Q6J("ngForOf",e)}}function oi(n,o){if(1&n&&(t.ynx(0),t.YNc(1,ii,2,1,"tr",9),t.BQk()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("ngForOf",e.itemMatrix)}}function si(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw().$implicit;t.xp6(1),t.hij(" ",e.title," ")}}function ai(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"td",12),t.TgZ(2,"div",13),t.TgZ(3,"span",14),t.YNc(4,si,2,1,"ng-container",7),t.qZA(),t.qZA(),t.qZA(),t.BQk()),2&n){const e=o.$implicit,i=t.oxw(4);t.xp6(1),t.Q6J("colSpan",e.span),t.xp6(2),t.ekj("ant-descriptions-item-no-colon",!i.nzColon),t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.title)}}function ri(n,o){}function li(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"td",12),t.TgZ(2,"div",13),t.TgZ(3,"span",15),t.YNc(4,ri,0,0,"ng-template",16),t.qZA(),t.qZA(),t.qZA(),t.BQk()),2&n){const e=o.$implicit;t.xp6(1),t.Q6J("colSpan",e.span),t.xp6(3),t.Q6J("ngTemplateOutlet",e.content)}}function ci(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"tr",10),t.YNc(2,ai,5,4,"ng-container",11),t.qZA(),t.TgZ(3,"tr",10),t.YNc(4,li,5,2,"ng-container",11),t.qZA(),t.BQk()),2&n){const e=o.$implicit;t.xp6(2),t.Q6J("ngForOf",e),t.xp6(2),t.Q6J("ngForOf",e)}}function ui(n,o){if(1&n&&(t.ynx(0),t.YNc(1,ci,5,2,"ng-container",11),t.BQk()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("ngForOf",e.itemMatrix)}}function pi(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.BQk()),2&n){const e=t.oxw().$implicit;t.xp6(1),t.hij(" ",e.title," ")}}function gi(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"td",19),t.YNc(2,pi,2,1,"ng-container",7),t.qZA(),t.BQk()),2&n){const e=o.$implicit;t.xp6(1),t.Q6J("colSpan",e.span),t.xp6(1),t.Q6J("nzStringTemplateOutlet",e.title)}}function di(n,o){}function mi(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"td",18),t.YNc(2,di,0,0,"ng-template",16),t.qZA(),t.BQk()),2&n){const e=o.$implicit;t.xp6(1),t.Q6J("colSpan",e.span),t.xp6(1),t.Q6J("ngTemplateOutlet",e.content)}}function _i(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"tr",10),t.YNc(2,gi,3,2,"ng-container",11),t.qZA(),t.TgZ(3,"tr",10),t.YNc(4,mi,3,2,"ng-container",11),t.qZA(),t.BQk()),2&n){const e=o.$implicit;t.xp6(2),t.Q6J("ngForOf",e),t.xp6(2),t.Q6J("ngForOf",e)}}function hi(n,o){if(1&n&&(t.ynx(0),t.YNc(1,_i,5,2,"ng-container",11),t.BQk()),2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("ngForOf",e.itemMatrix)}}function fi(n,o){if(1&n&&(t.ynx(0),t.YNc(1,ui,2,1,"ng-container",2),t.YNc(2,hi,2,1,"ng-container",2),t.BQk()),2&n){const e=t.oxw();t.xp6(1),t.Q6J("ngIf",!e.nzBordered),t.xp6(1),t.Q6J("ngIf",e.nzBordered)}}let Nt=(()=>{class n{constructor(){this.nzSpan=1,this.nzTitle="",this.inputChange$=new v.xQ}ngOnChanges(){this.inputChange$.next()}ngOnDestroy(){this.inputChange$.complete()}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-descriptions-item"]],viewQuery:function(e,i){if(1&e&&t.Gf(t.Rgc,7),2&e){let s;t.iGM(s=t.CRH())&&(i.content=s.first)}},inputs:{nzSpan:"nzSpan",nzTitle:"nzTitle"},exportAs:["nzDescriptionsItem"],features:[t.TTD],ngContentSelectors:Rn,decls:1,vars:0,template:function(e,i){1&e&&(t.F$t(),t.YNc(0,qn,1,0,"ng-template"))},encapsulation:2,changeDetection:0}),(0,_.gn)([(0,T.Rn)()],n.prototype,"nzSpan",void 0),n})();const Ci={xxl:3,xl:3,lg:3,md:3,sm:2,xs:1};let le=(()=>{class n{constructor(e,i,s,a){this.nzConfigService=e,this.cdr=i,this.breakpointService=s,this.directionality=a,this._nzModuleName="descriptions",this.nzBordered=!1,this.nzLayout="horizontal",this.nzColumn=Ci,this.nzSize="default",this.nzTitle="",this.nzColon=!0,this.itemMatrix=[],this.realColumn=3,this.dir="ltr",this.breakpoint=Ft.G_.md,this.destroy$=new v.xQ}ngOnInit(){var e;this.dir=this.directionality.value,null===(e=this.directionality.change)||void 0===e||e.pipe((0,y.R)(this.destroy$)).subscribe(i=>{this.dir=i})}ngOnChanges(e){e.nzColumn&&this.prepareMatrix()}ngAfterContentInit(){const e=this.items.changes.pipe((0,Jn.O)(this.items),(0,y.R)(this.destroy$));(0,re.T)(e,e.pipe((0,nt.w)(()=>(0,re.T)(...this.items.map(i=>i.inputChange$)).pipe((0,Qn.e)(16)))),this.breakpointService.subscribe(Ft.WV).pipe((0,k.b)(i=>this.breakpoint=i))).pipe((0,y.R)(this.destroy$)).subscribe(()=>{this.prepareMatrix(),this.cdr.markForCheck()})}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}prepareMatrix(){if(!this.items)return;let e=[],i=0;const s=this.realColumn=this.getColumn(),a=this.items.toArray(),c=a.length,p=[],d=()=>{p.push(e),e=[],i=0};for(let f=0;f=s?(i>s&&(0,Un.ZK)(`"nzColumn" is ${s} but we have row length ${i}`),e.push({title:N,content:I,span:s-(i-Q)}),d()):f===c-1?(e.push({title:N,content:I,span:s-(i-Q)}),d()):e.push({title:N,content:I,span:Q})}this.itemMatrix=p}getColumn(){return"number"!=typeof this.nzColumn?this.nzColumn[this.breakpoint]:this.nzColumn}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(S.jY),t.Y36(t.sBO),t.Y36(Ft.r3),t.Y36(b.Is,8))},n.\u0275cmp=t.Xpm({type:n,selectors:[["nz-descriptions"]],contentQueries:function(e,i,s){if(1&e&&t.Suo(s,Nt,4),2&e){let a;t.iGM(a=t.CRH())&&(i.items=a)}},hostAttrs:[1,"ant-descriptions"],hostVars:8,hostBindings:function(e,i){2&e&&t.ekj("ant-descriptions-bordered",i.nzBordered)("ant-descriptions-middle","middle"===i.nzSize)("ant-descriptions-small","small"===i.nzSize)("ant-descriptions-rtl","rtl"===i.dir)},inputs:{nzBordered:"nzBordered",nzLayout:"nzLayout",nzColumn:"nzColumn",nzSize:"nzSize",nzTitle:"nzTitle",nzExtra:"nzExtra",nzColon:"nzColon"},exportAs:["nzDescriptions"],features:[t.TTD],decls:6,vars:3,consts:[["class","ant-descriptions-header",4,"ngIf"],[1,"ant-descriptions-view"],[4,"ngIf"],[1,"ant-descriptions-header"],["class","ant-descriptions-title",4,"ngIf"],["class","ant-descriptions-extra",4,"ngIf"],[1,"ant-descriptions-title"],[4,"nzStringTemplateOutlet"],[1,"ant-descriptions-extra"],["class","ant-descriptions-row",4,"ngFor","ngForOf"],[1,"ant-descriptions-row"],[4,"ngFor","ngForOf"],[1,"ant-descriptions-item",3,"colSpan"],[1,"ant-descriptions-item-container"],[1,"ant-descriptions-item-label"],[1,"ant-descriptions-item-content"],[3,"ngTemplateOutlet"],["class","ant-descriptions-item-label",4,"nzStringTemplateOutlet"],[1,"ant-descriptions-item-content",3,"colSpan"],[1,"ant-descriptions-item-label",3,"colSpan"]],template:function(e,i){1&e&&(t.YNc(0,Vn,3,2,"div",0),t.TgZ(1,"div",1),t.TgZ(2,"table"),t.TgZ(3,"tbody"),t.YNc(4,oi,2,1,"ng-container",2),t.YNc(5,fi,3,2,"ng-container",2),t.qZA(),t.qZA(),t.qZA()),2&e&&(t.Q6J("ngIf",i.nzTitle||i.nzExtra),t.xp6(4),t.Q6J("ngIf","horizontal"===i.nzLayout),t.xp6(1),t.Q6J("ngIf","vertical"===i.nzLayout))},directives:[u.O5,E.f,u.sg,u.tP],encapsulation:2,changeDetection:0}),(0,_.gn)([(0,T.yF)(),(0,S.oS)()],n.prototype,"nzBordered",void 0),(0,_.gn)([(0,S.oS)()],n.prototype,"nzColumn",void 0),(0,_.gn)([(0,S.oS)()],n.prototype,"nzSize",void 0),(0,_.gn)([(0,S.oS)(),(0,T.yF)()],n.prototype,"nzColon",void 0),n})(),Ti=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[b.vT,u.ez,E.T,se.ud]]}),n})();var A=l(1086),xt=l(8896),kt=l(353),ce=l(6498),ue=l(3489);const pe={leading:!0,trailing:!1};class yi{constructor(o,e,i,s){this.duration=o,this.scheduler=e,this.leading=i,this.trailing=s}call(o,e){return e.subscribe(new bi(o,this.duration,this.scheduler,this.leading,this.trailing))}}class bi extends ue.L{constructor(o,e,i,s,a){super(o),this.duration=e,this.scheduler=i,this.leading=s,this.trailing=a,this._hasTrailingValue=!1,this._trailingValue=null}_next(o){this.throttled?this.trailing&&(this._trailingValue=o,this._hasTrailingValue=!0):(this.add(this.throttled=this.scheduler.schedule(Si,this.duration,{subscriber:this})),this.leading?this.destination.next(o):this.trailing&&(this._trailingValue=o,this._hasTrailingValue=!0))}_complete(){this._hasTrailingValue?(this.destination.next(this._trailingValue),this.destination.complete()):this.destination.complete()}clearThrottle(){const o=this.throttled;o&&(this.trailing&&this._hasTrailingValue&&(this.destination.next(this._trailingValue),this._trailingValue=null,this._hasTrailingValue=!1),o.unsubscribe(),this.remove(o),this.throttled=null)}}function Si(n){const{subscriber:o}=n;o.clearThrottle()}class Pt{constructor(o){this.changes=o}static of(o){return new Pt(o)}notEmpty(o){if(this.changes[o]){const e=this.changes[o].currentValue;if(null!=e)return(0,A.of)(e)}return xt.E}has(o){return this.changes[o]?(0,A.of)(this.changes[o].currentValue):xt.E}notFirst(o){return this.changes[o]&&!this.changes[o].isFirstChange()?(0,A.of)(this.changes[o].currentValue):xt.E}notFirstAndEmpty(o){if(this.changes[o]&&!this.changes[o].isFirstChange()){const e=this.changes[o].currentValue;if(null!=e)return(0,A.of)(e)}return xt.E}}const ge=new t.OlP("NGX_ECHARTS_CONFIG");let de=(()=>{class n{constructor(e,i,s){this.el=i,this.ngZone=s,this.autoResize=!0,this.loadingType="default",this.chartInit=new t.vpe,this.optionsError=new t.vpe,this.chartClick=this.createLazyEvent("click"),this.chartDblClick=this.createLazyEvent("dblclick"),this.chartMouseDown=this.createLazyEvent("mousedown"),this.chartMouseMove=this.createLazyEvent("mousemove"),this.chartMouseUp=this.createLazyEvent("mouseup"),this.chartMouseOver=this.createLazyEvent("mouseover"),this.chartMouseOut=this.createLazyEvent("mouseout"),this.chartGlobalOut=this.createLazyEvent("globalout"),this.chartContextMenu=this.createLazyEvent("contextmenu"),this.chartLegendSelectChanged=this.createLazyEvent("legendselectchanged"),this.chartLegendSelected=this.createLazyEvent("legendselected"),this.chartLegendUnselected=this.createLazyEvent("legendunselected"),this.chartLegendScroll=this.createLazyEvent("legendscroll"),this.chartDataZoom=this.createLazyEvent("datazoom"),this.chartDataRangeSelected=this.createLazyEvent("datarangeselected"),this.chartTimelineChanged=this.createLazyEvent("timelinechanged"),this.chartTimelinePlayChanged=this.createLazyEvent("timelineplaychanged"),this.chartRestore=this.createLazyEvent("restore"),this.chartDataViewChanged=this.createLazyEvent("dataviewchanged"),this.chartMagicTypeChanged=this.createLazyEvent("magictypechanged"),this.chartPieSelectChanged=this.createLazyEvent("pieselectchanged"),this.chartPieSelected=this.createLazyEvent("pieselected"),this.chartPieUnselected=this.createLazyEvent("pieunselected"),this.chartMapSelectChanged=this.createLazyEvent("mapselectchanged"),this.chartMapSelected=this.createLazyEvent("mapselected"),this.chartMapUnselected=this.createLazyEvent("mapunselected"),this.chartAxisAreaSelected=this.createLazyEvent("axisareaselected"),this.chartFocusNodeAdjacency=this.createLazyEvent("focusnodeadjacency"),this.chartUnfocusNodeAdjacency=this.createLazyEvent("unfocusnodeadjacency"),this.chartBrush=this.createLazyEvent("brush"),this.chartBrushEnd=this.createLazyEvent("brushend"),this.chartBrushSelected=this.createLazyEvent("brushselected"),this.chartRendered=this.createLazyEvent("rendered"),this.chartFinished=this.createLazyEvent("finished"),this.animationFrameID=null,this.resize$=new v.xQ,this.echarts=e.echarts}ngOnChanges(e){const i=Pt.of(e);i.notFirstAndEmpty("options").subscribe(s=>this.onOptionsChange(s)),i.notFirstAndEmpty("merge").subscribe(s=>this.setOption(s)),i.has("loading").subscribe(s=>this.toggleLoading(!!s)),i.notFirst("theme").subscribe(()=>this.refreshChart())}ngOnInit(){if(!window.ResizeObserver)throw new Error("please install a polyfill for ResizeObserver");this.resizeSub=this.resize$.pipe(function vi(n,o=kt.P,e=pe){return i=>i.lift(new yi(n,o,e.leading,e.trailing))}(100,kt.z,{leading:!1,trailing:!0})).subscribe(()=>this.resize()),this.autoResize&&(this.resizeOb=this.ngZone.runOutsideAngular(()=>new window.ResizeObserver(()=>{this.animationFrameID=window.requestAnimationFrame(()=>this.resize$.next())})),this.resizeOb.observe(this.el.nativeElement))}ngOnDestroy(){window.clearTimeout(this.initChartTimer),this.resizeSub&&this.resizeSub.unsubscribe(),this.animationFrameID&&window.cancelAnimationFrame(this.animationFrameID),this.resizeOb&&this.resizeOb.unobserve(this.el.nativeElement),this.dispose()}ngAfterViewInit(){this.initChartTimer=window.setTimeout(()=>this.initChart())}dispose(){this.chart&&(this.chart.isDisposed()||this.chart.dispose(),this.chart=null)}resize(){this.chart&&this.chart.resize()}toggleLoading(e){this.chart&&(e?this.chart.showLoading(this.loadingType,this.loadingOpts):this.chart.hideLoading())}setOption(e,i){if(this.chart)try{this.chart.setOption(e,i)}catch(s){console.error(s),this.optionsError.emit(s)}}refreshChart(){return(0,_.mG)(this,void 0,void 0,function*(){this.dispose(),yield this.initChart()})}createChart(){const e=this.el.nativeElement;if(window&&window.getComputedStyle){const i=window.getComputedStyle(e,null).getPropertyValue("height");(!i||"0px"===i)&&(!e.style.height||"0px"===e.style.height)&&(e.style.height="400px")}return this.ngZone.runOutsideAngular(()=>("function"==typeof this.echarts?this.echarts:()=>Promise.resolve(this.echarts))().then(({init:s})=>s(e,this.theme,this.initOpts)))}initChart(){return(0,_.mG)(this,void 0,void 0,function*(){yield this.onOptionsChange(this.options),this.merge&&this.chart&&this.setOption(this.merge)})}onOptionsChange(e){return(0,_.mG)(this,void 0,void 0,function*(){!e||(this.chart||(this.chart=yield this.createChart(),this.chartInit.emit(this.chart)),this.setOption(this.options,!0))})}createLazyEvent(e){return this.chartInit.pipe((0,nt.w)(i=>new ce.y(s=>(i.on(e,a=>this.ngZone.run(()=>s.next(a))),()=>{this.chart&&(this.chart.isDisposed()||i.off(e))}))))}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(ge),t.Y36(t.SBq),t.Y36(t.R0b))},n.\u0275dir=t.lG2({type:n,selectors:[["echarts"],["","echarts",""]],inputs:{options:"options",theme:"theme",loading:"loading",initOpts:"initOpts",merge:"merge",autoResize:"autoResize",loadingType:"loadingType",loadingOpts:"loadingOpts"},outputs:{chartInit:"chartInit",optionsError:"optionsError",chartClick:"chartClick",chartDblClick:"chartDblClick",chartMouseDown:"chartMouseDown",chartMouseMove:"chartMouseMove",chartMouseUp:"chartMouseUp",chartMouseOver:"chartMouseOver",chartMouseOut:"chartMouseOut",chartGlobalOut:"chartGlobalOut",chartContextMenu:"chartContextMenu",chartLegendSelectChanged:"chartLegendSelectChanged",chartLegendSelected:"chartLegendSelected",chartLegendUnselected:"chartLegendUnselected",chartLegendScroll:"chartLegendScroll",chartDataZoom:"chartDataZoom",chartDataRangeSelected:"chartDataRangeSelected",chartTimelineChanged:"chartTimelineChanged",chartTimelinePlayChanged:"chartTimelinePlayChanged",chartRestore:"chartRestore",chartDataViewChanged:"chartDataViewChanged",chartMagicTypeChanged:"chartMagicTypeChanged",chartPieSelectChanged:"chartPieSelectChanged",chartPieSelected:"chartPieSelected",chartPieUnselected:"chartPieUnselected",chartMapSelectChanged:"chartMapSelectChanged",chartMapSelected:"chartMapSelected",chartMapUnselected:"chartMapUnselected",chartAxisAreaSelected:"chartAxisAreaSelected",chartFocusNodeAdjacency:"chartFocusNodeAdjacency",chartUnfocusNodeAdjacency:"chartUnfocusNodeAdjacency",chartBrush:"chartBrush",chartBrushEnd:"chartBrushEnd",chartBrushSelected:"chartBrushSelected",chartRendered:"chartRendered",chartFinished:"chartFinished"},exportAs:["echarts"],features:[t.TTD]}),n})(),Ai=(()=>{class n{static forRoot(e){return{ngModule:n,providers:[{provide:ge,useValue:e}]}}static forChild(){return{ngModule:n}}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[]]}),n})();var Di=l(4466),ct=l(2302),Mi=l(4241);function vt(n=0,o=kt.P){return(!(0,Mi.k)(n)||n<0)&&(n=0),(!o||"function"!=typeof o.schedule)&&(o=kt.P),new ce.y(e=>(e.add(o.schedule(Zi,n,{subscriber:e,counter:0,period:n})),e))}function Zi(n){const{subscriber:o,counter:e,period:i}=n;o.next(e),this.schedule({subscriber:o,counter:e+1,period:i},i)}var Oi=l(3009),wi=l(6688),yt=l(5430),Et=l(1177);function It(...n){const o=n[n.length-1];return"function"==typeof o&&n.pop(),(0,Oi.n)(n,void 0).lift(new Fi(o))}class Fi{constructor(o){this.resultSelector=o}call(o,e){return e.subscribe(new Ni(o,this.resultSelector))}}class Ni extends ue.L{constructor(o,e,i=Object.create(null)){super(o),this.resultSelector=e,this.iterators=[],this.active=0,this.resultSelector="function"==typeof e?e:void 0}_next(o){const e=this.iterators;(0,wi.k)(o)?e.push(new Ei(o)):e.push("function"==typeof o[yt.hZ]?new Pi(o[yt.hZ]()):new Ii(this.destination,this,o))}_complete(){const o=this.iterators,e=o.length;if(this.unsubscribe(),0!==e){this.active=e;for(let i=0;ithis.index}hasCompleted(){return this.array.length===this.index}}class Ii extends Et.Ds{constructor(o,e,i){super(o),this.parent=e,this.observable=i,this.stillUnsubscribed=!0,this.buffer=[],this.isComplete=!1}[yt.hZ](){return this}next(){const o=this.buffer;return 0===o.length&&this.isComplete?{value:null,done:!0}:{value:o.shift(),done:!1}}hasValue(){return this.buffer.length>0}hasCompleted(){return 0===this.buffer.length&&this.isComplete}notifyComplete(){this.buffer.length>0?(this.isComplete=!0,this.parent.notifyInactive()):this.destination.complete()}notifyNext(o){this.buffer.push(o),this.parent.checkIterators()}subscribe(){return(0,Et.ft)(this.observable,new Et.IY(this))}}var Bt=l(534),it=l(7221),bt=l(7106),Jt=l(5278),Bi=l(2340),Z=(()=>{return(n=Z||(Z={})).ALL="all",n.PREPARING="preparing",n.LIVING="living",n.ROUNDING="rounding",n.MONITOR_ENABLED="monitor_enabled",n.MONITOR_DISABLED="monitor_disabled",n.RECORDER_ENABLED="recorder_enabled",n.RECORDER_DISABLED="recorder_disabled",n.STOPPED="stopped",n.WAITTING="waitting",n.RECORDING="recording",n.REMUXING="remuxing",n.INJECTING="injecting",Z;var n})(),O=(()=>{return(n=O||(O={})).STOPPED="stopped",n.WAITING="waiting",n.RECORDING="recording",n.REMUXING="remuxing",n.INJECTING="injecting",O;var n})(),ut=(()=>{return(n=ut||(ut={})).WAITING="waiting",n.REMUXING="remuxing",n.INJECTING="injecting",ut;var n})(),x=(()=>{return(n=x||(x={})).RECORDING="recording",n.REMUXING="remuxing",n.INJECTING="injecting",n.COMPLETED="completed",n.MISSING="missing",n.BROKEN="broken",x;var n})(),Ji=l(520);const C=Bi.N.apiUrl;let St=(()=>{class n{constructor(e){this.http=e}getAllTaskData(e=Z.ALL){return this.http.get(C+"/api/v1/tasks/data",{params:{select:e}})}getTaskData(e){return this.http.get(C+`/api/v1/tasks/${e}/data`)}getVideoFileDetails(e){return this.http.get(C+`/api/v1/tasks/${e}/videos`)}getDanmakuFileDetails(e){return this.http.get(C+`/api/v1/tasks/${e}/danmakus`)}getTaskParam(e){return this.http.get(C+`/api/v1/tasks/${e}/param`)}getMetadata(e){return this.http.get(C+`/api/v1/tasks/${e}/metadata`)}getStreamProfile(e){return this.http.get(C+`/api/v1/tasks/${e}/profile`)}updateAllTaskInfos(){return this.http.post(C+"/api/v1/tasks/info",null)}updateTaskInfo(e){return this.http.post(C+`/api/v1/tasks/${e}/info`,null)}addTask(e){return this.http.post(C+`/api/v1/tasks/${e}`,null)}removeTask(e){return this.http.delete(C+`/api/v1/tasks/${e}`)}removeAllTasks(){return this.http.delete(C+"/api/v1/tasks")}startTask(e){return this.http.post(C+`/api/v1/tasks/${e}/start`,null)}startAllTasks(){return this.http.post(C+"/api/v1/tasks/start",null)}stopTask(e,i=!1,s=!1){return this.http.post(C+`/api/v1/tasks/${e}/stop`,{force:i,background:s})}stopAllTasks(e=!1,i=!1){return this.http.post(C+"/api/v1/tasks/stop",{force:e,background:i})}enableTaskMonitor(e){return this.http.post(C+`/api/v1/tasks/${e}/monitor/enable`,null)}enableAllMonitors(){return this.http.post(C+"/api/v1/tasks/monitor/enable",null)}disableTaskMonitor(e,i=!1){return this.http.post(C+`/api/v1/tasks/${e}/monitor/disable`,{background:i})}disableAllMonitors(e=!1){return this.http.post(C+"/api/v1/tasks/monitor/disable",{background:e})}enableTaskRecorder(e){return this.http.post(C+`/api/v1/tasks/${e}/recorder/enable`,null)}enableAllRecorders(){return this.http.post(C+"/api/v1/tasks/recorder/enable",null)}disableTaskRecorder(e,i=!1,s=!1){return this.http.post(C+`/api/v1/tasks/${e}/recorder/disable`,{force:i,background:s})}disableAllRecorders(e=!1,i=!1){return this.http.post(C+"/api/v1/tasks/recorder/disable",{force:e,background:i})}cutStream(e){return this.http.post(C+`/api/v1/tasks/${e}/cut`,null)}}return n.\u0275fac=function(e){return new(e||n)(t.LFG(Ji.eN))},n.\u0275prov=t.Yz7({token:n,factory:n.\u0275fac,providedIn:"root"}),n})();var Qi=l(7512),Ui=l(5545);let qi=(()=>{class n{constructor(){this.loading=!0}ngOnInit(){}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-user-info-detail"]],inputs:{loading:"loading",userInfo:"userInfo"},decls:12,vars:6,consts:[["nzTitle","\u4e3b\u64ad\u4fe1\u606f",3,"nzLoading"],["nzTitle",""],["nzTitle","\u6635\u79f0"],["nzTitle","\u6027\u522b"],["nzTitle","UID"],["nzTitle","\u7b49\u7ea7"],["nzTitle","\u7b7e\u540d"]],template:function(e,i){1&e&&(t.TgZ(0,"nz-card",0),t.TgZ(1,"nz-descriptions",1),t.TgZ(2,"nz-descriptions-item",2),t._uU(3),t.qZA(),t.TgZ(4,"nz-descriptions-item",3),t._uU(5),t.qZA(),t.TgZ(6,"nz-descriptions-item",4),t._uU(7),t.qZA(),t.TgZ(8,"nz-descriptions-item",5),t._uU(9),t.qZA(),t.TgZ(10,"nz-descriptions-item",6),t._uU(11),t.qZA(),t.qZA(),t.qZA()),2&e&&(t.Q6J("nzLoading",i.loading),t.xp6(3),t.Oqu(i.userInfo.name),t.xp6(2),t.Oqu(i.userInfo.gender),t.xp6(2),t.Oqu(i.userInfo.uid),t.xp6(2),t.Oqu(i.userInfo.level),t.xp6(2),t.hij(" ",i.userInfo.sign," "))},directives:[D.bd,le,Nt],styles:[""],changeDetection:0}),n})();function Ri(n,o){if(1&n&&(t.TgZ(0,"span",18),t._uU(1),t.qZA()),2&n){const e=t.oxw();t.xp6(1),t.hij("",e.roomInfo.short_room_id," ")}}function Li(n,o){1&n&&(t.ynx(0),t._uU(1,"\u95f2\u7f6e"),t.BQk())}function Yi(n,o){1&n&&(t.ynx(0),t._uU(1,"\u76f4\u64ad\u4e2d"),t.BQk())}function Gi(n,o){1&n&&(t.ynx(0),t._uU(1,"\u8f6e\u64ad\u4e2d"),t.BQk())}function $i(n,o){if(1&n&&(t.ynx(0),t._uU(1),t.ALo(2,"date"),t.BQk()),2&n){const e=t.oxw();t.xp6(1),t.hij(" ",t.Dn7(2,1,1e3*e.roomInfo.live_start_time,"YYYY-MM-dd HH:mm:ss","+8")," ")}}function Vi(n,o){if(1&n&&(t.TgZ(0,"nz-tag"),t._uU(1),t.qZA()),2&n){const e=o.$implicit;t.xp6(1),t.hij(" ",e," ")}}function ji(n,o){if(1&n&&(t.TgZ(0,"p"),t._uU(1),t.qZA()),2&n){const e=o.$implicit;t.xp6(1),t.Oqu(e)}}let Hi=(()=>{class n{constructor(){this.loading=!0}ngOnInit(){}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-room-info-detail"]],inputs:{loading:"loading",roomInfo:"roomInfo"},decls:24,vars:13,consts:[["nzTitle","\u76f4\u64ad\u95f4\u4fe1\u606f",3,"nzLoading"],["nzTitle",""],["nzTitle","\u6807\u9898"],["nzTitle","\u5206\u533a"],["nzTitle","\u623f\u95f4\u53f7"],[1,"room-id-wrapper"],["class","short-room-id",4,"ngIf"],[1,"real-room-id"],["nzTitle","\u72b6\u6001"],[3,"ngSwitch"],[4,"ngSwitchCase"],["nzTitle","\u5f00\u64ad\u65f6\u95f4"],[4,"ngIf"],["nzTitle","\u6807\u7b7e"],[1,"tags"],[4,"ngFor","ngForOf"],["nzTitle","\u7b80\u4ecb"],[1,"introduction"],[1,"short-room-id"]],template:function(e,i){1&e&&(t.TgZ(0,"nz-card",0),t.TgZ(1,"nz-descriptions",1),t.TgZ(2,"nz-descriptions-item",2),t._uU(3),t.qZA(),t.TgZ(4,"nz-descriptions-item",3),t._uU(5),t.qZA(),t.TgZ(6,"nz-descriptions-item",4),t.TgZ(7,"span",5),t.YNc(8,Ri,2,1,"span",6),t.TgZ(9,"span",7),t._uU(10),t.qZA(),t.qZA(),t.qZA(),t.TgZ(11,"nz-descriptions-item",8),t.ynx(12,9),t.YNc(13,Li,2,0,"ng-container",10),t.YNc(14,Yi,2,0,"ng-container",10),t.YNc(15,Gi,2,0,"ng-container",10),t.BQk(),t.qZA(),t.TgZ(16,"nz-descriptions-item",11),t.YNc(17,$i,3,5,"ng-container",12),t.qZA(),t.TgZ(18,"nz-descriptions-item",13),t.TgZ(19,"div",14),t.YNc(20,Vi,2,1,"nz-tag",15),t.qZA(),t.qZA(),t.TgZ(21,"nz-descriptions-item",16),t.TgZ(22,"div",17),t.YNc(23,ji,2,1,"p",15),t.qZA(),t.qZA(),t.qZA(),t.qZA()),2&e&&(t.Q6J("nzLoading",i.loading),t.xp6(3),t.Oqu(i.roomInfo.title),t.xp6(2),t.AsE(" ",i.roomInfo.parent_area_name," - ",i.roomInfo.area_name," "),t.xp6(3),t.Q6J("ngIf",i.roomInfo.short_room_id),t.xp6(2),t.hij(" ",i.roomInfo.room_id," "),t.xp6(2),t.Q6J("ngSwitch",i.roomInfo.live_status),t.xp6(1),t.Q6J("ngSwitchCase",0),t.xp6(1),t.Q6J("ngSwitchCase",1),t.xp6(1),t.Q6J("ngSwitchCase",2),t.xp6(2),t.Q6J("ngIf",0!==i.roomInfo.live_start_time),t.xp6(3),t.Q6J("ngForOf",i.roomInfo.tags.split(",")),t.xp6(3),t.Q6J("ngForOf",i.roomInfo.description.split("\n")))},directives:[D.bd,le,Nt,u.O5,u.RF,u.n9,u.sg,st],pipes:[u.uU],styles:['.room-id-wrapper[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap}.room-id-wrapper[_ngcontent-%COMP%] .short-room-id[_ngcontent-%COMP%]:after{display:inline-block;width:1em;content:","}.tags[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap;row-gap:.5em}.introduction[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{margin:0;padding:0}'],changeDetection:0}),n})();var j=l(2134);let me=(()=>{class n{transform(e){if(e<0)throw RangeError("the argument totalSeconds must be greater than or equal to 0");const i=Math.floor(e/3600),s=Math.floor(e/60%60),a=Math.floor(e%60);let c="";return i>0&&(c+=i+":"),c+=s<10?"0"+s:s,c+=":",c+=a<10?"0"+a:a,c}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"duration",type:n,pure:!0}),n})(),At=(()=>{class n{transform(e,i){if("string"==typeof e)e=parseFloat(e);else if("number"!=typeof e||isNaN(e))return"N/A";return(i=Object.assign({bitrate:!1,precision:3,spacer:" "},i)).bitrate?(0,j.AX)(e,i.spacer,i.precision):(0,j.N4)(e,i.spacer,i.precision)}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"datarate",type:n,pure:!0}),n})();var Wi=l(855);let Dt=(()=>{class n{transform(e,i){return Wi(e,i)}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"filesize",type:n,pure:!0}),n})();const Xi={2e4:"4K",1e4:"\u539f\u753b",401:"\u84dd\u5149(\u675c\u6bd4)",400:"\u84dd\u5149",250:"\u8d85\u6e05",150:"\u9ad8\u6e05",80:"\u6d41\u7545"};let Qt=(()=>{class n{transform(e){return Xi[e]}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"quality",type:n,pure:!0}),n})();function Ki(n,o){if(1&n&&(t._uU(0),t.ALo(1,"duration")),2&n){const e=t.oxw();t.Oqu(t.lcZ(1,1,e.taskStatus.rec_elapsed))}}function to(n,o){if(1&n&&(t._uU(0),t.ALo(1,"datarate")),2&n){const e=t.oxw();t.Oqu(t.lcZ(1,1,e.taskStatus.rec_rate))}}const eo=function(){return{spacer:" "}};function no(n,o){if(1&n&&(t._uU(0),t.ALo(1,"filesize")),2&n){const e=t.oxw();t.Oqu(t.xi3(1,1,e.taskStatus.rec_total,t.DdM(4,eo)))}}function io(n,o){if(1&n&&(t._uU(0),t.ALo(1,"quality")),2&n){const e=t.oxw();t.Oqu(e.taskStatus.real_quality_number?t.lcZ(1,1,e.taskStatus.real_quality_number)+" ("+e.taskStatus.real_quality_number+")":"")}}let oo=(()=>{class n{constructor(e){this.changeDetector=e,this.loading=!0,this.initialChartOptions={},this.updatedChartOptions={},this.chartData=[],this.initChartOptions()}ngOnChanges(){this.taskStatus.running_status===O.RECORDING&&this.updateChartOptions()}initChartOptions(){const e=Date.now();for(let i=59;i>=0;i--){const s=new Date(e-1e3*i);this.chartData.push({name:s.toLocaleString("zh-CN",{hour12:!1}),value:[s.toISOString(),0]})}this.initialChartOptions={title:{},tooltip:{trigger:"axis",formatter:i=>{const s=i[0];return`\n
\n
\n ${new Date(s.name).toLocaleTimeString("zh-CN",{hour12:!1})}\n
\n
${(0,j.N4)(s.value[1])}
\n
\n `},axisPointer:{animation:!1}},xAxis:{type:"time",name:"\u65f6\u95f4",min:"dataMin",max:"dataMax",splitLine:{show:!0}},yAxis:{type:"value",name:"\u5f55\u5236\u901f\u5ea6",splitLine:{show:!0},axisLabel:{formatter:i=>(0,j.N4)(i)}},series:[{name:"\u5f55\u5236\u901f\u5ea6",type:"line",showSymbol:!1,smooth:!0,lineStyle:{width:1},areaStyle:{opacity:.2},data:this.chartData}]}}updateChartOptions(){const e=new Date;this.chartData.push({name:e.toLocaleString("zh-CN",{hour12:!1}),value:[e.toISOString(),this.taskStatus.rec_rate]}),this.chartData.shift(),this.updatedChartOptions={series:[{data:this.chartData}]},this.changeDetector.markForCheck()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-recording-detail"]],inputs:{loading:"loading",taskStatus:"taskStatus"},features:[t.TTD],decls:17,vars:17,consts:[["nzTitle","\u5f55\u5236\u8be6\u60c5",3,"nzLoading"],[1,"statistics"],[3,"nzTitle","nzValueTemplate"],["recordingElapsed",""],["recordingRate",""],["recordedTotal",""],["recordingQuality",""],[3,"nzTitle","nzValue"],["echarts","",1,"rec-rate-chart",3,"loading","options","merge"]],template:function(e,i){if(1&e&&(t.TgZ(0,"nz-card",0),t.TgZ(1,"div",1),t._UZ(2,"nz-statistic",2),t.YNc(3,Ki,2,3,"ng-template",null,3,t.W1O),t._UZ(5,"nz-statistic",2),t.YNc(6,to,2,3,"ng-template",null,4,t.W1O),t._UZ(8,"nz-statistic",2),t.YNc(9,no,2,5,"ng-template",null,5,t.W1O),t._UZ(11,"nz-statistic",2),t.YNc(12,io,2,3,"ng-template",null,6,t.W1O),t._UZ(14,"nz-statistic",7),t.ALo(15,"number"),t.qZA(),t._UZ(16,"div",8),t.qZA()),2&e){const s=t.MAs(4),a=t.MAs(7),c=t.MAs(10),p=t.MAs(13);t.Q6J("nzLoading",i.loading),t.xp6(2),t.Q6J("nzTitle","\u5f55\u5236\u7528\u65f6")("nzValueTemplate",s),t.xp6(3),t.Q6J("nzTitle","\u5f55\u5236\u901f\u5ea6")("nzValueTemplate",a),t.xp6(3),t.Q6J("nzTitle","\u5f55\u5236\u603b\u8ba1")("nzValueTemplate",c),t.xp6(3),t.Q6J("nzTitle","\u5f55\u5236\u753b\u8d28")("nzValueTemplate",p),t.xp6(3),t.Q6J("nzTitle","\u5f39\u5e55\u603b\u8ba1")("nzValue",t.xi3(15,14,i.taskStatus.danmu_total,"1.0-2")),t.xp6(2),t.Q6J("loading",i.loading)("options",i.initialChartOptions)("merge",i.updatedChartOptions)}},directives:[D.bd,ae,de],pipes:[me,At,Dt,Qt,u.JJ],styles:[".statistics[_ngcontent-%COMP%]{--grid-width: 200px;display:grid;grid-template-columns:repeat(auto-fill,var(--grid-width));grid-gap:1em;gap:1em;justify-content:center;margin:0 auto}@media screen and (max-width: 1024px){.statistics[_ngcontent-%COMP%]{--grid-width: 180px}}@media screen and (max-width: 720px){.statistics[_ngcontent-%COMP%]{--grid-width: 160px}}@media screen and (max-width: 680px){.statistics[_ngcontent-%COMP%]{--grid-width: 140px}}@media screen and (max-width: 480px){.statistics[_ngcontent-%COMP%]{--grid-width: 120px}}.rec-rate-chart[_ngcontent-%COMP%]{width:100%;height:300px;margin:0}"],changeDetection:0}),n})();function so(n,o){if(1&n&&t._uU(0),2&n){const e=t.oxw();t.Oqu(e.taskStatus.stream_host)}}function ao(n,o){if(1&n&&t._uU(0),2&n){const e=t.oxw();t.Oqu(e.taskStatus.real_stream_format)}}const ro=function(){return{bitrate:!0}};function lo(n,o){if(1&n&&(t._uU(0),t.ALo(1,"datarate")),2&n){const e=t.oxw();t.Oqu(t.xi3(1,1,8*e.taskStatus.dl_rate,t.DdM(4,ro)))}}const co=function(){return{spacer:" "}};function uo(n,o){if(1&n&&(t._uU(0),t.ALo(1,"filesize")),2&n){const e=t.oxw();t.Oqu(t.xi3(1,1,e.taskStatus.dl_total,t.DdM(4,co)))}}let po=(()=>{class n{constructor(e){this.changeDetector=e,this.loading=!0,this.initialChartOptions={},this.updatedChartOptions={},this.chartData=[],this.initChartOptions()}ngOnChanges(){this.taskStatus.running_status===O.RECORDING&&this.updateChartOptions()}initChartOptions(){const e=Date.now();for(let i=59;i>=0;i--){const s=new Date(e-1e3*i);this.chartData.push({name:s.toLocaleString("zh-CN",{hour12:!1}),value:[s.toISOString(),0]})}this.initialChartOptions={title:{},tooltip:{trigger:"axis",formatter:i=>{const s=i[0];return`\n
\n
\n ${new Date(s.name).toLocaleTimeString("zh-CN",{hour12:!1})}\n
\n
${(0,j.AX)(s.value[1])}
\n
\n `},axisPointer:{animation:!1}},xAxis:{type:"time",name:"\u65f6\u95f4",min:"dataMin",max:"dataMax",splitLine:{show:!0}},yAxis:{type:"value",name:"\u4e0b\u8f7d\u901f\u5ea6",splitLine:{show:!0},axisLabel:{formatter:function(i){return(0,j.AX)(i)}}},series:[{name:"\u4e0b\u8f7d\u901f\u5ea6",type:"line",showSymbol:!1,smooth:!0,lineStyle:{width:1},areaStyle:{opacity:.2},data:this.chartData}]}}updateChartOptions(){const e=new Date;this.chartData.push({name:e.toLocaleString("zh-CN",{hour12:!1}),value:[e.toISOString(),8*this.taskStatus.dl_rate]}),this.chartData.shift(),this.updatedChartOptions={series:[{data:this.chartData}]},this.changeDetector.markForCheck()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-network-detail"]],inputs:{loading:"loading",taskStatus:"taskStatus"},features:[t.TTD],decls:15,vars:12,consts:[["nzTitle","\u7f51\u7edc\u8be6\u60c5",3,"nzLoading"],[1,"statistics"],[1,"stream-host",3,"nzTitle","nzValueTemplate"],["streamHost",""],[3,"nzTitle","nzValueTemplate"],["realStreamFormat",""],["downloadRate",""],["downloadTotal",""],["echarts","",1,"dl-rate-chart",3,"loading","options","merge"]],template:function(e,i){if(1&e&&(t.TgZ(0,"nz-card",0),t.TgZ(1,"div",1),t._UZ(2,"nz-statistic",2),t.YNc(3,so,1,1,"ng-template",null,3,t.W1O),t._UZ(5,"nz-statistic",4),t.YNc(6,ao,1,1,"ng-template",null,5,t.W1O),t._UZ(8,"nz-statistic",4),t.YNc(9,lo,2,5,"ng-template",null,6,t.W1O),t._UZ(11,"nz-statistic",4),t.YNc(12,uo,2,5,"ng-template",null,7,t.W1O),t.qZA(),t._UZ(14,"div",8),t.qZA()),2&e){const s=t.MAs(4),a=t.MAs(7),c=t.MAs(10),p=t.MAs(13);t.Q6J("nzLoading",i.loading),t.xp6(2),t.Q6J("nzTitle","\u6d41\u4e3b\u673a")("nzValueTemplate",s),t.xp6(3),t.Q6J("nzTitle","\u6d41\u683c\u5f0f")("nzValueTemplate",a),t.xp6(3),t.Q6J("nzTitle","\u4e0b\u8f7d\u901f\u5ea6")("nzValueTemplate",c),t.xp6(3),t.Q6J("nzTitle","\u4e0b\u8f7d\u603b\u8ba1")("nzValueTemplate",p),t.xp6(3),t.Q6J("loading",i.loading)("options",i.initialChartOptions)("merge",i.updatedChartOptions)}},directives:[D.bd,ae,de],pipes:[At,Dt],styles:[".statistics[_ngcontent-%COMP%]{--grid-width: 200px;display:grid;grid-template-columns:repeat(auto-fill,var(--grid-width));grid-gap:1em;gap:1em;justify-content:center;margin:0 auto}@media screen and (max-width: 1024px){.statistics[_ngcontent-%COMP%]{--grid-width: 180px}}@media screen and (max-width: 720px){.statistics[_ngcontent-%COMP%]{--grid-width: 160px}}@media screen and (max-width: 680px){.statistics[_ngcontent-%COMP%]{--grid-width: 140px}}@media screen and (max-width: 480px){.statistics[_ngcontent-%COMP%]{--grid-width: 120px}}.stream-host[_ngcontent-%COMP%]{grid-column:1/3;grid-row:1}.dl-rate-chart[_ngcontent-%COMP%]{width:100%;height:300px;margin:0}"],changeDetection:0}),n})(),Ut=(()=>{class n{transform(e){var i,s;return e?e.startsWith("/")?null!==(i=e.split("/").pop())&&void 0!==i?i:"":null!==(s=e.split("\\").pop())&&void 0!==s?s:"":""}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"filename",type:n,pure:!0}),n})(),_e=(()=>{class n{transform(e){return e&&0!==e.duration?Math.round(e.time/e.duration*100):0}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"progress",type:n,pure:!0}),n})(),go=(()=>{class n{constructor(){this.loading=!0}ngOnInit(){}get title(){switch(this.taskStatus.postprocessor_status){case ut.INJECTING:return"\u66f4\u65b0 FLV \u5143\u6570\u636e";case ut.REMUXING:return"\u8f6c\u6362 FLV \u4e3a MP4";default:return"\u6587\u4ef6\u5904\u7406"}}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-postprocessing-detail"]],inputs:{loading:"loading",taskStatus:"taskStatus"},decls:6,vars:9,consts:[[3,"nzTitle","nzLoading"],[3,"title"],["nzStatus","active",3,"nzPercent"]],template:function(e,i){if(1&e&&(t.TgZ(0,"nz-card",0),t.TgZ(1,"p",1),t._uU(2),t.ALo(3,"filename"),t.qZA(),t._UZ(4,"nz-progress",2),t.ALo(5,"progress"),t.qZA()),2&e){let s;t.Q6J("nzTitle",i.title)("nzLoading",i.loading),t.xp6(1),t.Q6J("title",i.taskStatus.postprocessing_path),t.xp6(1),t.hij(" ",t.lcZ(3,5,null!==(s=i.taskStatus.postprocessing_path)&&void 0!==s?s:"")," "),t.xp6(2),t.Q6J("nzPercent",null===i.taskStatus.postprocessing_progress?0:t.lcZ(5,7,i.taskStatus.postprocessing_progress))}},directives:[D.bd,oe],pipes:[Ut,_e],styles:["p[_ngcontent-%COMP%]{margin:0}"],changeDetection:0}),n})();const mo=new Map([[x.RECORDING,"\u5f55\u5236\u4e2d"],[x.INJECTING,"\u5904\u7406\u4e2d"],[x.REMUXING,"\u5904\u7406\u4e2d"],[x.COMPLETED,"\u5df2\u5b8c\u6210"],[x.MISSING,"\u4e0d\u5b58\u5728"],[x.BROKEN,"\u5f55\u5236\u4e2d\u65ad"]]);let _o=(()=>{class n{transform(e){var i;return null!==(i=mo.get(e))&&void 0!==i?i:"\uff1f\uff1f\uff1f"}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"filestatus",type:n,pure:!0}),n})();function ho(n,o){if(1&n&&(t.TgZ(0,"th",5),t._uU(1),t.qZA()),2&n){const e=o.$implicit;t.Q6J("nzSortOrder",e.sortOrder)("nzSortFn",e.sortFn)("nzSortDirections",e.sortDirections)("nzFilters",e.listOfFilter)("nzFilterFn",e.filterFn)("nzFilterMultiple",e.filterMultiple)("nzShowFilter",e.listOfFilter.length>0),t.xp6(1),t.hij(" ",e.name," ")}}function fo(n,o){if(1&n&&(t.TgZ(0,"tr"),t.TgZ(1,"td",6),t._uU(2),t.ALo(3,"filename"),t.qZA(),t.TgZ(4,"td",6),t.ALo(5,"number"),t._uU(6),t.ALo(7,"filesize"),t.qZA(),t.TgZ(8,"td",6),t._uU(9),t.ALo(10,"filestatus"),t.qZA(),t.qZA()),2&n){const e=o.$implicit;t.xp6(1),t.s9C("title",e.path),t.xp6(1),t.Oqu(t.lcZ(3,9,e.path)),t.xp6(2),t.s9C("title",t.lcZ(5,11,e.size)),t.xp6(2),t.Oqu(t.lcZ(7,13,e.size)),t.xp6(2),t.Gre("status ",e.status,""),t.s9C("title",e.status),t.xp6(1),t.hij(" ",t.lcZ(10,15,e.status)," ")}}const he=[x.RECORDING,x.INJECTING,x.REMUXING,x.COMPLETED,x.MISSING];let zo=(()=>{class n{constructor(){this.loading=!0,this.videoFileDetails=[],this.danmakuFileDetails=[],this.VideoFileStatus=x,this.fileDetails=[],this.columns=[{name:"\u6587\u4ef6",sortOrder:"ascend",sortFn:(e,i)=>e.path.localeCompare(i.path),sortDirections:["ascend","descend"],filterMultiple:!1,listOfFilter:[{text:"\u89c6\u9891",value:"video"},{text:"\u5f39\u5e55",value:"danmaku"}],filterFn:(e,i)=>{switch(e){case"video":return i.path.endsWith(".flv")||i.path.endsWith(".mp4");case"danmaku":return i.path.endsWith(".xml");default:return!1}}},{name:"\u5927\u5c0f",sortOrder:null,sortFn:(e,i)=>e.size-i.size,sortDirections:["ascend","descend",null],filterMultiple:!0,listOfFilter:[],filterFn:null},{name:"\u72b6\u6001",sortOrder:null,sortFn:(e,i)=>he.indexOf(e.status)-he.indexOf(i.status),sortDirections:["ascend","descend",null],filterMultiple:!0,listOfFilter:[{text:"\u5f55\u5236\u4e2d",value:[x.RECORDING]},{text:"\u5904\u7406\u4e2d",value:[x.INJECTING,x.REMUXING]},{text:"\u5df2\u5b8c\u6210",value:[x.COMPLETED]},{text:"\u4e0d\u5b58\u5728",value:[x.MISSING]}],filterFn:(e,i)=>e.some(s=>s.some(a=>a===i.status))}]}ngOnChanges(){this.fileDetails=[...this.videoFileDetails,...this.danmakuFileDetails]}trackByPath(e,i){return i.path}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-file-detail"]],inputs:{loading:"loading",videoFileDetails:"videoFileDetails",danmakuFileDetails:"danmakuFileDetails"},features:[t.TTD],decls:8,vars:8,consts:[["nzTitle","\u6587\u4ef6\u8be6\u60c5",3,"nzLoading"],[3,"nzLoading","nzData","nzPageSize","nzHideOnSinglePage"],["fileDetailsTable",""],[3,"nzSortOrder","nzSortFn","nzSortDirections","nzFilters","nzFilterFn","nzFilterMultiple","nzShowFilter",4,"ngFor","ngForOf"],[4,"ngFor","ngForOf","ngForTrackBy"],[3,"nzSortOrder","nzSortFn","nzSortDirections","nzFilters","nzFilterFn","nzFilterMultiple","nzShowFilter"],[3,"title"]],template:function(e,i){if(1&e&&(t.TgZ(0,"nz-card",0),t.TgZ(1,"nz-table",1,2),t.TgZ(3,"thead"),t.TgZ(4,"tr"),t.YNc(5,ho,2,8,"th",3),t.qZA(),t.qZA(),t.TgZ(6,"tbody"),t.YNc(7,fo,11,17,"tr",4),t.qZA(),t.qZA(),t.qZA()),2&e){const s=t.MAs(2);t.Q6J("nzLoading",i.loading),t.xp6(1),t.Q6J("nzLoading",i.loading)("nzData",i.fileDetails)("nzPageSize",8)("nzHideOnSinglePage",!0),t.xp6(4),t.Q6J("ngForOf",i.columns),t.xp6(2),t.Q6J("ngForOf",s.data)("ngForTrackBy",i.trackByPath)}},directives:[D.bd,J.N8,J.Om,J.$Z,u.sg,J.Uo,J._C,J.qD,J.p0],pipes:[Ut,u.JJ,Dt,_o],styles:[".status.recording[_ngcontent-%COMP%]{color:red}.status.injecting[_ngcontent-%COMP%], .status.remuxing[_ngcontent-%COMP%]{color:#00f}.status.completed[_ngcontent-%COMP%]{color:green}.status.missing[_ngcontent-%COMP%]{color:gray}.status.broken[_ngcontent-%COMP%]{color:orange}"],changeDetection:0}),n})();function Co(n,o){if(1&n&&t._UZ(0,"app-task-user-info-detail",6),2&n){const e=t.oxw(2);t.Q6J("loading",e.loading)("userInfo",e.taskData.user_info)}}function To(n,o){if(1&n&&t._UZ(0,"app-task-room-info-detail",7),2&n){const e=t.oxw(2);t.Q6J("loading",e.loading)("roomInfo",e.taskData.room_info)}}function xo(n,o){if(1&n&&t._UZ(0,"app-task-recording-detail",8),2&n){const e=t.oxw(2);t.Q6J("loading",e.loading)("taskStatus",e.taskData.task_status)}}function ko(n,o){if(1&n&&t._UZ(0,"app-task-network-detail",8),2&n){const e=t.oxw(2);t.Q6J("loading",e.loading)("taskStatus",e.taskData.task_status)}}function vo(n,o){if(1&n&&t._UZ(0,"app-task-postprocessing-detail",8),2&n){const e=t.oxw(2);t.Q6J("loading",e.loading)("taskStatus",e.taskData.task_status)}}function yo(n,o){if(1&n&&(t.YNc(0,Co,1,2,"app-task-user-info-detail",2),t.YNc(1,To,1,2,"app-task-room-info-detail",3),t.YNc(2,xo,1,2,"app-task-recording-detail",4),t.YNc(3,ko,1,2,"app-task-network-detail",4),t.YNc(4,vo,1,2,"app-task-postprocessing-detail",4),t._UZ(5,"app-task-file-detail",5)),2&n){const e=t.oxw();t.Q6J("ngIf",e.taskData),t.xp6(1),t.Q6J("ngIf",e.taskData),t.xp6(1),t.Q6J("ngIf",e.taskData),t.xp6(1),t.Q6J("ngIf",e.taskData),t.xp6(1),t.Q6J("ngIf",null==e.taskData||null==e.taskData.task_status?null:e.taskData.task_status.postprocessing_path),t.xp6(1),t.Q6J("loading",e.loading)("videoFileDetails",e.videoFileDetails)("danmakuFileDetails",e.danmakuFileDetails)}}const bo=function(){return{"max-width":"unset"}},So=function(){return{"row-gap":"1em"}};let Ao=(()=>{class n{constructor(e,i,s,a,c){this.route=e,this.router=i,this.changeDetector=s,this.notification=a,this.taskService=c,this.videoFileDetails=[],this.danmakuFileDetails=[],this.loading=!0}ngOnInit(){this.route.paramMap.subscribe(e=>{this.roomId=parseInt(e.get("id")),this.syncData()})}ngOnDestroy(){this.desyncData()}syncData(){this.dataSubscription=(0,A.of)((0,A.of)(0),vt(1e3)).pipe((0,Bt.u)(),(0,nt.w)(()=>It(this.taskService.getTaskData(this.roomId),this.taskService.getVideoFileDetails(this.roomId),this.taskService.getDanmakuFileDetails(this.roomId))),(0,it.K)(e=>{throw this.notification.error("\u83b7\u53d6\u4efb\u52a1\u6570\u636e\u51fa\u9519",e.message),e}),(0,bt.X)(10,3e3)).subscribe(([e,i,s])=>{this.loading=!1,this.taskData=e,this.videoFileDetails=i,this.danmakuFileDetails=s,this.changeDetector.markForCheck()},e=>{this.notification.error("\u83b7\u53d6\u4efb\u52a1\u6570\u636e\u51fa\u9519","\u7f51\u7edc\u8fde\u63a5\u5f02\u5e38, \u8bf7\u5f85\u7f51\u7edc\u6b63\u5e38\u540e\u5237\u65b0\u3002",{nzDuration:0})})}desyncData(){var e;null===(e=this.dataSubscription)||void 0===e||e.unsubscribe()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(ct.gz),t.Y36(ct.F0),t.Y36(t.sBO),t.Y36(Jt.zb),t.Y36(St))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-detail"]],decls:2,vars:5,consts:[["pageTitle","\u4efb\u52a1\u8be6\u60c5",3,"loading","pageStyles","contentStyles"],["appSubPageContent",""],[3,"loading","userInfo",4,"ngIf"],[3,"loading","roomInfo",4,"ngIf"],[3,"loading","taskStatus",4,"ngIf"],[3,"loading","videoFileDetails","danmakuFileDetails"],[3,"loading","userInfo"],[3,"loading","roomInfo"],[3,"loading","taskStatus"]],template:function(e,i){1&e&&(t.TgZ(0,"app-sub-page",0),t.YNc(1,yo,6,8,"ng-template",1),t.qZA()),2&e&&t.Q6J("loading",i.loading)("pageStyles",t.DdM(3,bo))("contentStyles",t.DdM(4,So))},directives:[Qi.q,Ui.Y,u.O5,qi,Hi,oo,po,go,zo],styles:[""],changeDetection:0}),n})();var fe=l(2323),Do=l(13),Mo=l(5778),H=l(4850);const pt=["(max-width: 534.98px)","(min-width: 535px) and (max-width: 1199.98px)","(min-width: 1200px)"];var qt=l(9727);let Rt=(()=>{class n{constructor(e,i){this.message=e,this.taskService=i}getAllTaskRoomIds(){return this.taskService.getAllTaskData().pipe((0,H.U)(e=>e.map(i=>i.room_info.room_id)))}updateTaskInfo(e){return this.taskService.updateTaskInfo(e).pipe((0,k.b)(()=>{this.message.success(`[${e}] \u6210\u529f\u5237\u65b0\u4efb\u52a1\u7684\u6570\u636e`)},i=>{this.message.error(`[${e}] \u5237\u65b0\u4efb\u52a1\u7684\u6570\u636e\u51fa\u9519: ${i.message}`)}))}updateAllTaskInfos(){return this.taskService.updateAllTaskInfos().pipe((0,k.b)(()=>{this.message.success("\u6210\u529f\u5237\u65b0\u5168\u90e8\u4efb\u52a1\u7684\u6570\u636e")},e=>{this.message.error(`\u5237\u65b0\u5168\u90e8\u4efb\u52a1\u7684\u6570\u636e\u51fa\u9519: ${e.message}`)}))}addTask(e){return this.taskService.addTask(e).pipe((0,H.U)(i=>({type:"success",message:"\u6210\u529f\u6dfb\u52a0\u4efb\u52a1"})),(0,it.K)(i=>{let s;return s=409==i.status?{type:"error",message:"\u4efb\u52a1\u5df2\u5b58\u5728\uff0c\u4e0d\u80fd\u91cd\u590d\u6dfb\u52a0\u3002"}:403==i.status?{type:"warning",message:"\u4efb\u52a1\u6570\u91cf\u8d85\u8fc7\u9650\u5236\uff0c\u4e0d\u80fd\u6dfb\u52a0\u4efb\u52a1\u3002"}:404==i.status?{type:"error",message:"\u76f4\u64ad\u95f4\u4e0d\u5b58\u5728"}:{type:"error",message:`\u6dfb\u52a0\u4efb\u52a1\u51fa\u9519: ${i.message}`},(0,A.of)(s)}),(0,H.U)(i=>(i.message=`${e}: ${i.message}`,i)),(0,k.b)(i=>{this.message[i.type](i.message)}))}removeTask(e){return this.taskService.removeTask(e).pipe((0,k.b)(()=>{this.message.success(`[${e}] \u4efb\u52a1\u5df2\u5220\u9664`)},i=>{this.message.error(`[${e}] \u5220\u9664\u4efb\u52a1\u51fa\u9519: ${i.message}`)}))}removeAllTasks(){const e=this.message.loading("\u6b63\u5728\u5220\u9664\u5168\u90e8\u4efb\u52a1...",{nzDuration:0}).messageId;return this.taskService.removeAllTasks().pipe((0,k.b)(()=>{this.message.remove(e),this.message.success("\u6210\u529f\u5220\u9664\u5168\u90e8\u4efb\u52a1")},i=>{this.message.remove(e),this.message.error(`\u5220\u9664\u5168\u90e8\u4efb\u52a1\u51fa\u9519: ${i.message}`)}))}startTask(e){const i=this.message.loading(`[${e}] \u6b63\u5728\u8fd0\u884c\u4efb\u52a1...`,{nzDuration:0}).messageId;return this.taskService.startTask(e).pipe((0,k.b)(()=>{this.message.remove(i),this.message.success(`[${e}] \u6210\u529f\u8fd0\u884c\u4efb\u52a1`)},s=>{this.message.remove(i),this.message.error(`[${e}] \u8fd0\u884c\u4efb\u52a1\u51fa\u9519: ${s.message}`)}))}startAllTasks(){const e=this.message.loading("\u6b63\u5728\u8fd0\u884c\u5168\u90e8\u4efb\u52a1...",{nzDuration:0}).messageId;return this.taskService.startAllTasks().pipe((0,k.b)(()=>{this.message.remove(e),this.message.success("\u6210\u529f\u8fd0\u884c\u5168\u90e8\u4efb\u52a1")},i=>{this.message.remove(e),this.message.error(`\u8fd0\u884c\u5168\u90e8\u4efb\u52a1\u51fa\u9519: ${i.message}`)}))}stopTask(e,i=!1){const s=this.message.loading(`[${e}] \u6b63\u5728\u505c\u6b62\u4efb\u52a1...`,{nzDuration:0}).messageId;return this.taskService.stopTask(e,i).pipe((0,k.b)(()=>{this.message.remove(s),this.message.success(`[${e}] \u6210\u529f\u505c\u6b62\u4efb\u52a1`)},a=>{this.message.remove(s),this.message.error(`[${e}] \u505c\u6b62\u4efb\u52a1\u51fa\u9519: ${a.message}`)}))}stopAllTasks(e=!1){const i=this.message.loading("\u6b63\u5728\u505c\u6b62\u5168\u90e8\u4efb\u52a1...",{nzDuration:0}).messageId;return this.taskService.stopAllTasks(e).pipe((0,k.b)(()=>{this.message.remove(i),this.message.success("\u6210\u529f\u505c\u6b62\u5168\u90e8\u4efb\u52a1")},s=>{this.message.remove(i),this.message.error(`\u505c\u6b62\u5168\u90e8\u4efb\u52a1\u51fa\u9519: ${s.message}`)}))}enableRecorder(e){const i=this.message.loading(`[${e}] \u6b63\u5728\u5f00\u542f\u5f55\u5236...`,{nzDuration:0}).messageId;return this.taskService.enableTaskRecorder(e).pipe((0,k.b)(()=>{this.message.remove(i),this.message.success(`[${e}] \u6210\u529f\u5f00\u542f\u5f55\u5236`)},s=>{this.message.remove(i),this.message.error(`[${e}] \u5f00\u542f\u5f55\u5236\u51fa\u9519: ${s.message}`)}))}enableAllRecorders(){const e=this.message.loading("\u6b63\u5728\u5f00\u542f\u5168\u90e8\u4efb\u52a1\u7684\u5f55\u5236...",{nzDuration:0}).messageId;return this.taskService.enableAllRecorders().pipe((0,k.b)(()=>{this.message.remove(e),this.message.success("\u6210\u529f\u5f00\u542f\u5168\u90e8\u4efb\u52a1\u7684\u5f55\u5236")},i=>{this.message.remove(e),this.message.error(`\u5f00\u542f\u5168\u90e8\u4efb\u52a1\u7684\u5f55\u5236\u51fa\u9519: ${i.message}`)}))}disableRecorder(e,i=!1){const s=this.message.loading(`[${e}] \u6b63\u5728\u5173\u95ed\u5f55\u5236...`,{nzDuration:0}).messageId;return this.taskService.disableTaskRecorder(e,i).pipe((0,k.b)(()=>{this.message.remove(s),this.message.success(`[${e}] \u6210\u529f\u5173\u95ed\u5f55\u5236`)},a=>{this.message.remove(s),this.message.error(`[${e}] \u5173\u95ed\u5f55\u5236\u51fa\u9519: ${a.message}`)}))}disableAllRecorders(e=!1){const i=this.message.loading("\u6b63\u5728\u5173\u95ed\u5168\u90e8\u4efb\u52a1\u7684\u5f55\u5236...",{nzDuration:0}).messageId;return this.taskService.disableAllRecorders(e).pipe((0,k.b)(()=>{this.message.remove(i),this.message.success("\u6210\u529f\u5173\u95ed\u5168\u90e8\u4efb\u52a1\u7684\u5f55\u5236")},s=>{this.message.remove(i),this.message.error(`\u5173\u95ed\u5168\u90e8\u4efb\u52a1\u7684\u5f55\u5236\u51fa\u9519: ${s.message}`)}))}cutStream(e){return this.taskService.cutStream(e).pipe((0,k.b)(()=>{this.message.success(`[${e}] \u6587\u4ef6\u5207\u5272\u5df2\u89e6\u53d1`)},i=>{403==i.status?this.message.warning(`[${e}] \u65f6\u957f\u592a\u77ed\u4e0d\u80fd\u5207\u5272\uff0c\u8bf7\u7a0d\u540e\u518d\u8bd5\u3002`):this.message.error(`[${e}] \u5207\u5272\u6587\u4ef6\u51fa\u9519: ${i.message}`)}))}}return n.\u0275fac=function(e){return new(e||n)(t.LFG(qt.dD),t.LFG(St))},n.\u0275prov=t.Yz7({token:n,factory:n.\u0275fac,providedIn:"root"}),n})();var Lt=l(2683),gt=l(4219);function Zo(n,o){if(1&n&&(t.ynx(0),t.GkF(1,8),t._UZ(2,"nz-divider",13),t.GkF(3,8),t._UZ(4,"nz-divider",13),t.GkF(5,8),t._UZ(6,"nz-divider",13),t.GkF(7,8),t.BQk()),2&n){t.oxw();const e=t.MAs(5),i=t.MAs(9),s=t.MAs(11),a=t.MAs(13);t.xp6(1),t.Q6J("ngTemplateOutlet",e),t.xp6(2),t.Q6J("ngTemplateOutlet",i),t.xp6(2),t.Q6J("ngTemplateOutlet",s),t.xp6(2),t.Q6J("ngTemplateOutlet",a)}}function Oo(n,o){if(1&n&&(t.ynx(0),t.GkF(1,8),t._UZ(2,"nz-divider",13),t.GkF(3,8),t._UZ(4,"nz-divider",13),t.GkF(5,8),t._UZ(6,"nz-divider",13),t.GkF(7,8),t.BQk()),2&n){t.oxw();const e=t.MAs(7),i=t.MAs(9),s=t.MAs(11),a=t.MAs(13);t.xp6(1),t.Q6J("ngTemplateOutlet",e),t.xp6(2),t.Q6J("ngTemplateOutlet",i),t.xp6(2),t.Q6J("ngTemplateOutlet",s),t.xp6(2),t.Q6J("ngTemplateOutlet",a)}}function wo(n,o){if(1&n&&(t.ynx(0),t.GkF(1,8),t.GkF(2,8),t.BQk()),2&n){t.oxw();const e=t.MAs(9),i=t.MAs(20);t.xp6(1),t.Q6J("ngTemplateOutlet",e),t.xp6(1),t.Q6J("ngTemplateOutlet",i)}}function Fo(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"label",16),t._uU(2),t.qZA(),t.BQk()),2&n){const e=o.$implicit;t.xp6(1),t.Q6J("nzValue",e.value),t.xp6(1),t.Oqu(e.label)}}function No(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"nz-radio-group",14),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().selection=s})("ngModelChange",function(s){return t.CHM(e),t.oxw().selectionChange.emit(s)}),t.YNc(1,Fo,3,2,"ng-container",15),t.qZA()}if(2&n){const e=t.oxw();t.Q6J("ngModel",e.selection),t.xp6(1),t.Q6J("ngForOf",e.selections)}}function Po(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"nz-select",17),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().selection=s})("ngModelChange",function(s){return t.CHM(e),t.oxw().selectionChange.emit(s)}),t.qZA()}if(2&n){const e=t.oxw();t.Q6J("nzOptions",e.selections)("ngModel",e.selection)}}function Eo(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"i",23),t.NdJ("click",function(){t.CHM(e),t.oxw(2);const s=t.MAs(2),a=t.oxw();return s.value="",a.onFilterInput("")}),t.qZA()}}function Io(n,o){if(1&n&&t.YNc(0,Eo,1,0,"i",22),2&n){t.oxw();const e=t.MAs(2);t.Q6J("ngIf",e.value)}}function Bo(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"nz-input-group",18),t.TgZ(1,"input",19,20),t.NdJ("input",function(){t.CHM(e);const s=t.MAs(2);return t.oxw().onFilterInput(s.value)}),t.qZA(),t.qZA(),t.YNc(3,Io,1,1,"ng-template",null,21,t.W1O)}if(2&n){const e=t.MAs(4);t.Q6J("nzSuffix",e)}}function Jo(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"button",24),t.NdJ("click",function(){return t.CHM(e),t.oxw().toggleReverse()}),t.TgZ(1,"span"),t._uU(2),t.qZA(),t._UZ(3,"i",25),t.qZA()}if(2&n){const e=t.oxw();t.xp6(2),t.Oqu(e.reverse?"\u5012\u5e8f":"\u6b63\u5e8f"),t.xp6(1),t.Q6J("nzType",e.reverse?"swap-left":"swap-right")("nzRotate",90)}}function Qo(n,o){if(1&n&&(t.TgZ(0,"button",26),t._UZ(1,"i",27),t.qZA()),2&n){t.oxw();const e=t.MAs(15);t.Q6J("nzDropdownMenu",e)}}function Uo(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"ul",28),t.TgZ(1,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().startAllTasks()}),t._uU(2,"\u5168\u90e8\u8fd0\u884c"),t.qZA(),t.TgZ(3,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().stopAllTasks()}),t._uU(4,"\u5168\u90e8\u505c\u6b62"),t.qZA(),t.TgZ(5,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().stopAllTasks(!0)}),t._uU(6,"\u5168\u90e8\u5f3a\u5236\u505c\u6b62"),t.qZA(),t._UZ(7,"li",30),t.TgZ(8,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().disableAllRecorders(!1)}),t._uU(9,"\u5168\u90e8\u5173\u95ed\u5f55\u5236"),t.qZA(),t.TgZ(10,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().disableAllRecorders(!0)}),t._uU(11,"\u5168\u90e8\u5f3a\u5236\u5173\u95ed\u5f55\u5236"),t.qZA(),t._UZ(12,"li",30),t.TgZ(13,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().removeAllTasks()}),t._uU(14,"\u5168\u90e8\u5220\u9664"),t.qZA(),t.TgZ(15,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().updateAllTaskInfos()}),t._uU(16,"\u5168\u90e8\u5237\u65b0\u6570\u636e"),t.qZA(),t.TgZ(17,"li",29),t.NdJ("click",function(){return t.CHM(e),t.oxw().copyAllTaskRoomIds()}),t._uU(18,"\u590d\u5236\u5168\u90e8\u623f\u95f4\u53f7"),t.qZA(),t.qZA()}}function qo(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"button",31),t.NdJ("click",function(){return t.CHM(e),t.oxw().drawerVisible=!0}),t._UZ(1,"i",27),t.qZA()}}function Ro(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"div",35),t._UZ(2,"nz-divider",36),t.GkF(3,8),t._UZ(4,"nz-divider",37),t.TgZ(5,"div",38),t.GkF(6,8),t.qZA(),t.qZA(),t.BQk()),2&n){t.oxw(2);const e=t.MAs(5),i=t.MAs(11);t.xp6(3),t.Q6J("ngTemplateOutlet",e),t.xp6(3),t.Q6J("ngTemplateOutlet",i)}}function Lo(n,o){if(1&n){const e=t.EpF();t.ynx(0),t.TgZ(1,"div",39),t.NdJ("click",function(){return t.CHM(e),t.oxw(2).menuDrawerVisible=!1}),t.GkF(2,8),t.qZA(),t.BQk()}if(2&n){t.oxw(2);const e=t.MAs(18);t.xp6(2),t.Q6J("ngTemplateOutlet",e)}}const Yo=function(){return{padding:"0"}};function Go(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"nz-drawer",32),t.NdJ("nzVisibleChange",function(s){return t.CHM(e),t.oxw().drawerVisible=s})("nzOnClose",function(){return t.CHM(e),t.oxw().drawerVisible=!1}),t.YNc(1,Ro,7,2,"ng-container",33),t.TgZ(2,"nz-drawer",34),t.NdJ("nzVisibleChange",function(s){return t.CHM(e),t.oxw().menuDrawerVisible=s})("nzOnClose",function(){return t.CHM(e),t.oxw().menuDrawerVisible=!1}),t.YNc(3,Lo,3,1,"ng-container",33),t.qZA(),t.qZA()}if(2&n){const e=t.oxw(),i=t.MAs(23);t.Q6J("nzTitle",i)("nzClosable",!1)("nzVisible",e.drawerVisible),t.xp6(2),t.Q6J("nzClosable",!1)("nzBodyStyle",t.DdM(6,Yo))("nzVisible",e.menuDrawerVisible)}}function $o(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"div",40),t.TgZ(1,"button",31),t.NdJ("click",function(){return t.CHM(e),t.oxw().menuDrawerVisible=!0}),t._UZ(2,"i",27),t.qZA(),t.qZA()}}let Vo=(()=>{class n{constructor(e,i,s,a,c,p){this.message=s,this.modal=a,this.clipboard=c,this.taskManager=p,this.selectionChange=new t.vpe,this.reverseChange=new t.vpe,this.filterChange=new t.vpe,this.destroyed=new v.xQ,this.useDrawer=!1,this.useSelector=!1,this.useRadioGroup=!0,this.drawerVisible=!1,this.menuDrawerVisible=!1,this.filterTerms=new v.xQ,this.selections=[{label:"\u5168\u90e8",value:Z.ALL},{label:"\u5f55\u5236\u4e2d",value:Z.RECORDING},{label:"\u5f55\u5236\u5f00",value:Z.RECORDER_ENABLED},{label:"\u5f55\u5236\u5173",value:Z.RECORDER_DISABLED},{label:"\u8fd0\u884c",value:Z.MONITOR_ENABLED},{label:"\u505c\u6b62",value:Z.MONITOR_DISABLED},{label:"\u76f4\u64ad",value:Z.LIVING},{label:"\u8f6e\u64ad",value:Z.ROUNDING},{label:"\u95f2\u7f6e",value:Z.PREPARING}],i.observe(pt).pipe((0,y.R)(this.destroyed)).subscribe(d=>{this.useDrawer=d.breakpoints[pt[0]],this.useSelector=d.breakpoints[pt[1]],this.useRadioGroup=d.breakpoints[pt[2]],e.markForCheck()})}ngOnInit(){this.filterTerms.pipe((0,Do.b)(300),(0,Mo.x)()).subscribe(e=>{this.filterChange.emit(e)})}ngOnDestroy(){this.destroyed.next(),this.destroyed.complete()}onFilterInput(e){this.filterTerms.next(e)}toggleReverse(){this.reverse=!this.reverse,this.reverseChange.emit(this.reverse)}removeAllTasks(){this.modal.confirm({nzTitle:"\u786e\u5b9a\u8981\u5220\u9664\u5168\u90e8\u4efb\u52a1\uff1f",nzContent:"\u6b63\u5728\u5f55\u5236\u7684\u4efb\u52a1\u5c06\u88ab\u5f3a\u5236\u505c\u6b62\uff01\u4efb\u52a1\u5220\u9664\u540e\u5c06\u4e0d\u53ef\u6062\u590d\uff01",nzOnOk:()=>new Promise((e,i)=>{this.taskManager.removeAllTasks().subscribe(e,i)})})}startAllTasks(){this.taskManager.startAllTasks().subscribe()}stopAllTasks(e=!1){e?this.modal.confirm({nzTitle:"\u786e\u5b9a\u8981\u5f3a\u5236\u505c\u6b62\u5168\u90e8\u4efb\u52a1\uff1f",nzContent:"\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\u4f1a\u88ab\u5f3a\u884c\u4e2d\u65ad\uff01\u786e\u5b9a\u8981\u653e\u5f03\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\uff1f",nzOnOk:()=>new Promise((i,s)=>{this.taskManager.stopAllTasks(e).subscribe(i,s)})}):this.taskManager.stopAllTasks().subscribe()}disableAllRecorders(e=!1){e?this.modal.confirm({nzTitle:"\u786e\u5b9a\u8981\u5f3a\u5236\u5173\u95ed\u5168\u90e8\u4efb\u52a1\u7684\u5f55\u5236\uff1f",nzContent:"\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\u4f1a\u88ab\u5f3a\u884c\u4e2d\u65ad\uff01\u786e\u5b9a\u8981\u653e\u5f03\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\uff1f",nzOnOk:()=>new Promise((i,s)=>{this.taskManager.disableAllRecorders(e).subscribe(i,s)})}):this.taskManager.disableAllRecorders().subscribe()}updateAllTaskInfos(){this.taskManager.updateAllTaskInfos().subscribe()}copyAllTaskRoomIds(){this.taskManager.getAllTaskRoomIds().pipe((0,H.U)(e=>e.join(" ")),(0,k.b)(e=>{if(!this.clipboard.copy(e))throw Error("Failed to copy text to the clipboard")})).subscribe(()=>{this.message.success("\u5168\u90e8\u623f\u95f4\u53f7\u5df2\u590d\u5236\u5230\u526a\u5207\u677f")},e=>{this.message.error("\u590d\u5236\u5168\u90e8\u623f\u95f4\u53f7\u5230\u526a\u5207\u677f\u51fa\u9519",e)})}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO),t.Y36(U.Yg),t.Y36(qt.dD),t.Y36(V.Sf),t.Y36(q),t.Y36(Rt))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-toolbar"]],inputs:{selection:"selection",reverse:"reverse"},outputs:{selectionChange:"selectionChange",reverseChange:"reverseChange",filterChange:"filterChange"},decls:24,vars:7,consts:[[1,"controls-wrapper"],[4,"ngIf"],["radioGroup",""],["selector",""],["filter",""],["reorderButton",""],["menuButton",""],["dropdownMenu","nzDropdownMenu"],[3,"ngTemplateOutlet"],["menu",""],["drawerButton",""],["nzPlacement","bottom","nzHeight","auto",3,"nzTitle","nzClosable","nzVisible","nzVisibleChange","nzOnClose",4,"ngIf"],["drawerHeader",""],["nzType","vertical"],["nzButtonStyle","solid",1,"radio-group",3,"ngModel","ngModelChange"],[4,"ngFor","ngForOf"],["nz-radio-button","",3,"nzValue"],[1,"selector",3,"nzOptions","ngModel","ngModelChange"],[1,"filter",3,"nzSuffix"],["nz-input","","type","text","maxlength","18","placeholder","\u7528\u6807\u9898\u3001\u5206\u533a\u3001\u4e3b\u64ad\u540d\u3001\u623f\u95f4\u53f7\u7b5b\u9009",3,"input"],["filterInput",""],["inputClearTpl",""],["nz-icon","","class","filter-clear","nzTheme","fill","nzType","close-circle",3,"click",4,"ngIf"],["nz-icon","","nzTheme","fill","nzType","close-circle",1,"filter-clear",3,"click"],["nz-button","","nzType","text","nzSize","default",1,"reverse-button",3,"click"],["nz-icon","",3,"nzType","nzRotate"],["nz-button","","nzType","text","nzSize","default","nz-dropdown","","nzPlacement","bottomRight",1,"more-actions-button",3,"nzDropdownMenu"],["nz-icon","","nzType","more"],["nz-menu","",1,"menu"],["nz-menu-item","",3,"click"],["nz-menu-divider",""],["nz-button","","nzType","text","nzSize","default",1,"more-actions-button",3,"click"],["nzPlacement","bottom","nzHeight","auto",3,"nzTitle","nzClosable","nzVisible","nzVisibleChange","nzOnClose"],[4,"nzDrawerContent"],["nzPlacement","bottom","nzHeight","auto",3,"nzClosable","nzBodyStyle","nzVisible","nzVisibleChange","nzOnClose"],[1,"drawer-content"],["nzText","\u7b5b\u9009"],["nzText","\u6392\u5e8f"],[1,"reorder-button-wrapper"],[1,"drawer-content",3,"click"],[1,"drawer-header"]],template:function(e,i){if(1&e&&(t.TgZ(0,"div",0),t.YNc(1,Zo,8,4,"ng-container",1),t.YNc(2,Oo,8,4,"ng-container",1),t.YNc(3,wo,3,2,"ng-container",1),t.qZA(),t.YNc(4,No,2,2,"ng-template",null,2,t.W1O),t.YNc(6,Po,1,2,"ng-template",null,3,t.W1O),t.YNc(8,Bo,5,1,"ng-template",null,4,t.W1O),t.YNc(10,Jo,4,3,"ng-template",null,5,t.W1O),t.YNc(12,Qo,2,1,"ng-template",null,6,t.W1O),t.TgZ(14,"nz-dropdown-menu",null,7),t.GkF(16,8),t.YNc(17,Uo,19,0,"ng-template",null,9,t.W1O),t.qZA(),t.YNc(19,qo,2,0,"ng-template",null,10,t.W1O),t.YNc(21,Go,4,7,"nz-drawer",11),t.YNc(22,$o,3,0,"ng-template",null,12,t.W1O)),2&e){const s=t.MAs(18);t.ekj("use-drawer",i.useDrawer),t.xp6(1),t.Q6J("ngIf",i.useRadioGroup),t.xp6(1),t.Q6J("ngIf",i.useSelector),t.xp6(1),t.Q6J("ngIf",i.useDrawer),t.xp6(13),t.Q6J("ngTemplateOutlet",s),t.xp6(5),t.Q6J("ngIf",i.useDrawer)}},directives:[u.O5,u.tP,Xt.g,Tt.Dg,m.JJ,m.On,u.sg,Tt.Of,Tt.Bq,wt.Vq,Lt.w,et.gB,et.ke,et.Zp,M.Ls,Ct.ix,tt.wA,tt.cm,tt.RR,gt.wO,gt.r9,gt.YV,lt.Vz,lt.SQ],styles:[".drawer-content[_ngcontent-%COMP%] .menu[_ngcontent-%COMP%]{box-shadow:none;padding:.5em 0}.drawer-content[_ngcontent-%COMP%] .menu[_ngcontent-%COMP%] *[nz-menu-item][_ngcontent-%COMP%]{margin:0;padding:.5em 2em}.controls-wrapper[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap;align-items:center;gap:.2em;width:100%;padding:.2em;background:#f9f9f9;border-left:none;border-right:none}.controls-wrapper[_ngcontent-%COMP%] nz-divider[_ngcontent-%COMP%]{height:1.8em;top:0}.controls-wrapper[_ngcontent-%COMP%]:not(.use-drawer) .filter[_ngcontent-%COMP%]{max-width:18em}.controls-wrapper.use-drawer[_ngcontent-%COMP%] .filter[_ngcontent-%COMP%]{max-width:unset;width:unset;flex:auto}.controls-wrapper[_ngcontent-%COMP%] .selector[_ngcontent-%COMP%]{min-width:6em}.reverse-button[_ngcontent-%COMP%]{padding:0 .5em}.reverse-button[_ngcontent-%COMP%] i[_ngcontent-%COMP%]{margin:0}.more-actions-button[_ngcontent-%COMP%]{margin-left:auto;border:none;background:inherit}.more-actions-button[_ngcontent-%COMP%] i[_ngcontent-%COMP%]{font-size:20px}.menu[_ngcontent-%COMP%] nz-divider[_ngcontent-%COMP%]{margin:0}.drawer-header[_ngcontent-%COMP%]{display:flex}.drawer-content[_ngcontent-%COMP%] .reorder-button-wrapper[_ngcontent-%COMP%], .drawer-content[_ngcontent-%COMP%] .radio-group[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(4,1fr);grid-gap:2vw;gap:2vw}.drawer-content[_ngcontent-%COMP%] nz-divider[_ngcontent-%COMP%]:first-of-type{margin-top:0}.drawer-content[_ngcontent-%COMP%] .radio-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%]{text-align:center;padding:0}"],changeDetection:0}),n})();var jo=l(5136);let Ho=(()=>{class n{constructor(e){this.storage=e}getSettings(e){var i;const s=this.storage.getData(this.getStorageKey(e));return s&&null!==(i=JSON.parse(s))&&void 0!==i?i:{}}updateSettings(e,i){i=Object.assign(this.getSettings(e),i);const s=JSON.stringify(i);this.storage.setData(this.getStorageKey(e),s)}getStorageKey(e){return`app-tasks-${e}`}}return n.\u0275fac=function(e){return new(e||n)(t.LFG(fe.V))},n.\u0275prov=t.Yz7({token:n,factory:n.\u0275fac,providedIn:"root"}),n})(),Wo=(()=>{class n{constructor(e){this.changeDetector=e,this.value=0,this.width=200,this.height=16,this.stroke="white",this.data=[],this.points=[];for(let i=0;i<=this.width;i+=2)this.data.push(0),this.points.push({x:i,y:this.height})}get polylinePoints(){return this.points.map(e=>`${e.x},${e.y}`).join(" ")}ngOnInit(){this.subscription=vt(1e3).subscribe(()=>{this.data.push(this.value||0),this.data.shift();let e=Math.max(...this.data);this.points=this.data.map((i,s)=>({x:Math.min(2*s,this.width),y:(1-i/(e||1))*this.height})),this.changeDetector.markForCheck()})}ngOnDestroy(){var e;null===(e=this.subscription)||void 0===e||e.unsubscribe()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-wave-graph"]],inputs:{value:"value",width:"width",height:"height",stroke:"stroke"},decls:2,vars:4,consts:[["fill","none"]],template:function(e,i){1&e&&(t.O4$(),t.TgZ(0,"svg"),t._UZ(1,"polyline",0),t.qZA()),2&e&&(t.uIk("width",i.width)("height",i.height),t.xp6(1),t.uIk("stroke",i.stroke)("points",i.polylinePoints))},styles:["[_nghost-%COMP%]{position:relative;top:2px}"],changeDetection:0}),n})();function Xo(n,o){1&n&&(t.ynx(0),t._uU(1,", bluray"),t.BQk())}function Ko(n,o){if(1&n&&(t.TgZ(0,"li",4),t.TgZ(1,"span",5),t._uU(2,"\u6d41\u7f16\u7801\u5668"),t.qZA(),t.TgZ(3,"span",6),t._uU(4),t.qZA(),t.qZA()),2&n){const e=t.oxw(2);t.xp6(4),t.Oqu(null==e.profile.streams[0]||null==e.profile.streams[0].tags?null:e.profile.streams[0].tags.encoder)}}const Yt=function(){return{bitrate:!0}};function ts(n,o){if(1&n&&(t.TgZ(0,"ul",3),t.TgZ(1,"li",4),t.TgZ(2,"span",5),t._uU(3,"\u89c6\u9891\u4fe1\u606f"),t.qZA(),t.TgZ(4,"span",6),t.TgZ(5,"span"),t._uU(6),t.qZA(),t.TgZ(7,"span"),t._uU(8),t.qZA(),t.TgZ(9,"span"),t._uU(10),t.qZA(),t.TgZ(11,"span"),t._uU(12),t.ALo(13,"datarate"),t.qZA(),t.qZA(),t.qZA(),t.TgZ(14,"li",4),t.TgZ(15,"span",5),t._uU(16,"\u97f3\u9891\u4fe1\u606f"),t.qZA(),t.TgZ(17,"span",6),t.TgZ(18,"span"),t._uU(19),t.qZA(),t.TgZ(20,"span"),t._uU(21),t.qZA(),t.TgZ(22,"span"),t._uU(23),t.qZA(),t.TgZ(24,"span"),t._uU(25),t.ALo(26,"datarate"),t.qZA(),t.qZA(),t.qZA(),t.TgZ(27,"li",4),t.TgZ(28,"span",5),t._uU(29,"\u683c\u5f0f\u753b\u8d28"),t.qZA(),t.TgZ(30,"span",6),t.TgZ(31,"span"),t._uU(32),t.qZA(),t.TgZ(33,"span"),t._uU(34),t.ALo(35,"quality"),t.YNc(36,Xo,2,0,"ng-container",7),t._uU(37,") "),t.qZA(),t.qZA(),t.qZA(),t.YNc(38,Ko,5,1,"li",8),t.TgZ(39,"li",4),t.TgZ(40,"span",5),t._uU(41,"\u6d41\u4e3b\u673a\u540d"),t.qZA(),t.TgZ(42,"span",6),t._uU(43),t.qZA(),t.qZA(),t.TgZ(44,"li",4),t.TgZ(45,"span",5),t._uU(46,"\u4e0b\u8f7d\u901f\u5ea6"),t.qZA(),t._UZ(47,"app-wave-graph",9),t.TgZ(48,"span",6),t._uU(49),t.ALo(50,"datarate"),t.qZA(),t.qZA(),t.TgZ(51,"li",4),t.TgZ(52,"span",5),t._uU(53,"\u5f55\u5236\u901f\u5ea6"),t.qZA(),t._UZ(54,"app-wave-graph",9),t.TgZ(55,"span",6),t._uU(56),t.ALo(57,"datarate"),t.qZA(),t.qZA(),t.qZA()),2&n){const e=t.oxw();let i;t.xp6(6),t.hij(" ",null==e.profile.streams[0]?null:e.profile.streams[0].codec_name," "),t.xp6(2),t.AsE(" ",null==e.profile.streams[0]?null:e.profile.streams[0].width,"x",null==e.profile.streams[0]?null:e.profile.streams[0].height," "),t.xp6(2),t.hij(" ",(null==e.profile.streams[0]?null:e.profile.streams[0].r_frame_rate).split("/")[0]," fps"),t.xp6(2),t.hij(" ",t.xi3(13,19,1e3*e.metadata.videodatarate,t.DdM(32,Yt))," "),t.xp6(7),t.hij(" ",null==e.profile.streams[1]?null:e.profile.streams[1].codec_name," "),t.xp6(2),t.hij(" ",null==e.profile.streams[1]?null:e.profile.streams[1].sample_rate," HZ"),t.xp6(2),t.hij(" ",null==e.profile.streams[1]?null:e.profile.streams[1].channel_layout," "),t.xp6(2),t.hij(" ",t.xi3(26,22,1e3*e.metadata.audiodatarate,t.DdM(33,Yt))," "),t.xp6(7),t.hij(" ",e.data.task_status.real_stream_format?e.data.task_status.real_stream_format:"N/A"," "),t.xp6(2),t.AsE(" ",e.data.task_status.real_quality_number?t.lcZ(35,25,e.data.task_status.real_quality_number):"N/A"," (",null!==(i=e.data.task_status.real_quality_number)&&void 0!==i?i:"N/A",""),t.xp6(2),t.Q6J("ngIf",e.isBlurayStreamQuality()),t.xp6(2),t.Q6J("ngIf",null==e.profile.streams[0]||null==e.profile.streams[0].tags?null:e.profile.streams[0].tags.encoder),t.xp6(5),t.hij(" ",e.data.task_status.stream_host," "),t.xp6(4),t.Q6J("value",e.data.task_status.dl_rate),t.xp6(2),t.hij(" ",t.xi3(50,27,8*e.data.task_status.dl_rate,t.DdM(34,Yt))," "),t.xp6(5),t.Q6J("value",e.data.task_status.rec_rate),t.xp6(2),t.hij(" ",t.lcZ(57,30,e.data.task_status.rec_rate)," ")}}let es=(()=>{class n{constructor(e,i,s){this.changeDetector=e,this.notification=i,this.taskService=s,this.metadata=null,this.close=new t.vpe,this.RunningStatus=O}ngOnInit(){this.syncData()}ngOnDestroy(){this.desyncData()}isBlurayStreamQuality(){return/_bluray/.test(this.data.task_status.stream_url)}closePanel(e){e.preventDefault(),e.stopPropagation(),this.close.emit()}syncData(){this.dataSubscription=(0,A.of)((0,A.of)(0),vt(1e3)).pipe((0,Bt.u)(),(0,nt.w)(()=>It(this.taskService.getStreamProfile(this.data.room_info.room_id),this.taskService.getMetadata(this.data.room_info.room_id))),(0,it.K)(e=>{throw this.notification.error("\u83b7\u53d6\u6570\u636e\u51fa\u9519",e.message),e}),(0,bt.X)(3,1e3)).subscribe(([e,i])=>{this.profile=e,this.metadata=i,this.changeDetector.markForCheck()},e=>{this.notification.error("\u83b7\u53d6\u6570\u636e\u51fa\u9519","\u7f51\u7edc\u8fde\u63a5\u5f02\u5e38, \u8bf7\u5f85\u7f51\u7edc\u6b63\u5e38\u540e\u5237\u65b0\u3002",{nzDuration:0})})}desyncData(){var e;null===(e=this.dataSubscription)||void 0===e||e.unsubscribe()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO),t.Y36(Jt.zb),t.Y36(St))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-info-panel"]],inputs:{data:"data",profile:"profile",metadata:"metadata"},outputs:{close:"close"},decls:4,vars:1,consts:[[1,"info-panel"],["title","\u5173\u95ed",1,"close-panel",3,"click"],["class","info-list",4,"ngIf"],[1,"info-list"],[1,"info-item"],[1,"label"],[1,"value"],[4,"ngIf"],["class","info-item",4,"ngIf"],[3,"value"]],template:function(e,i){1&e&&(t.TgZ(0,"div",0),t.TgZ(1,"button",1),t.NdJ("click",function(a){return i.closePanel(a)}),t._uU(2," [x] "),t.qZA(),t.YNc(3,ts,58,35,"ul",2),t.qZA()),2&e&&(t.xp6(3),t.Q6J("ngIf",i.data.task_status.running_status===i.RunningStatus.RECORDING&&i.profile&&i.profile.streams&&i.profile.format&&i.metadata))},directives:[u.O5,Wo],pipes:[At,Qt],styles:['@charset "UTF-8";.info-panel[_ngcontent-%COMP%]{color:#fff;text-shadow:1px 1px 2px black;margin:0;padding:0 .5rem;background:rgba(0,0,0,.32)}.info-panel[_ngcontent-%COMP%]{position:absolute;top:2.55rem;bottom:2rem;left:0rem;right:0rem;width:100%;font-size:1rem;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;overflow:auto}.info-panel[_ngcontent-%COMP%]::-webkit-scrollbar{background-color:transparent;width:4px}.info-panel[_ngcontent-%COMP%]::-webkit-scrollbar-track{background:transparent}.info-panel[_ngcontent-%COMP%]::-webkit-scrollbar-thumb{background:#eee;border-radius:2px}.info-panel[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover{background:#fff}.info-panel[_ngcontent-%COMP%] .close-panel[_ngcontent-%COMP%]{position:absolute;top:0rem;right:0rem;width:2rem;height:2rem;padding:0;color:#fff;background:transparent;border:none;font-size:1rem;display:flex;align-items:center;justify-content:center;cursor:pointer}.info-panel[_ngcontent-%COMP%] .info-list[_ngcontent-%COMP%]{margin:0;padding:0;list-style:none;width:100%;height:100%}.info-panel[_ngcontent-%COMP%] .info-list[_ngcontent-%COMP%] .info-item[_ngcontent-%COMP%] .label[_ngcontent-%COMP%]{display:inline-block;margin:0;width:5rem;text-align:right}.info-panel[_ngcontent-%COMP%] .info-list[_ngcontent-%COMP%] .info-item[_ngcontent-%COMP%] .label[_ngcontent-%COMP%]:after{content:"\\ff1a"}.info-panel[_ngcontent-%COMP%] .info-list[_ngcontent-%COMP%] .info-item[_ngcontent-%COMP%] .value[_ngcontent-%COMP%]{display:inline-block;margin:0;text-align:left}.info-panel[_ngcontent-%COMP%] .info-list[_ngcontent-%COMP%] .info-item[_ngcontent-%COMP%] .value[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:not(:first-child):before{content:", "}app-wave-graph[_ngcontent-%COMP%]{margin-right:1rem}'],changeDetection:0}),n})();const ze=function(){return{spacer:""}};function ns(n,o){if(1&n&&(t.TgZ(0,"div",2),t.TgZ(1,"p",3),t.TgZ(2,"span",4),t._UZ(3,"i"),t.qZA(),t.TgZ(4,"span",5),t._uU(5),t.ALo(6,"duration"),t.qZA(),t.TgZ(7,"span",6),t._uU(8),t.ALo(9,"datarate"),t.qZA(),t.TgZ(10,"span",7),t._uU(11),t.ALo(12,"filesize"),t.qZA(),t.TgZ(13,"span",8),t.ALo(14,"number"),t._uU(15),t.ALo(16,"number"),t.qZA(),t.TgZ(17,"span",9),t._uU(18),t.ALo(19,"quality"),t.qZA(),t.qZA(),t.qZA()),2&n){const e=t.oxw();t.xp6(5),t.hij(" ",t.lcZ(6,6,e.status.rec_elapsed)," "),t.xp6(3),t.hij(" ",t.xi3(9,8,e.status.rec_rate,t.DdM(22,ze))," "),t.xp6(3),t.hij(" ",t.xi3(12,11,e.status.rec_total,t.DdM(23,ze))," "),t.xp6(2),t.MGl("nzTooltipTitle","\u5f39\u5e55\u603b\u8ba1\uff1a",t.xi3(14,14,e.status.danmu_total,"1.0-0"),""),t.xp6(2),t.hij(" ",t.xi3(16,17,e.status.danmu_total,"1.0-0")," "),t.xp6(3),t.hij(" ",e.status.real_quality_number?t.lcZ(19,20,e.status.real_quality_number):""," ")}}function is(n,o){if(1&n&&(t.TgZ(0,"div",2),t.TgZ(1,"p",10),t.ALo(2,"filename"),t._uU(3),t.ALo(4,"filename"),t.qZA(),t._UZ(5,"nz-progress",11),t.ALo(6,"progress"),t.qZA()),2&n){const e=t.oxw();let i,s;t.xp6(1),t.MGl("nzTooltipTitle","\u6b63\u5728\u6dfb\u52a0\u5143\u6570\u636e\uff1a",t.lcZ(2,7,null!==(i=e.status.postprocessing_path)&&void 0!==i?i:""),""),t.xp6(2),t.hij(" ",t.lcZ(4,9,null!==(s=e.status.postprocessing_path)&&void 0!==s?s:"")," "),t.xp6(2),t.Q6J("nzType","line")("nzShowInfo",!1)("nzStrokeLinecap","square")("nzStrokeWidth",2)("nzPercent",null===e.status.postprocessing_progress?0:t.lcZ(6,11,e.status.postprocessing_progress))}}function os(n,o){if(1&n&&(t.TgZ(0,"div",2),t.TgZ(1,"p",12),t.ALo(2,"filename"),t._uU(3),t.ALo(4,"filename"),t.qZA(),t._UZ(5,"nz-progress",11),t.ALo(6,"progress"),t.qZA()),2&n){const e=t.oxw();let i,s;t.xp6(1),t.MGl("nzTooltipTitle","\u6b63\u5728\u8f6c\u5c01\u88c5\uff1a",t.lcZ(2,7,null!==(i=e.status.postprocessing_path)&&void 0!==i?i:""),""),t.xp6(2),t.hij(" ",t.lcZ(4,9,null!==(s=e.status.postprocessing_path)&&void 0!==s?s:"")," "),t.xp6(2),t.Q6J("nzType","line")("nzShowInfo",!1)("nzStrokeLinecap","square")("nzStrokeWidth",2)("nzPercent",null===e.status.postprocessing_progress?0:t.lcZ(6,11,e.status.postprocessing_progress))}}let ss=(()=>{class n{constructor(){this.RunningStatus=O}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-status-display"]],inputs:{status:"status"},decls:4,vars:4,consts:[[3,"ngSwitch"],["class","status-display",4,"ngSwitchCase"],[1,"status-display"],[1,"status-bar","recording"],["nz-tooltip","","nzTooltipTitle","\u6b63\u5728\u5f55\u5236","nzTooltipPlacement","top",1,"status-indicator"],["nz-tooltip","","nzTooltipTitle","\u5f55\u5236\u7528\u65f6","nzTooltipPlacement","top",1,"time-elapsed"],["nz-tooltip","","nzTooltipTitle","\u5f55\u5236\u901f\u5ea6","nzTooltipPlacement","top",1,"data-rate"],["nz-tooltip","","nzTooltipTitle","\u5f55\u5236\u603b\u8ba1","nzTooltipPlacement","top",1,"data-count"],["nz-tooltip","","nzTooltipPlacement","top",1,"danmu-count",3,"nzTooltipTitle"],["nz-tooltip","","nzTooltipTitle","\u5f55\u5236\u753b\u8d28","nzTooltipPlacement","leftTop",1,"quality"],["nz-tooltip","","nzTooltipPlacement","top",1,"status-bar","injecting",3,"nzTooltipTitle"],[3,"nzType","nzShowInfo","nzStrokeLinecap","nzStrokeWidth","nzPercent"],["nz-tooltip","","nzTooltipPlacement","top",1,"status-bar","remuxing",3,"nzTooltipTitle"]],template:function(e,i){1&e&&(t.ynx(0,0),t.YNc(1,ns,20,24,"div",1),t.YNc(2,is,7,13,"div",1),t.YNc(3,os,7,13,"div",1),t.BQk()),2&e&&(t.Q6J("ngSwitch",i.status.running_status),t.xp6(1),t.Q6J("ngSwitchCase",i.RunningStatus.RECORDING),t.xp6(1),t.Q6J("ngSwitchCase",i.RunningStatus.INJECTING),t.xp6(1),t.Q6J("ngSwitchCase",i.RunningStatus.REMUXING))},directives:[u.RF,u.n9,rt.SY,oe],pipes:[me,At,Dt,u.JJ,Qt,Ut,_e],styles:[".status-bar[_ngcontent-%COMP%]{color:#fff;text-shadow:1px 1px 2px black;margin:0;padding:0 .5rem;background:rgba(0,0,0,.32)}.status-display[_ngcontent-%COMP%]{position:absolute;bottom:0;left:0;width:100%}.status-bar[_ngcontent-%COMP%]{display:flex;gap:1rem;font-size:1rem;line-height:1.8}.status-bar.recording[_ngcontent-%COMP%] .status-indicator[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:center}.status-bar.recording[_ngcontent-%COMP%] .status-indicator[_ngcontent-%COMP%] i[_ngcontent-%COMP%]{width:1rem;height:1rem;border-radius:.5rem;color:red;background:red;animation:blinker 1s cubic-bezier(1,0,0,1) infinite}@keyframes blinker{0%{opacity:0}to{opacity:1}}.status-bar.injecting[_ngcontent-%COMP%], .status-bar.remuxing[_ngcontent-%COMP%], .status-bar[_ngcontent-%COMP%] .danmu-count[_ngcontent-%COMP%]{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.status-bar[_ngcontent-%COMP%] .quality[_ngcontent-%COMP%]{flex:none;margin-left:auto}nz-progress[_ngcontent-%COMP%]{display:flex}nz-progress[_ngcontent-%COMP%] .ant-progress-outer{display:flex}"],changeDetection:0}),n})();var F=l(3523),w=l(8737);function as(n,o){1&n&&(t.ynx(0),t._uU(1," \u8bf7\u8f93\u5165\u8def\u5f84\u6a21\u677f "),t.BQk())}function rs(n,o){1&n&&(t.ynx(0),t._uU(1," \u8def\u5f84\u6a21\u677f\u6709\u9519\u8bef "),t.BQk())}function ls(n,o){if(1&n&&(t.YNc(0,as,2,0,"ng-container",66),t.YNc(1,rs,2,0,"ng-container",66)),2&n){const e=o.$implicit;t.Q6J("ngIf",e.hasError("required")),t.xp6(1),t.Q6J("ngIf",e.hasError("pattern"))}}function cs(n,o){1&n&&(t.TgZ(0,"p"),t._uU(1," \u9009\u62e9\u8981\u5f55\u5236\u7684\u76f4\u64ad\u6d41\u683c\u5f0f "),t._UZ(2,"br"),t._uU(3," FLV: \u7f51\u7edc\u4e0d\u7a33\u5b9a\u5bb9\u6613\u4e2d\u65ad\u4e22\u5931\u6570\u636e\u6216\u5f55\u5236\u5230\u4e8c\u538b\u753b\u8d28 "),t._UZ(4,"br"),t._uU(5," HLS (fmp4): \u57fa\u672c\u4e0d\u53d7\u7f51\u7edc\u6ce2\u52a8\u5f71\u54cd\uff0c\u4f46\u53ea\u6709\u90e8\u5206\u76f4\u64ad\u95f4\u652f\u6301\u3002 "),t._UZ(6,"br"),t._uU(7," P.S. "),t._UZ(8,"br"),t._uU(9," \u5f55\u5236 HLS \u6d41\u9700\u8981 ffmpeg "),t._UZ(10,"br"),t._uU(11," \u5728\u8bbe\u5b9a\u65f6\u95f4\u5185\u6ca1\u6709 fmp4 \u6d41\u4f1a\u81ea\u52a8\u5207\u6362\u5f55\u5236 flv \u6d41 "),t._UZ(12,"br"),t._uU(13," WEB \u7aef\u76f4\u64ad\u64ad\u653e\u5668\u662f Hls7Player \u7684\u76f4\u64ad\u95f4\u652f\u6301\u5f55\u5236 fmp4 \u6d41, fMp4Player \u5219\u4e0d\u652f\u6301\u3002 "),t.qZA())}function us(n,o){1&n&&(t.TgZ(0,"p"),t._uU(1," \u5982\u679c\u8d85\u8fc7\u6240\u8bbe\u7f6e\u7684\u7b49\u5f85\u65f6\u95f4 fmp4 \u6d41\u8fd8\u6ca1\u6709\u5c31\u5207\u6362\u4e3a\u5f55\u5236 flv \u6d41 "),t._UZ(2,"br"),t._uU(3," fmp4 \u6d41\u5728\u521a\u63a8\u6d41\u662f\u6ca1\u6709\u7684\uff0c\u8981\u8fc7\u4e00\u4f1a\u624d\u6709\u3002 "),t._UZ(4,"br"),t._uU(5," fmp4 \u6d41\u51fa\u73b0\u7684\u65f6\u95f4\u548c\u76f4\u64ad\u5ef6\u8fdf\u6709\u5173\uff0c\u4e00\u822c\u90fd\u5728 10 \u79d2\u5185\uff0c\u4f46\u4e5f\u6709\u5ef6\u8fdf\u6bd4\u8f83\u5927\u8d85\u8fc7 1 \u5206\u949f\u7684\u3002 "),t._UZ(6,"br"),t._uU(7," \u63a8\u8350\u5168\u5c40\u8bbe\u7f6e\u4e3a 10 \u79d2\uff0c\u4e2a\u522b\u5ef6\u8fdf\u6bd4\u8f83\u5927\u7684\u76f4\u64ad\u95f4\u5355\u72ec\u8bbe\u7f6e\u3002 "),t.qZA())}function ps(n,o){1&n&&(t.TgZ(0,"p"),t._uU(1," \u9ed8\u8ba4: \u6bcf\u4e2a\u5206\u5272\u7684\u5f55\u64ad\u6587\u4ef6\u5bf9\u5e94\u4fdd\u5b58\u4e00\u4e2a\u5c01\u9762\u6587\u4ef6\uff0c\u4e0d\u7ba1\u5c01\u9762\u662f\u5426\u76f8\u540c\u3002"),t._UZ(2,"br"),t._uU(3," \u53bb\u91cd: \u76f8\u540c\u7684\u5c01\u9762\u53ea\u4fdd\u5b58\u4e00\u6b21"),t._UZ(4,"br"),t._uU(5," P.S. "),t._UZ(6,"br"),t._uU(7," \u5224\u65ad\u662f\u5426\u76f8\u540c\u662f\u4f9d\u636e\u5c01\u9762\u6570\u636e\u7684 sha1\uff0c\u53ea\u5728\u5355\u6b21\u5f55\u5236\u5185\u6709\u6548\u3002 "),t.qZA())}function gs(n,o){1&n&&(t.TgZ(0,"p"),t._uU(1," \u81ea\u52a8: \u6ca1\u51fa\u9519\u5c31\u5220\u9664\u6e90\u6587\u4ef6"),t._UZ(2,"br"),t._uU(3," \u8c28\u614e: \u6ca1\u51fa\u9519\u4e14\u6ca1\u8b66\u544a\u624d\u5220\u9664\u6e90\u6587\u4ef6"),t._UZ(4,"br"),t._uU(5," \u4ece\u4e0d: \u603b\u662f\u4fdd\u7559\u6e90\u6587\u4ef6"),t._UZ(6,"br"),t.qZA())}function ds(n,o){1&n&&(t.ynx(0),t._uU(1," \u8bf7\u8f93\u5165 User Agent "),t.BQk())}function ms(n,o){1&n&&t.YNc(0,ds,2,0,"ng-container",66),2&n&&t.Q6J("ngIf",o.$implicit.hasError("required"))}function _s(n,o){if(1&n){const e=t.EpF();t.ynx(0),t.TgZ(1,"form",2),t.TgZ(2,"div",3),t.TgZ(3,"h2"),t._uU(4,"\u6587\u4ef6"),t.qZA(),t.TgZ(5,"nz-form-item",4),t.TgZ(6,"nz-form-label",5),t._uU(7,"\u8def\u5f84\u6a21\u677f"),t.qZA(),t.TgZ(8,"nz-form-control",6),t.TgZ(9,"input",7),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.output.pathTemplate=s}),t.qZA(),t.YNc(10,ls,2,2,"ng-template",null,8,t.W1O),t.qZA(),t.TgZ(12,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.output.pathTemplate=s?a.globalSettings.output.pathTemplate:null}),t._uU(13,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(14,"nz-form-item",10),t.TgZ(15,"nz-form-label",11),t._uU(16,"\u5927\u5c0f\u9650\u5236"),t.qZA(),t.TgZ(17,"nz-form-control",12),t.TgZ(18,"nz-select",13),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.output.filesizeLimit=s}),t.qZA(),t.qZA(),t.TgZ(19,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.output.filesizeLimit=s?a.globalSettings.output.filesizeLimit:null}),t._uU(20,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(21,"nz-form-item",10),t.TgZ(22,"nz-form-label",11),t._uU(23,"\u65f6\u957f\u9650\u5236"),t.qZA(),t.TgZ(24,"nz-form-control",12),t.TgZ(25,"nz-select",14),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.output.durationLimit=s}),t.qZA(),t.qZA(),t.TgZ(26,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.output.durationLimit=s?a.globalSettings.output.durationLimit:null}),t._uU(27,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.qZA(),t.TgZ(28,"div",15),t.TgZ(29,"h2"),t._uU(30,"\u5f55\u5236"),t.qZA(),t.TgZ(31,"nz-form-item",10),t.TgZ(32,"nz-form-label",11),t._uU(33,"\u76f4\u64ad\u6d41\u683c\u5f0f"),t.qZA(),t.YNc(34,cs,14,0,"ng-template",null,16,t.W1O),t.TgZ(36,"nz-form-control",12),t.TgZ(37,"nz-select",17),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.streamFormat=s}),t.qZA(),t.qZA(),t.TgZ(38,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.streamFormat=s?a.globalSettings.recorder.streamFormat:null}),t._uU(39,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(40,"nz-form-item",10),t.TgZ(41,"nz-form-label",11),t._uU(42,"fmp4 \u6d41\u7b49\u5f85\u65f6\u95f4"),t.qZA(),t.YNc(43,us,8,0,"ng-template",null,18,t.W1O),t.TgZ(45,"nz-form-control",12),t.TgZ(46,"nz-select",19,20),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.fmp4StreamTimeout=s}),t.qZA(),t.qZA(),t.TgZ(48,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.fmp4StreamTimeout=s?a.globalSettings.recorder.fmp4StreamTimeout:null}),t._uU(49,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(50,"nz-form-item",10),t.TgZ(51,"nz-form-label",21),t._uU(52,"\u753b\u8d28"),t.qZA(),t.TgZ(53,"nz-form-control",12),t.TgZ(54,"nz-select",22),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.qualityNumber=s}),t.qZA(),t.qZA(),t.TgZ(55,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.qualityNumber=s?a.globalSettings.recorder.qualityNumber:null}),t._uU(56,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(57,"nz-form-item",10),t.TgZ(58,"nz-form-label",23),t._uU(59,"\u4fdd\u5b58\u5c01\u9762"),t.qZA(),t.TgZ(60,"nz-form-control",24),t.TgZ(61,"nz-switch",25),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.saveCover=s}),t.qZA(),t.qZA(),t.TgZ(62,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.saveCover=s?a.globalSettings.recorder.saveCover:null}),t._uU(63,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(64,"nz-form-item",10),t.TgZ(65,"nz-form-label",11),t._uU(66,"\u5c01\u9762\u4fdd\u5b58\u7b56\u7565"),t.qZA(),t.YNc(67,ps,8,0,"ng-template",null,26,t.W1O),t.TgZ(69,"nz-form-control",12),t.TgZ(70,"nz-select",27),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.coverSaveStrategy=s}),t.qZA(),t.qZA(),t.TgZ(71,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.coverSaveStrategy=s?a.globalSettings.recorder.coverSaveStrategy:null}),t._uU(72,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(73,"nz-form-item",10),t.TgZ(74,"nz-form-label",28),t._uU(75,"\u6570\u636e\u8bfb\u53d6\u8d85\u65f6"),t.qZA(),t.TgZ(76,"nz-form-control",29),t.TgZ(77,"nz-select",30,31),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.readTimeout=s}),t.qZA(),t.qZA(),t.TgZ(79,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.readTimeout=s?a.globalSettings.recorder.readTimeout:null}),t._uU(80,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(81,"nz-form-item",10),t.TgZ(82,"nz-form-label",32),t._uU(83,"\u65ad\u7f51\u7b49\u5f85\u65f6\u95f4"),t.qZA(),t.TgZ(84,"nz-form-control",12),t.TgZ(85,"nz-select",33),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.disconnectionTimeout=s}),t.qZA(),t.qZA(),t.TgZ(86,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.bufferSize=s?a.globalSettings.recorder.bufferSize:null}),t._uU(87,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(88,"nz-form-item",10),t.TgZ(89,"nz-form-label",34),t._uU(90,"\u786c\u76d8\u5199\u5165\u7f13\u51b2"),t.qZA(),t.TgZ(91,"nz-form-control",12),t.TgZ(92,"nz-select",35),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.recorder.bufferSize=s}),t.qZA(),t.qZA(),t.TgZ(93,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.recorder.bufferSize=s?a.globalSettings.recorder.bufferSize:null}),t._uU(94,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.qZA(),t.TgZ(95,"div",36),t.TgZ(96,"h2"),t._uU(97,"\u5f39\u5e55"),t.qZA(),t.TgZ(98,"nz-form-item",10),t.TgZ(99,"nz-form-label",37),t._uU(100,"\u8bb0\u5f55\u793c\u7269"),t.qZA(),t.TgZ(101,"nz-form-control",24),t.TgZ(102,"nz-switch",38),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.danmaku.recordGiftSend=s}),t.qZA(),t.qZA(),t.TgZ(103,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.danmaku.recordGiftSend=s?a.globalSettings.danmaku.recordGiftSend:null}),t._uU(104,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(105,"nz-form-item",10),t.TgZ(106,"nz-form-label",39),t._uU(107,"\u8bb0\u5f55\u514d\u8d39\u793c\u7269"),t.qZA(),t.TgZ(108,"nz-form-control",24),t.TgZ(109,"nz-switch",40),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.danmaku.recordFreeGifts=s}),t.qZA(),t.qZA(),t.TgZ(110,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.danmaku.recordFreeGifts=s?a.globalSettings.danmaku.recordFreeGifts:null}),t._uU(111,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(112,"nz-form-item",10),t.TgZ(113,"nz-form-label",41),t._uU(114,"\u8bb0\u5f55\u4e0a\u8230"),t.qZA(),t.TgZ(115,"nz-form-control",24),t.TgZ(116,"nz-switch",42),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.danmaku.recordGuardBuy=s}),t.qZA(),t.qZA(),t.TgZ(117,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.danmaku.recordGuardBuy=s?a.globalSettings.danmaku.recordGuardBuy:null}),t._uU(118,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(119,"nz-form-item",10),t.TgZ(120,"nz-form-label",43),t._uU(121,"\u8bb0\u5f55 Super Chat"),t.qZA(),t.TgZ(122,"nz-form-control",24),t.TgZ(123,"nz-switch",44),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.danmaku.recordSuperChat=s}),t.qZA(),t.qZA(),t.TgZ(124,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.danmaku.recordSuperChat=s?a.globalSettings.danmaku.recordSuperChat:null}),t._uU(125,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(126,"nz-form-item",10),t.TgZ(127,"nz-form-label",45),t._uU(128,"\u5f39\u5e55\u524d\u52a0\u7528\u6237\u540d"),t.qZA(),t.TgZ(129,"nz-form-control",24),t.TgZ(130,"nz-switch",46),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.danmaku.danmuUname=s}),t.qZA(),t.qZA(),t.TgZ(131,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.danmaku.danmuUname=s?a.globalSettings.danmaku.danmuUname:null}),t._uU(132,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(133,"nz-form-item",10),t.TgZ(134,"nz-form-label",47),t._uU(135,"\u4fdd\u5b58\u539f\u59cb\u5f39\u5e55"),t.qZA(),t.TgZ(136,"nz-form-control",24),t.TgZ(137,"nz-switch",48),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.danmaku.saveRawDanmaku=s}),t.qZA(),t.qZA(),t.TgZ(138,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.danmaku.saveRawDanmaku=s?a.globalSettings.danmaku.saveRawDanmaku:null}),t._uU(139,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.qZA(),t.TgZ(140,"div",49),t.TgZ(141,"h2"),t._uU(142,"\u6587\u4ef6\u5904\u7406"),t.qZA(),t.TgZ(143,"nz-form-item",10),t.TgZ(144,"nz-form-label",50),t._uU(145,"flv \u6dfb\u52a0\u5143\u6570\u636e"),t.qZA(),t.TgZ(146,"nz-form-control",24),t.TgZ(147,"nz-switch",51),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.postprocessing.injectExtraMetadata=s}),t.qZA(),t.qZA(),t.TgZ(148,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.postprocessing.injectExtraMetadata=s?a.globalSettings.postprocessing.injectExtraMetadata:null}),t._uU(149,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(150,"nz-form-item",10),t.TgZ(151,"nz-form-label",52),t._uU(152,"flv \u8f6c\u5c01\u88c5\u4e3a mp4"),t.qZA(),t.TgZ(153,"nz-form-control",24),t.TgZ(154,"nz-switch",53),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.postprocessing.remuxToMp4=s}),t.qZA(),t.qZA(),t.TgZ(155,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.postprocessing.remuxToMp4=s?a.globalSettings.postprocessing.remuxToMp4:null}),t._uU(156,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(157,"nz-form-item",10),t.TgZ(158,"nz-form-label",11),t._uU(159,"\u6e90\u6587\u4ef6\u5220\u9664\u7b56\u7565"),t.qZA(),t.YNc(160,gs,7,0,"ng-template",null,54,t.W1O),t.TgZ(162,"nz-form-control",12),t.TgZ(163,"nz-select",55),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.postprocessing.deleteSource=s}),t.qZA(),t.qZA(),t.TgZ(164,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.postprocessing.deleteSource=s?a.globalSettings.postprocessing.deleteSource:null}),t._uU(165,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.qZA(),t.TgZ(166,"div",56),t.TgZ(167,"h2"),t._uU(168,"\u7f51\u7edc\u8bf7\u6c42"),t.qZA(),t.TgZ(169,"nz-form-item",57),t.TgZ(170,"nz-form-label",58),t._uU(171,"User Agent"),t.qZA(),t.TgZ(172,"nz-form-control",59),t.TgZ(173,"textarea",60,61),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.header.userAgent=s}),t.qZA(),t.qZA(),t.YNc(175,ms,1,1,"ng-template",null,8,t.W1O),t.TgZ(177,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.header.userAgent=s?a.globalSettings.header.userAgent:null}),t._uU(178,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.TgZ(179,"nz-form-item",57),t.TgZ(180,"nz-form-label",62),t._uU(181,"Cookie"),t.qZA(),t.TgZ(182,"nz-form-control",63),t.TgZ(183,"textarea",64,65),t.NdJ("ngModelChange",function(s){return t.CHM(e),t.oxw().model.header.cookie=s}),t.qZA(),t.qZA(),t.TgZ(185,"label",9),t.NdJ("nzCheckedChange",function(s){t.CHM(e);const a=t.oxw();return a.options.header.cookie=s?a.globalSettings.header.cookie:null}),t._uU(186,"\u8986\u76d6\u5168\u5c40\u8bbe\u7f6e"),t.qZA(),t.qZA(),t.qZA(),t.qZA(),t.BQk()}if(2&n){const e=t.MAs(11),i=t.MAs(35),s=t.MAs(44),a=t.MAs(68),c=t.MAs(78),p=t.MAs(161),d=t.MAs(174),f=t.MAs(184),r=t.oxw();t.xp6(8),t.Q6J("nzErrorTip",e),t.xp6(1),t.Q6J("pattern",r.pathTemplatePattern)("ngModel",r.model.output.pathTemplate)("disabled",null===r.options.output.pathTemplate),t.xp6(3),t.Q6J("nzChecked",null!==r.options.output.pathTemplate),t.xp6(3),t.Q6J("nzTooltipTitle",r.splitFileTip),t.xp6(3),t.Q6J("ngModel",r.model.output.filesizeLimit)("disabled",null===r.options.output.filesizeLimit)("nzOptions",r.filesizeLimitOptions),t.xp6(1),t.Q6J("nzChecked",null!==r.options.output.filesizeLimit),t.xp6(3),t.Q6J("nzTooltipTitle",r.splitFileTip),t.xp6(3),t.Q6J("ngModel",r.model.output.durationLimit)("disabled",null===r.options.output.durationLimit)("nzOptions",r.durationLimitOptions),t.xp6(1),t.Q6J("nzChecked",null!==r.options.output.durationLimit),t.xp6(6),t.Q6J("nzTooltipTitle",i),t.xp6(5),t.Q6J("ngModel",r.model.recorder.streamFormat)("disabled",null===r.options.recorder.streamFormat)("nzOptions",r.streamFormatOptions),t.xp6(1),t.Q6J("nzChecked",null!==r.options.recorder.streamFormat),t.xp6(3),t.Q6J("nzTooltipTitle",s),t.xp6(5),t.Q6J("ngModel",r.model.recorder.fmp4StreamTimeout)("disabled",null===r.options.recorder.fmp4StreamTimeout)("nzOptions",r.fmp4StreamTimeoutOptions),t.xp6(2),t.Q6J("nzChecked",null!==r.options.recorder.fmp4StreamTimeout),t.xp6(6),t.Q6J("ngModel",r.model.recorder.qualityNumber)("disabled",null===r.options.recorder.qualityNumber)("nzOptions",r.qualityOptions),t.xp6(1),t.Q6J("nzChecked",null!==r.options.recorder.qualityNumber),t.xp6(6),t.Q6J("ngModel",r.model.recorder.saveCover)("disabled",null===r.options.recorder.saveCover),t.xp6(1),t.Q6J("nzChecked",null!==r.options.recorder.saveCover),t.xp6(3),t.Q6J("nzTooltipTitle",a),t.xp6(5),t.Q6J("ngModel",r.model.recorder.coverSaveStrategy)("disabled",null===r.options.recorder.coverSaveStrategy||!r.options.recorder.saveCover)("nzOptions",r.coverSaveStrategies),t.xp6(1),t.Q6J("nzChecked",null!==r.options.recorder.coverSaveStrategy),t.xp6(5),t.Q6J("nzValidateStatus",c.value>3?"warning":c),t.xp6(1),t.Q6J("ngModel",r.model.recorder.readTimeout)("disabled",null===r.options.recorder.readTimeout)("nzOptions",r.readTimeoutOptions),t.xp6(2),t.Q6J("nzChecked",null!==r.options.recorder.readTimeout),t.xp6(6),t.Q6J("ngModel",r.model.recorder.disconnectionTimeout)("disabled",null===r.options.recorder.disconnectionTimeout)("nzOptions",r.disconnectionTimeoutOptions)("nzOptionOverflowSize",6),t.xp6(1),t.Q6J("nzChecked",null!==r.options.recorder.bufferSize),t.xp6(6),t.Q6J("ngModel",r.model.recorder.bufferSize)("disabled",null===r.options.recorder.bufferSize)("nzOptions",r.bufferOptions)("nzOptionOverflowSize",6),t.xp6(1),t.Q6J("nzChecked",null!==r.options.recorder.bufferSize),t.xp6(9),t.Q6J("ngModel",r.model.danmaku.recordGiftSend)("disabled",null===r.options.danmaku.recordGiftSend),t.xp6(1),t.Q6J("nzChecked",null!==r.options.danmaku.recordGiftSend),t.xp6(6),t.Q6J("ngModel",r.model.danmaku.recordFreeGifts)("disabled",null===r.options.danmaku.recordFreeGifts),t.xp6(1),t.Q6J("nzChecked",null!==r.options.danmaku.recordFreeGifts),t.xp6(6),t.Q6J("ngModel",r.model.danmaku.recordGuardBuy)("disabled",null===r.options.danmaku.recordGuardBuy),t.xp6(1),t.Q6J("nzChecked",null!==r.options.danmaku.recordGuardBuy),t.xp6(6),t.Q6J("ngModel",r.model.danmaku.recordSuperChat)("disabled",null===r.options.danmaku.recordSuperChat),t.xp6(1),t.Q6J("nzChecked",null!==r.options.danmaku.recordSuperChat),t.xp6(6),t.Q6J("ngModel",r.model.danmaku.danmuUname)("disabled",null===r.options.danmaku.danmuUname),t.xp6(1),t.Q6J("nzChecked",null!==r.options.danmaku.danmuUname),t.xp6(6),t.Q6J("ngModel",r.model.danmaku.saveRawDanmaku)("disabled",null===r.options.danmaku.saveRawDanmaku),t.xp6(1),t.Q6J("nzChecked",null!==r.options.danmaku.saveRawDanmaku),t.xp6(9),t.Q6J("ngModel",r.model.postprocessing.injectExtraMetadata)("disabled",null===r.options.postprocessing.injectExtraMetadata||!!r.options.postprocessing.remuxToMp4),t.xp6(1),t.Q6J("nzChecked",null!==r.options.postprocessing.injectExtraMetadata),t.xp6(6),t.Q6J("ngModel",r.model.postprocessing.remuxToMp4)("disabled",null===r.options.postprocessing.remuxToMp4),t.xp6(1),t.Q6J("nzChecked",null!==r.options.postprocessing.remuxToMp4),t.xp6(3),t.Q6J("nzTooltipTitle",p),t.xp6(5),t.Q6J("ngModel",r.model.postprocessing.deleteSource)("disabled",null===r.options.postprocessing.deleteSource||!r.options.postprocessing.remuxToMp4)("nzOptions",r.deleteStrategies),t.xp6(1),t.Q6J("nzChecked",null!==r.options.postprocessing.deleteSource),t.xp6(8),t.Q6J("nzWarningTip",r.warningTip)("nzValidateStatus",d.valid&&r.options.header.userAgent!==r.taskOptions.header.userAgent&&r.options.header.userAgent!==r.globalSettings.header.userAgent?"warning":d)("nzErrorTip",e),t.xp6(1),t.Q6J("rows",3)("ngModel",r.model.header.userAgent)("disabled",null===r.options.header.userAgent),t.xp6(4),t.Q6J("nzChecked",null!==r.options.header.userAgent),t.xp6(5),t.Q6J("nzWarningTip",r.warningTip)("nzValidateStatus",f.valid&&r.options.header.cookie!==r.taskOptions.header.cookie&&r.options.header.cookie!==r.globalSettings.header.cookie?"warning":f),t.xp6(1),t.Q6J("rows",3)("ngModel",r.model.header.cookie)("disabled",null===r.options.header.cookie),t.xp6(2),t.Q6J("nzChecked",null!==r.options.header.cookie)}}let hs=(()=>{class n{constructor(e){this.changeDetector=e,this.visible=!1,this.visibleChange=new t.vpe,this.cancel=new t.vpe,this.confirm=new t.vpe,this.afterOpen=new t.vpe,this.afterClose=new t.vpe,this.warningTip="\u9700\u8981\u91cd\u542f\u5f39\u5e55\u5ba2\u6237\u7aef\u624d\u80fd\u751f\u6548\uff0c\u5982\u679c\u4efb\u52a1\u6b63\u5728\u5f55\u5236\u53ef\u80fd\u4f1a\u4e22\u5931\u5f39\u5e55\uff01",this.splitFileTip=w.Uk,this.pathTemplatePattern=w._m,this.filesizeLimitOptions=(0,F.Z)(w.Pu),this.durationLimitOptions=(0,F.Z)(w.Fg),this.streamFormatOptions=(0,F.Z)(w.tp),this.fmp4StreamTimeoutOptions=(0,F.Z)(w.D4),this.qualityOptions=(0,F.Z)(w.O6),this.readTimeoutOptions=(0,F.Z)(w.D4),this.disconnectionTimeoutOptions=(0,F.Z)(w.$w),this.bufferOptions=(0,F.Z)(w.Rc),this.deleteStrategies=(0,F.Z)(w.rc),this.coverSaveStrategies=(0,F.Z)(w.J_)}ngOnChanges(){this.options=(0,F.Z)(this.taskOptions),this.setupModel(),this.changeDetector.markForCheck()}close(){this.setVisible(!1)}setVisible(e){this.visible=e,this.visibleChange.emit(e),this.changeDetector.markForCheck()}handleCancel(){this.cancel.emit(),this.close()}handleConfirm(){this.confirm.emit((0,j.e5)(this.options,this.taskOptions)),this.close()}setupModel(){const e={};for(const i of Object.keys(this.options)){const c=this.globalSettings[i];Reflect.set(e,i,new Proxy(this.options[i],{get:(p,d)=>{var f;return null!==(f=Reflect.get(p,d))&&void 0!==f?f:Reflect.get(c,d)},set:(p,d,f)=>Reflect.set(p,d,f)}))}this.model=e}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-settings-dialog"]],viewQuery:function(e,i){if(1&e&&t.Gf(m.F,5),2&e){let s;t.iGM(s=t.CRH())&&(i.ngForm=s.first)}},inputs:{taskOptions:"taskOptions",globalSettings:"globalSettings",visible:"visible"},outputs:{visibleChange:"visibleChange",cancel:"cancel",confirm:"confirm",afterOpen:"afterOpen",afterClose:"afterClose"},features:[t.TTD],decls:2,vars:2,consts:[["nzTitle","\u4efb\u52a1\u8bbe\u7f6e","nzCentered","",3,"nzVisible","nzOkDisabled","nzOnOk","nzOnCancel","nzAfterOpen","nzAfterClose"],[4,"nzModalContent"],["nz-form","","ngForm",""],["ngModelGroup","output",1,"form-group","output"],[1,"setting-item","input"],["nzNoColon","","nzTooltipTitle","\u53d8\u91cf\u8bf4\u660e\u8bf7\u67e5\u770b\u5bf9\u5e94\u5168\u5c40\u8bbe\u7f6e",1,"setting-label"],[1,"setting-control","input",3,"nzErrorTip"],["type","text","required","","nz-input","","name","pathTemplate",3,"pattern","ngModel","disabled","ngModelChange"],["errorTip",""],["nz-checkbox","",3,"nzChecked","nzCheckedChange"],[1,"setting-item"],["nzNoColon","",1,"setting-label",3,"nzTooltipTitle"],[1,"setting-control","select"],["name","filesizeLimit",3,"ngModel","disabled","nzOptions","ngModelChange"],["name","durationLimit",3,"ngModel","disabled","nzOptions","ngModelChange"],["ngModelGroup","recorder",1,"form-group","recorder"],["streamFormatTip",""],["name","streamFormat",3,"ngModel","disabled","nzOptions","ngModelChange"],["fmp4StreamTimeoutTip",""],["name","fmp4StreamTimeout",3,"ngModel","disabled","nzOptions","ngModelChange"],["fmp4StreamTimeout","ngModel"],["nzNoColon","","nzTooltipTitle","\u6240\u9009\u753b\u8d28\u4e0d\u5b58\u5728\u5c06\u4ee5\u539f\u753b\u4ee3\u66ff",1,"setting-label"],["name","qualityNumber",3,"ngModel","disabled","nzOptions","ngModelChange"],["nzNoColon","","nzTooltipTitle","\u5f55\u64ad\u6587\u4ef6\u5b8c\u6210\u65f6\u4fdd\u5b58\u5f53\u524d\u76f4\u64ad\u95f4\u7684\u5c01\u9762",1,"setting-label"],[1,"setting-control","switch"],["name","saveCover",3,"ngModel","disabled","ngModelChange"],["coverSaveStrategyTip",""],["name","coverSaveStrategy",3,"ngModel","disabled","nzOptions","ngModelChange"],["nzNoColon","","nzTooltipTitle","\u8d85\u65f6\u65f6\u95f4\u8bbe\u7f6e\u5f97\u6bd4\u8f83\u957f\u76f8\u5bf9\u4e0d\u5bb9\u6613\u56e0\u7f51\u7edc\u4e0d\u7a33\u5b9a\u800c\u51fa\u73b0\u6d41\u4e2d\u65ad\uff0c\u4f46\u662f\u4e00\u65e6\u51fa\u73b0\u4e2d\u65ad\u5c31\u65e0\u6cd5\u5b9e\u73b0\u65e0\u7f1d\u62fc\u63a5\u4e14\u6f0f\u5f55\u8f83\u591a\u3002",1,"setting-label"],["nzWarningTip","\u65e0\u7f1d\u62fc\u63a5\u4f1a\u5931\u6548\uff01",1,"setting-control","select",3,"nzValidateStatus"],["name","readTimeout",3,"ngModel","disabled","nzOptions","ngModelChange"],["readTimeout","ngModel"],["nzNoColon","","nzTooltipTitle","\u65ad\u7f51\u8d85\u8fc7\u7b49\u5f85\u65f6\u95f4\u5c31\u7ed3\u675f\u5f55\u5236\uff0c\u5982\u679c\u7f51\u7edc\u6062\u590d\u540e\u4ecd\u672a\u4e0b\u64ad\u4f1a\u81ea\u52a8\u91cd\u65b0\u5f00\u59cb\u5f55\u5236\u3002",1,"setting-label"],["name","disconnectionTimeout",3,"ngModel","disabled","nzOptions","nzOptionOverflowSize","ngModelChange"],["nzNoColon","","nzTooltipTitle","\u786c\u76d8\u5199\u5165\u7f13\u51b2\u8bbe\u7f6e\u5f97\u6bd4\u8f83\u5927\u53ef\u4ee5\u51cf\u5c11\u5bf9\u786c\u76d8\u7684\u5199\u5165\uff0c\u4f46\u9700\u8981\u5360\u7528\u66f4\u591a\u7684\u5185\u5b58\u3002",1,"setting-label"],["name","bufferSize",3,"ngModel","disabled","nzOptions","nzOptionOverflowSize","ngModelChange"],["ngModelGroup","danmaku",1,"form-group","danmaku"],["nzFor","recordGiftSend","nzNoColon","","nzTooltipTitle","\u8bb0\u5f55\u793c\u7269\u4fe1\u606f\u5230\u5f39\u5e55\u6587\u4ef6\u91cc",1,"setting-label"],["id","recordGiftSend","name","recordGiftSend",3,"ngModel","disabled","ngModelChange"],["nzFor","recordFreeGifts","nzNoColon","","nzTooltipTitle","\u8bb0\u5f55\u514d\u8d39\u793c\u7269\u4fe1\u606f\u5230\u5f39\u5e55\u6587\u4ef6\u91cc",1,"setting-label"],["id","recordFreeGifts","name","recordFreeGifts",3,"ngModel","disabled","ngModelChange"],["nzFor","recordGuardBuy","nzNoColon","","nzTooltipTitle","\u8bb0\u5f55\u4e0a\u8230\u4fe1\u606f\u5230\u5f39\u5e55\u6587\u4ef6\u91cc",1,"setting-label"],["id","recordGuardBuy","name","recordGuardBuy",3,"ngModel","disabled","ngModelChange"],["nzFor","recordSuperChat","nzNoColon","","nzTooltipTitle","\u8bb0\u5f55 Super Chat \u4fe1\u606f\u5230\u5f39\u5e55\u6587\u4ef6\u91cc",1,"setting-label"],["id","recordSuperChat","name","recordSuperChat",3,"ngModel","disabled","ngModelChange"],["nzFor","danmuUname","nzNoColon","","nzTooltipTitle","\u53d1\u9001\u8005: \u5f39\u5e55\u5185\u5bb9",1,"setting-label"],["id","danmuUname","name","danmuUname",3,"ngModel","disabled","ngModelChange"],["nzFor","saveRawDanmaku","nzNoColon","","nzTooltipTitle","\u4fdd\u5b58\u539f\u59cb\u5f39\u5e55\u5230 JSON lines \u6587\u4ef6\uff0c\u4e3b\u8981\u7528\u4e8e\u5206\u6790\u8c03\u8bd5\u3002",1,"setting-label"],["id","saveRawDanmaku","name","saveRawDanmaku",3,"ngModel","disabled","ngModelChange"],["ngModelGroup","postprocessing",1,"form-group","postprocessing"],["nzNoColon","","nzTooltipTitle","\u6dfb\u52a0\u5173\u952e\u5e27\u7b49\u5143\u6570\u636e\u4f7f\u5b9a\u4f4d\u64ad\u653e\u548c\u62d6\u8fdb\u5ea6\u6761\u4e0d\u4f1a\u5361\u987f",1,"setting-label"],["name","injectExtraMetadata",3,"ngModel","disabled","ngModelChange"],["nzNoColon","","nzTooltipTitle","\u8c03\u7528 ffmpeg \u8fdb\u884c\u8f6c\u6362\uff0c\u9700\u8981\u5b89\u88c5 ffmpeg \u3002",1,"setting-label"],["name","remuxToMp4",3,"ngModel","disabled","ngModelChange"],["deleteSourceTip",""],["name","deleteSource",3,"ngModel","disabled","nzOptions","ngModelChange"],["ngModelGroup","header",1,"form-group","header"],[1,"setting-item","textarea"],["nzFor","userAgent","nzNoColon","",1,"setting-label"],[1,"setting-control","textarea",3,"nzWarningTip","nzValidateStatus","nzErrorTip"],["nz-input","","required","","id","userAgent","name","userAgent",3,"rows","ngModel","disabled","ngModelChange"],["userAgent","ngModel"],["nzFor","cookie","nzNoColon","",1,"setting-label"],[1,"setting-control","textarea",3,"nzWarningTip","nzValidateStatus"],["nz-input","","id","cookie","name","cookie",3,"rows","ngModel","disabled","ngModelChange"],["cookie","ngModel"],[4,"ngIf"]],template:function(e,i){1&e&&(t.TgZ(0,"nz-modal",0),t.NdJ("nzOnOk",function(){return i.handleConfirm()})("nzOnCancel",function(){return i.handleCancel()})("nzAfterOpen",function(){return i.afterOpen.emit()})("nzAfterClose",function(){return i.afterClose.emit()}),t.YNc(1,_s,187,94,"ng-container",1),t.qZA()),2&e&&t.Q6J("nzVisible",i.visible)("nzOkDisabled",null==i.ngForm||null==i.ngForm.form?null:i.ngForm.form.invalid)},directives:[V.du,V.Hf,m._Y,m.JL,m.F,B.Lr,m.Mq,P.SK,B.Nx,P.t3,B.iK,B.Fd,et.Zp,m.Fj,m.Q7,m.c5,m.JJ,m.On,u.O5,Wt.Ie,wt.Vq,Ot.i],styles:[".settings-page[_ngcontent-%COMP%]{max-width:680px;margin:0 auto}.setting-item[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap;align-items:center;margin:0;padding:1em 2em;border-top:1px solid rgba(0,0,0,.06)}.setting-item[_ngcontent-%COMP%]:first-child{border-top:none}.settings-page-content[_ngcontent-%COMP%]{display:flex;flex-direction:column;width:100%}.form-group[_ngcontent-%COMP%] h2[_ngcontent-%COMP%]{margin:0;font-weight:700;font-size:1.2em;border-bottom:1px solid rgba(0,0,0,.1)}.setting-item[_ngcontent-%COMP%]{gap:.5em}.setting-item.actionable[_ngcontent-%COMP%]{cursor:pointer}.setting-item.actionable[_ngcontent-%COMP%]:hover{background-color:#2021241a}a.setting-item[_ngcontent-%COMP%]{outline:none;text-decoration:none;color:inherit;cursor:pointer}a.setting-item[_ngcontent-%COMP%]:hover{background-color:#2021241a}.setting-label[_ngcontent-%COMP%]{padding:0!important;max-width:-moz-fit-content!important;max-width:fit-content!important;text-align:left}.setting-label.align-required[_ngcontent-%COMP%]{position:relative;left:11px}.setting-control[_ngcontent-%COMP%], .setting-value[_ngcontent-%COMP%]{max-width:-moz-fit-content!important;max-width:fit-content!important;margin-left:auto!important}.setting-control.input[_ngcontent-%COMP%], .setting-value.input[_ngcontent-%COMP%]{max-width:100%!important}.setting-control.select[_ngcontent-%COMP%], .setting-value.select[_ngcontent-%COMP%]{max-width:8em!important;width:8em!important}.setting-control.switch[_ngcontent-%COMP%], .setting-value.switch[_ngcontent-%COMP%]{flex-direction:row-reverse;align-items:center}.setting-control.checkbox[_ngcontent-%COMP%], .setting-value.checkbox[_ngcontent-%COMP%]{max-width:100%!important;width:100%!important;margin-left:0}.setting-control.checkbox[_ngcontent-%COMP%] label[_ngcontent-%COMP%], .setting-value.checkbox[_ngcontent-%COMP%] label[_ngcontent-%COMP%]{width:100%}.setting-control.textarea[_ngcontent-%COMP%], .setting-value.textarea[_ngcontent-%COMP%]{max-width:100%!important;width:100%!important;margin-left:0}@media screen and (max-width: 332px){.setting-control.radio[_ngcontent-%COMP%], .setting-value.radio[_ngcontent-%COMP%]{margin-left:0!important}}@media screen and (max-width: 319px){.setting-control.select[_ngcontent-%COMP%], .setting-value.select[_ngcontent-%COMP%]{margin-left:0!important}}.setting-value[_ngcontent-%COMP%]{color:#5f6368;font-weight:400}nz-divider[_ngcontent-%COMP%]{margin:0!important}.form-group[_ngcontent-%COMP%]:last-child .setting-item[_ngcontent-%COMP%]:last-child{padding-bottom:0}.setting-item[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(3,1fr);align-items:center;padding:1em 0;grid-gap:1em;gap:1em;border:none}.setting-item[_ngcontent-%COMP%] > *[_ngcontent-%COMP%]{margin:0!important}.setting-item[_ngcontent-%COMP%] .setting-label[_ngcontent-%COMP%]{justify-self:start}.setting-item[_ngcontent-%COMP%] .setting-control[_ngcontent-%COMP%]{justify-self:center}.setting-item[_ngcontent-%COMP%] label[nz-checkbox][_ngcontent-%COMP%]{justify-self:end}.setting-item[_ngcontent-%COMP%] label[nz-checkbox][_ngcontent-%COMP%] span:last-of-type{padding-right:0}.setting-item.input[_ngcontent-%COMP%], .setting-item.textarea[_ngcontent-%COMP%]{grid-template-columns:repeat(2,1fr)}.setting-item.input[_ngcontent-%COMP%] .setting-label[_ngcontent-%COMP%], .setting-item.textarea[_ngcontent-%COMP%] .setting-label[_ngcontent-%COMP%]{grid-row:1/2;grid-column:1/2;justify-self:center}.setting-item.input[_ngcontent-%COMP%] .setting-control[_ngcontent-%COMP%], .setting-item.textarea[_ngcontent-%COMP%] .setting-control[_ngcontent-%COMP%]{grid-row:2/3;grid-column:1/-1;justify-self:stretch}.setting-item.input[_ngcontent-%COMP%] label[nz-checkbox][_ngcontent-%COMP%], .setting-item.textarea[_ngcontent-%COMP%] label[nz-checkbox][_ngcontent-%COMP%]{grid-row:1/2;grid-column:2/3;justify-self:center}@media screen and (max-width: 450px){.setting-item[_ngcontent-%COMP%]{grid-template-columns:repeat(2,1fr)}.setting-item[_ngcontent-%COMP%] .setting-label[_ngcontent-%COMP%]{grid-column:1/-1;justify-self:center}.setting-item[_ngcontent-%COMP%] label[nz-checkbox][_ngcontent-%COMP%]{justify-self:end}}"],changeDetection:0}),n})();function Ce(n,o,e,i,s,a,c){try{var p=n[a](c),d=p.value}catch(f){return void e(f)}p.done?o(d):Promise.resolve(d).then(i,s)}var Gt=l(5254),zs=l(3753),Cs=l(2313);const $t=new Map,Vt=new Map;let Ts=(()=>{class n{constructor(e){this.domSanitizer=e}transform(e,i="object"){return"object"===i?Vt.has(e)?(0,A.of)(Vt.get(e)):(0,Gt.D)(this.fetchImage(e)).pipe((0,H.U)(s=>URL.createObjectURL(s)),(0,H.U)(s=>this.domSanitizer.bypassSecurityTrustUrl(s)),(0,k.b)(s=>Vt.set(e,s)),(0,it.K)(()=>(0,A.of)(this.domSanitizer.bypassSecurityTrustUrl("")))):$t.has(e)?(0,A.of)($t.get(e)):(0,Gt.D)(this.fetchImage(e)).pipe((0,nt.w)(s=>this.createDataURL(s)),(0,k.b)(s=>$t.set(e,s)),(0,it.K)(()=>(0,A.of)(this.domSanitizer.bypassSecurityTrustUrl(""))))}fetchImage(e){return function fs(n){return function(){var o=this,e=arguments;return new Promise(function(i,s){var a=n.apply(o,e);function c(d){Ce(a,i,s,c,p,"next",d)}function p(d){Ce(a,i,s,c,p,"throw",d)}c(void 0)})}}(function*(){return yield(yield fetch(e,{referrer:""})).blob()})()}createDataURL(e){const i=new FileReader,s=(0,zs.R)(i,"load").pipe((0,H.U)(()=>this.domSanitizer.bypassSecurityTrustUrl(i.result)));return i.readAsDataURL(e),s}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(Cs.H7,16))},n.\u0275pipe=t.Yjl({name:"dataurl",type:n,pure:!0}),n})();function xs(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"app-info-panel",21),t.NdJ("close",function(){return t.CHM(e),t.oxw(2).showInfoPanel=!1}),t.qZA()}if(2&n){const e=t.oxw(2);t.Q6J("data",e.data)}}const ks=function(n){return[n,"detail"]};function vs(n,o){if(1&n&&(t.TgZ(0,"a",15),t.TgZ(1,"div",16),t._UZ(2,"img",17),t.ALo(3,"async"),t.ALo(4,"dataurl"),t.TgZ(5,"h2",18),t._uU(6),t.qZA(),t.YNc(7,xs,1,1,"app-info-panel",19),t._UZ(8,"app-status-display",20),t.qZA(),t.qZA()),2&n){const e=t.oxw();t.Q6J("routerLink",t.VKq(10,ks,e.data.room_info.room_id)),t.xp6(2),t.Q6J("src",t.lcZ(3,6,t.lcZ(4,8,e.data.room_info.cover)),t.LSH),t.xp6(3),t.Q6J("nzTooltipTitle","\u76f4\u64ad\u95f4\u6807\u9898\uff1a"+e.data.room_info.title),t.xp6(1),t.hij(" ",e.data.room_info.title," "),t.xp6(1),t.Q6J("ngIf",e.showInfoPanel),t.xp6(1),t.Q6J("status",e.data.task_status)}}function ys(n,o){if(1&n&&(t._UZ(0,"nz-avatar",22),t.ALo(1,"async"),t.ALo(2,"dataurl")),2&n){const e=t.oxw();t.Q6J("nzShape","square")("nzSize",54)("nzSrc",t.lcZ(1,3,t.lcZ(2,5,e.data.user_info.face)))}}function bs(n,o){1&n&&(t.TgZ(0,"nz-tag",31),t._UZ(1,"i",32),t.TgZ(2,"span"),t._uU(3,"\u672a\u5f00\u64ad"),t.qZA(),t.qZA())}function Ss(n,o){1&n&&(t.TgZ(0,"nz-tag",33),t._UZ(1,"i",34),t.TgZ(2,"span"),t._uU(3,"\u76f4\u64ad\u4e2d"),t.qZA(),t.qZA())}function As(n,o){1&n&&(t.TgZ(0,"nz-tag",35),t._UZ(1,"i",36),t.TgZ(2,"span"),t._uU(3,"\u8f6e\u64ad\u4e2d"),t.qZA(),t.qZA())}function Ds(n,o){if(1&n&&(t.TgZ(0,"p",23),t.TgZ(1,"span",24),t.TgZ(2,"a",25),t._uU(3),t.qZA(),t.qZA(),t.TgZ(4,"span",26),t.ynx(5,27),t.YNc(6,bs,4,0,"nz-tag",28),t.YNc(7,Ss,4,0,"nz-tag",29),t.YNc(8,As,4,0,"nz-tag",30),t.BQk(),t.qZA(),t.qZA()),2&n){const e=t.oxw();t.xp6(2),t.MGl("href","https://space.bilibili.com/",e.data.user_info.uid,"",t.LSH),t.xp6(1),t.hij(" ",e.data.user_info.name," "),t.xp6(2),t.Q6J("ngSwitch",e.data.room_info.live_status),t.xp6(1),t.Q6J("ngSwitchCase",0),t.xp6(1),t.Q6J("ngSwitchCase",1),t.xp6(1),t.Q6J("ngSwitchCase",2)}}function Ms(n,o){if(1&n&&(t.TgZ(0,"span",44),t.TgZ(1,"a",25),t._uU(2),t.qZA(),t.qZA()),2&n){const e=t.oxw(2);t.xp6(1),t.MGl("href","https://live.bilibili.com/",e.data.room_info.short_room_id,"",t.LSH),t.xp6(1),t.hij(" ",e.data.room_info.short_room_id,"")}}function Zs(n,o){if(1&n&&(t.TgZ(0,"p",37),t.TgZ(1,"span",38),t.TgZ(2,"span",39),t._uU(3,"\u623f\u95f4\u53f7\uff1a"),t.qZA(),t.YNc(4,Ms,3,2,"span",40),t.TgZ(5,"span",41),t.TgZ(6,"a",25),t._uU(7),t.qZA(),t.qZA(),t.qZA(),t.TgZ(8,"span",42),t.TgZ(9,"a",25),t.TgZ(10,"nz-tag",43),t._uU(11),t.qZA(),t.qZA(),t.qZA(),t.qZA()),2&n){const e=t.oxw();t.xp6(4),t.Q6J("ngIf",e.data.room_info.short_room_id),t.xp6(2),t.MGl("href","https://live.bilibili.com/",e.data.room_info.room_id,"",t.LSH),t.xp6(1),t.Oqu(e.data.room_info.room_id),t.xp6(2),t.hYB("href","https://live.bilibili.com/p/eden/area-tags?parentAreaId=",e.data.room_info.parent_area_id,"&areaId=",e.data.room_info.area_id,"",t.LSH),t.xp6(1),t.Q6J("nzColor","#23ade5"),t.xp6(1),t.hij(" ",e.data.room_info.area_name," ")}}function Os(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"nz-switch",45),t.NdJ("click",function(){return t.CHM(e),t.oxw().toggleRecorder()}),t.qZA()}if(2&n){const e=t.oxw();t.Q6J("nzDisabled",e.toggleRecorderForbidden)("ngModel",e.data.task_status.recorder_enabled)("nzControl",!0)("nzLoading",e.switchPending)}}function ws(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"div",46),t.NdJ("click",function(){return t.CHM(e),t.oxw().cutStream()}),t._UZ(1,"i",47),t.qZA()}if(2&n){const e=t.oxw();t.ekj("not-allowed",e.data.task_status.running_status!==e.RunningStatus.RECORDING)}}function Fs(n,o){if(1&n){const e=t.EpF();t.ynx(0),t.TgZ(1,"app-task-settings-dialog",51),t.NdJ("visibleChange",function(s){return t.CHM(e),t.oxw(2).settingsDialogVisible=s})("confirm",function(s){return t.CHM(e),t.oxw(2).changeTaskOptions(s)})("afterClose",function(){return t.CHM(e),t.oxw(2).cleanSettingsData()}),t.qZA(),t.BQk()}if(2&n){const e=t.oxw(2);t.xp6(1),t.Q6J("taskOptions",e.taskOptions)("globalSettings",e.globalSettings)("visible",e.settingsDialogVisible)}}function Ns(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"div",48),t.NdJ("click",function(){return t.CHM(e),t.oxw().openSettingsDialog()}),t._UZ(1,"i",49),t.qZA(),t.YNc(2,Fs,2,3,"ng-container",50)}if(2&n){const e=t.oxw();t.xp6(2),t.Q6J("ngIf",e.taskOptions&&e.globalSettings)}}function Ps(n,o){if(1&n&&(t.TgZ(0,"div",54),t._UZ(1,"i",55),t.qZA()),2&n){t.oxw(2);const e=t.MAs(20);t.Q6J("nzDropdownMenu",e)}}function Es(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"div",56),t.NdJ("click",function(){return t.CHM(e),t.oxw(2).menuDrawerVisible=!0}),t._UZ(1,"i",55),t.qZA()}}function Is(n,o){if(1&n&&(t.YNc(0,Ps,2,1,"div",52),t.YNc(1,Es,2,0,"div",53)),2&n){const e=t.oxw();t.Q6J("ngIf",!e.useDrawer),t.xp6(1),t.Q6J("ngIf",e.useDrawer)}}function Bs(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"ul",57),t.TgZ(1,"li",58),t.NdJ("click",function(){return t.CHM(e),t.oxw().startTask()}),t._uU(2,"\u8fd0\u884c\u4efb\u52a1"),t.qZA(),t.TgZ(3,"li",58),t.NdJ("click",function(){return t.CHM(e),t.oxw().stopTask()}),t._uU(4,"\u505c\u6b62\u4efb\u52a1"),t.qZA(),t.TgZ(5,"li",58),t.NdJ("click",function(){return t.CHM(e),t.oxw().removeTask()}),t._uU(6,"\u5220\u9664\u4efb\u52a1"),t.qZA(),t.TgZ(7,"li",58),t.NdJ("click",function(){return t.CHM(e),t.oxw().stopTask(!0)}),t._uU(8,"\u5f3a\u5236\u505c\u6b62\u4efb\u52a1"),t.qZA(),t.TgZ(9,"li",58),t.NdJ("click",function(){return t.CHM(e),t.oxw().disableRecorder(!0)}),t._uU(10,"\u5f3a\u5236\u5173\u95ed\u5f55\u5236"),t.qZA(),t.TgZ(11,"li",58),t.NdJ("click",function(){return t.CHM(e),t.oxw().updateTaskInfo()}),t._uU(12,"\u5237\u65b0\u6570\u636e"),t.qZA(),t.TgZ(13,"li",58),t.NdJ("click",function(){return t.CHM(e),t.oxw().showInfoPanel=!0}),t._uU(14,"\u663e\u793a\u5f55\u5236\u4fe1\u606f"),t.qZA(),t.qZA()}}function Js(n,o){if(1&n){const e=t.EpF();t.ynx(0),t.TgZ(1,"div",61),t.NdJ("click",function(){return t.CHM(e),t.oxw(2).menuDrawerVisible=!1}),t.GkF(2,12),t.qZA(),t.BQk()}if(2&n){t.oxw(2);const e=t.MAs(23);t.xp6(2),t.Q6J("ngTemplateOutlet",e)}}const Qs=function(){return{padding:"0"}};function Us(n,o){if(1&n){const e=t.EpF();t.TgZ(0,"nz-drawer",59),t.NdJ("nzVisibleChange",function(s){return t.CHM(e),t.oxw().menuDrawerVisible=s})("nzOnClose",function(){return t.CHM(e),t.oxw().menuDrawerVisible=!1}),t.YNc(1,Js,3,1,"ng-container",60),t.qZA()}if(2&n){const e=t.oxw();t.Q6J("nzClosable",!1)("nzBodyStyle",t.DdM(3,Qs))("nzVisible",e.menuDrawerVisible)}}const qs=function(n,o,e,i){return[n,o,e,i]},Rs=function(){return{padding:"0.5rem"}},Ls=function(){return{size:"large"}};let Ys=(()=>{class n{constructor(e,i,s,a,c,p,d){this.changeDetector=i,this.message=s,this.modal=a,this.settingService=c,this.taskManager=p,this.appTaskSettings=d,this.stopped=!1,this.destroyed=new v.xQ,this.useDrawer=!1,this.menuDrawerVisible=!1,this.switchPending=!1,this.settingsDialogVisible=!1,this.RunningStatus=O,e.observe(pt[0]).pipe((0,y.R)(this.destroyed)).subscribe(f=>{this.useDrawer=f.matches,i.markForCheck()})}get roomId(){return this.data.room_info.room_id}get toggleRecorderForbidden(){return!this.data.task_status.monitor_enabled}get showInfoPanel(){return Boolean(this.appTaskSettings.getSettings(this.roomId).showInfoPanel)}set showInfoPanel(e){this.appTaskSettings.updateSettings(this.roomId,{showInfoPanel:e})}ngOnChanges(e){console.debug("[ngOnChanges]",this.roomId,e),this.stopped=this.data.task_status.running_status===O.STOPPED}ngOnDestroy(){this.destroyed.next(),this.destroyed.complete()}updateTaskInfo(){this.taskManager.updateTaskInfo(this.roomId).subscribe()}toggleRecorder(){this.toggleRecorderForbidden||this.switchPending||(this.switchPending=!0,this.data.task_status.recorder_enabled?this.taskManager.disableRecorder(this.roomId).subscribe(()=>this.switchPending=!1):this.taskManager.enableRecorder(this.roomId).subscribe(()=>this.switchPending=!1))}removeTask(){this.taskManager.removeTask(this.roomId).subscribe()}startTask(){this.data.task_status.running_status===O.STOPPED?this.taskManager.startTask(this.roomId).subscribe():this.message.warning("\u4efb\u52a1\u8fd0\u884c\u4e2d\uff0c\u5ffd\u7565\u64cd\u4f5c\u3002")}stopTask(e=!1){this.data.task_status.running_status!==O.STOPPED?e&&this.data.task_status.running_status==O.RECORDING?this.modal.confirm({nzTitle:"\u786e\u5b9a\u8981\u5f3a\u5236\u505c\u6b62\u4efb\u52a1\uff1f",nzContent:"\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\u4f1a\u88ab\u5f3a\u884c\u4e2d\u65ad\uff01\u786e\u5b9a\u8981\u653e\u5f03\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\uff1f",nzOnOk:()=>new Promise((i,s)=>{this.taskManager.stopTask(this.roomId,e).subscribe(i,s)})}):this.taskManager.stopTask(this.roomId).subscribe():this.message.warning("\u4efb\u52a1\u5904\u4e8e\u505c\u6b62\u72b6\u6001\uff0c\u5ffd\u7565\u64cd\u4f5c\u3002")}disableRecorder(e=!1){this.data.task_status.recorder_enabled?e&&this.data.task_status.running_status==O.RECORDING?this.modal.confirm({nzTitle:"\u786e\u5b9a\u8981\u5f3a\u5236\u505c\u6b62\u5f55\u5236\uff1f",nzContent:"\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\u4f1a\u88ab\u5f3a\u884c\u4e2d\u65ad\uff01\u786e\u5b9a\u8981\u653e\u5f03\u6b63\u5728\u5f55\u5236\u7684\u6587\u4ef6\uff1f",nzOnOk:()=>new Promise((i,s)=>{this.taskManager.disableRecorder(this.roomId,e).subscribe(i,s)})}):this.taskManager.disableRecorder(this.roomId).subscribe():this.message.warning("\u5f55\u5236\u5904\u4e8e\u5173\u95ed\u72b6\u6001\uff0c\u5ffd\u7565\u64cd\u4f5c\u3002")}openSettingsDialog(){It(this.settingService.getTaskOptions(this.roomId),this.settingService.getSettings(["output","header","danmaku","recorder","postprocessing"])).subscribe(([e,i])=>{this.taskOptions=e,this.globalSettings=i,this.settingsDialogVisible=!0,this.changeDetector.markForCheck()},e=>{this.message.error(`\u83b7\u53d6\u4efb\u52a1\u8bbe\u7f6e\u51fa\u9519: ${e.message}`)})}cleanSettingsData(){delete this.taskOptions,delete this.globalSettings,this.changeDetector.markForCheck()}changeTaskOptions(e){this.settingService.changeTaskOptions(this.roomId,e).pipe((0,bt.X)(3,300)).subscribe(i=>{this.message.success("\u4fee\u6539\u4efb\u52a1\u8bbe\u7f6e\u6210\u529f")},i=>{this.message.error(`\u4fee\u6539\u4efb\u52a1\u8bbe\u7f6e\u51fa\u9519: ${i.message}`)})}cutStream(){this.data.task_status.running_status===O.RECORDING&&this.taskManager.cutStream(this.roomId).subscribe()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(U.Yg),t.Y36(t.sBO),t.Y36(qt.dD),t.Y36(V.Sf),t.Y36(jo.R),t.Y36(Rt),t.Y36(Ho))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-item"]],hostVars:2,hostBindings:function(e,i){2&e&&t.ekj("stopped",i.stopped)},inputs:{data:"data"},features:[t.TTD],decls:25,vars:19,consts:[[3,"nzCover","nzHoverable","nzActions","nzBodyStyle"],[3,"nzActive","nzLoading","nzAvatar"],[3,"nzAvatar","nzTitle","nzDescription"],["coverTemplate",""],["avatarTemplate",""],["titleTemplate",""],["descTemplate",""],["actionSwitch",""],["actionDelete",""],["actionSetting",""],["actionMore",""],["dropdownMenu","nzDropdownMenu"],[3,"ngTemplateOutlet"],["menu",""],["nzPlacement","bottom","nzHeight","auto",3,"nzClosable","nzBodyStyle","nzVisible","nzVisibleChange","nzOnClose",4,"ngIf"],[3,"routerLink"],[1,"cover-wrapper"],["alt","\u76f4\u64ad\u95f4\u5c01\u9762",1,"cover",3,"src"],["nz-tooltip","","nzTooltipPlacement","bottomLeft",1,"title",3,"nzTooltipTitle"],[3,"data","close",4,"ngIf"],[3,"status"],[3,"data","close"],[3,"nzShape","nzSize","nzSrc"],[1,"meta-title"],["nz-tooltip","","nzTooltipTitle","\u6253\u5f00\u4e3b\u64ad\u4e2a\u4eba\u7a7a\u95f4\u9875\u9762","nzTooltipPlacement","right",1,"user-name"],["target","_blank",3,"href"],[1,"live-status"],[3,"ngSwitch"],["nzColor","default",4,"ngSwitchCase"],["nzColor","red",4,"ngSwitchCase"],["nzColor","green",4,"ngSwitchCase"],["nzColor","default"],["nz-icon","","nzType","frown"],["nzColor","red"],["nz-icon","","nzType","fire"],["nzColor","green"],["nz-icon","","nzType","sync","nzSpin",""],[1,"meta-desc"],[1,"room-id-wrapper"],[1,"room-id-label"],["class","short-room-id","nz-tooltip","","nzTooltipTitle","\u6253\u5f00\u76f4\u64ad\u95f4\u9875\u9762","nzTooltipPlacement","bottom",4,"ngIf"],["nz-tooltip","","nzTooltipTitle","\u6253\u5f00\u76f4\u64ad\u95f4\u9875\u9762","nzTooltipPlacement","bottom",1,"real-room-id"],["nz-tooltip","","nzTooltipTitle","\u6253\u5f00\u76f4\u64ad\u5206\u533a\u9875\u9762","nzTooltipPlacement","leftTop",1,"area-name"],[3,"nzColor"],["nz-tooltip","","nzTooltipTitle","\u6253\u5f00\u76f4\u64ad\u95f4\u9875\u9762","nzTooltipPlacement","bottom",1,"short-room-id"],["nz-tooltip","","nzTooltipTitle","\u5f55\u5236\u5f00\u5173",3,"nzDisabled","ngModel","nzControl","nzLoading","click"],["nz-tooltip","","nzTooltipTitle","\u5207\u5272\u6587\u4ef6",3,"click"],["nz-icon","","nzType","scissor",1,"action-icon"],["nz-tooltip","","nzTooltipTitle","\u4efb\u52a1\u8bbe\u7f6e",3,"click"],["nz-icon","","nzType","setting",1,"action-icon"],[4,"ngIf"],[3,"taskOptions","globalSettings","visible","visibleChange","confirm","afterClose"],["nz-dropdown","","nzPlacement","topRight",3,"nzDropdownMenu",4,"ngIf"],[3,"click",4,"ngIf"],["nz-dropdown","","nzPlacement","topRight",3,"nzDropdownMenu"],["nz-icon","","nzType","more",1,"action-icon"],[3,"click"],["nz-menu","",1,"menu"],["nz-menu-item","",3,"click"],["nzPlacement","bottom","nzHeight","auto",3,"nzClosable","nzBodyStyle","nzVisible","nzVisibleChange","nzOnClose"],[4,"nzDrawerContent"],[1,"drawer-content",3,"click"]],template:function(e,i){if(1&e&&(t.TgZ(0,"nz-card",0),t.TgZ(1,"nz-skeleton",1),t._UZ(2,"nz-card-meta",2),t.qZA(),t.qZA(),t.YNc(3,vs,9,12,"ng-template",null,3,t.W1O),t.YNc(5,ys,3,7,"ng-template",null,4,t.W1O),t.YNc(7,Ds,9,6,"ng-template",null,5,t.W1O),t.YNc(9,Zs,12,7,"ng-template",null,6,t.W1O),t.YNc(11,Os,1,4,"ng-template",null,7,t.W1O),t.YNc(13,ws,2,2,"ng-template",null,8,t.W1O),t.YNc(15,Ns,3,1,"ng-template",null,9,t.W1O),t.YNc(17,Is,2,2,"ng-template",null,10,t.W1O),t.TgZ(19,"nz-dropdown-menu",null,11),t.GkF(21,12),t.YNc(22,Bs,15,0,"ng-template",null,13,t.W1O),t.qZA(),t.YNc(24,Us,2,4,"nz-drawer",14)),2&e){const s=t.MAs(4),a=t.MAs(6),c=t.MAs(8),p=t.MAs(10),d=t.MAs(12),f=t.MAs(14),r=t.MAs(16),N=t.MAs(18),I=t.MAs(23);t.Q6J("nzCover",s)("nzHoverable",!0)("nzActions",t.l5B(12,qs,f,r,d,N))("nzBodyStyle",t.DdM(17,Rs)),t.xp6(1),t.Q6J("nzActive",!0)("nzLoading",!i.data)("nzAvatar",t.DdM(18,Ls)),t.xp6(1),t.Q6J("nzAvatar",a)("nzTitle",c)("nzDescription",p),t.xp6(19),t.Q6J("ngTemplateOutlet",I),t.xp6(3),t.Q6J("ngIf",i.useDrawer)}},directives:[D.bd,ye,D.l7,ct.yS,rt.SY,u.O5,es,ss,at.Dz,u.RF,u.n9,st,M.Ls,Lt.w,Ot.i,m.JJ,m.On,hs,tt.cm,tt.RR,u.tP,gt.wO,gt.r9,lt.Vz,lt.SQ],pipes:[u.Ov,Ts],styles:['.cover-wrapper[_ngcontent-%COMP%] .title[_ngcontent-%COMP%]{color:#fff;text-shadow:1px 1px 2px black;margin:0;padding:0 .5rem;background:rgba(0,0,0,.32)}.drawer-content[_ngcontent-%COMP%] .menu[_ngcontent-%COMP%]{box-shadow:none;padding:.5em 0}.drawer-content[_ngcontent-%COMP%] .menu[_ngcontent-%COMP%] *[nz-menu-item][_ngcontent-%COMP%]{margin:0;padding:.5em 2em}.stopped[_nghost-%COMP%]{filter:grayscale(100%)}a[_ngcontent-%COMP%]{color:inherit}a[_ngcontent-%COMP%]:hover{color:#1890ff}a[_ngcontent-%COMP%]:focus-visible{outline:-webkit-focus-ring-color auto 1px}.cover-wrapper[_ngcontent-%COMP%]{--cover-ratio: 264 / 470;--cover-height: calc(var(--card-width) * var(--cover-ratio));position:relative;width:var(--card-width);height:var(--cover-height)}.cover-wrapper[_ngcontent-%COMP%] .cover[_ngcontent-%COMP%]{width:100%;max-height:var(--cover-height);object-fit:cover}.cover-wrapper[_ngcontent-%COMP%] .title[_ngcontent-%COMP%]{position:absolute;top:.5rem;left:.5rem;font-size:1.2rem;width:-moz-fit-content;width:fit-content;max-width:calc(100% - 1em);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}nz-card-meta[_ngcontent-%COMP%]{margin:0}.meta-title[_ngcontent-%COMP%]{margin:0;display:flex;column-gap:1em}.meta-title[_ngcontent-%COMP%] .user-name[_ngcontent-%COMP%]{color:#fb7299;font-size:1rem;font-weight:700;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.meta-title[_ngcontent-%COMP%] .live-status[_ngcontent-%COMP%] nz-tag[_ngcontent-%COMP%]{margin:0;position:relative;bottom:1px}.meta-desc[_ngcontent-%COMP%]{margin:0;display:flex}.meta-desc[_ngcontent-%COMP%] .room-id-wrapper[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap}.meta-desc[_ngcontent-%COMP%] .room-id-wrapper[_ngcontent-%COMP%] .short-room-id[_ngcontent-%COMP%]:after{display:inline-block;width:1em;content:","}@media screen and (max-width: 320px){.meta-desc[_ngcontent-%COMP%] .room-id-wrapper[_ngcontent-%COMP%] .room-id-label[_ngcontent-%COMP%]{display:none}}.meta-desc[_ngcontent-%COMP%] .area-name[_ngcontent-%COMP%]{margin-left:auto}.meta-desc[_ngcontent-%COMP%] .area-name[_ngcontent-%COMP%] nz-tag[_ngcontent-%COMP%]{margin:0;border-radius:30px;padding:0 1em}.action-icon[_ngcontent-%COMP%]{font-size:16px}.not-allowed[_ngcontent-%COMP%]{cursor:not-allowed}'],changeDetection:0}),n})();function Gs(n,o){1&n&&(t.TgZ(0,"div",2),t._UZ(1,"nz-empty"),t.qZA())}function $s(n,o){1&n&&t._UZ(0,"app-task-item",6),2&n&&t.Q6J("data",o.$implicit)}function Vs(n,o){if(1&n&&(t.TgZ(0,"div",3,4),t.YNc(2,$s,1,1,"app-task-item",5),t.qZA()),2&n){const e=t.oxw();t.xp6(2),t.Q6J("ngForOf",e.dataList)("ngForTrackBy",e.trackByRoomId)}}let js=(()=>{class n{constructor(){this.dataList=[]}trackByRoomId(e,i){return i.room_info.room_id}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-task-list"]],inputs:{dataList:"dataList"},decls:3,vars:2,consts:[["class","empty-container",4,"ngIf","ngIfElse"],["elseBlock",""],[1,"empty-container"],[1,"tasks-container"],["tasks",""],[3,"data",4,"ngFor","ngForOf","ngForTrackBy"],[3,"data"]],template:function(e,i){if(1&e&&(t.YNc(0,Gs,2,0,"div",0),t.YNc(1,Vs,3,2,"ng-template",null,1,t.W1O)),2&e){const s=t.MAs(2);t.Q6J("ngIf",0===i.dataList.length)("ngIfElse",s)}},directives:[u.O5,Kt.p9,u.sg,Ys],styles:["[_nghost-%COMP%]{height:100%;width:100%;position:relative;display:block;margin:0;padding:1rem;background:#f1f1f1;overflow:auto}[_nghost-%COMP%]{--card-width: 400px;--grid-gutter: 12px;padding:var(--grid-gutter)}@media screen and (max-width: 400px){[_nghost-%COMP%]{--card-width: 100%;padding:var(--grid-gutter) 0}}.tasks-container[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(auto-fill,var(--card-width));grid-gap:var(--grid-gutter);gap:var(--grid-gutter);justify-content:center;max-width:100%;margin:0 auto}.empty-container[_ngcontent-%COMP%]{height:100%;width:100%;display:flex;align-items:center;justify-content:center}"],changeDetection:0}),n})();var Hs=l(2643),Ws=l(1406);function Xs(n,o){1&n&&(t.ynx(0),t._uU(1," \u8bf7\u8f93\u5165\u76f4\u64ad\u95f4\u53f7\u6216 URL "),t.BQk())}function Ks(n,o){1&n&&(t.ynx(0),t._uU(1," \u8f93\u5165\u6709\u9519\u8bef "),t.BQk())}function ta(n,o){if(1&n&&(t.YNc(0,Xs,2,0,"ng-container",8),t.YNc(1,Ks,2,0,"ng-container",8)),2&n){const e=o.$implicit;t.Q6J("ngIf",e.hasError("required")),t.xp6(1),t.Q6J("ngIf",e.hasError("pattern"))}}function ea(n,o){if(1&n&&(t.ynx(0),t._UZ(1,"nz-alert",9),t.BQk()),2&n){const e=o.$implicit;t.xp6(1),t.Q6J("nzType",e.type)("nzMessage",e.message)}}function na(n,o){if(1&n&&(t.ynx(0),t.TgZ(1,"form",2),t.TgZ(2,"nz-form-item"),t.TgZ(3,"nz-form-control",3),t._UZ(4,"input",4),t.YNc(5,ta,2,2,"ng-template",null,5,t.W1O),t.qZA(),t.qZA(),t.qZA(),t.TgZ(7,"div",6),t.YNc(8,ea,2,2,"ng-container",7),t.qZA(),t.BQk()),2&n){const e=t.MAs(6),i=t.oxw();t.xp6(1),t.Q6J("formGroup",i.formGroup),t.xp6(2),t.Q6J("nzErrorTip",e),t.xp6(1),t.Q6J("pattern",i.pattern),t.xp6(4),t.Q6J("ngForOf",i.resultMessages)}}const ia=/^https?:\/\/live\.bilibili\.com\/(\d+).*$/,oa=/^\s*(?:\d+(?:[ ]+\d+)*|https?:\/\/live\.bilibili\.com\/\d+.*)\s*$/;let sa=(()=>{class n{constructor(e,i,s){this.changeDetector=i,this.taskManager=s,this.visible=!1,this.visibleChange=new t.vpe,this.pending=!1,this.resultMessages=[],this.pattern=oa,this.formGroup=e.group({input:["",[m.kI.required,m.kI.pattern(this.pattern)]]})}get inputControl(){return this.formGroup.get("input")}open(){this.setVisible(!0)}close(){this.resultMessages=[],this.reset(),this.setVisible(!1)}setVisible(e){this.visible=e,this.visibleChange.emit(e),this.changeDetector.markForCheck()}reset(){this.pending=!1,this.formGroup.reset(),this.changeDetector.markForCheck()}handleCancel(){this.close()}handleConfirm(){this.pending=!0;const e=this.inputControl.value.trim();let i;i=e.startsWith("http")?[parseInt(ia.exec(e)[1])]:new Set(e.split(/\s+/).map(s=>parseInt(s))),(0,Gt.D)(i).pipe((0,Ws.b)(s=>this.taskManager.addTask(s)),(0,k.b)(s=>{this.resultMessages.push(s),this.changeDetector.markForCheck()})).subscribe({complete:()=>{this.resultMessages.every(s=>"success"===s.type)?this.close():this.reset()}})}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(m.qu),t.Y36(t.sBO),t.Y36(Rt))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-add-task-dialog"]],inputs:{visible:"visible"},outputs:{visibleChange:"visibleChange"},decls:2,vars:6,consts:[["nzTitle","\u6dfb\u52a0\u4efb\u52a1","nzCentered","","nzOkText","\u6dfb\u52a0",3,"nzVisible","nzOkLoading","nzOkDisabled","nzCancelDisabled","nzClosable","nzMaskClosable","nzOnOk","nzOnCancel"],[4,"nzModalContent"],["nz-form","",3,"formGroup"],["nzHasFeedback","",3,"nzErrorTip"],["nz-input","","required","","placeholder","\u76f4\u64ad\u95f4 URL \u6216\u623f\u95f4\u53f7\uff08\u652f\u6301\u591a\u4e2a\u623f\u95f4\u53f7\u7528\u7a7a\u683c\u9694\u5f00\uff09","formControlName","input",3,"pattern"],["errorTip",""],[1,"result-messages-container"],[4,"ngFor","ngForOf"],[4,"ngIf"],["nzShowIcon","",3,"nzType","nzMessage"]],template:function(e,i){1&e&&(t.TgZ(0,"nz-modal",0),t.NdJ("nzOnOk",function(){return i.handleConfirm()})("nzOnCancel",function(){return i.handleCancel()}),t.YNc(1,na,9,4,"ng-container",1),t.qZA()),2&e&&t.Q6J("nzVisible",i.visible)("nzOkLoading",i.pending)("nzOkDisabled",i.formGroup.invalid)("nzCancelDisabled",i.pending)("nzClosable",!i.pending)("nzMaskClosable",!i.pending)},directives:[V.du,V.Hf,m._Y,m.JL,B.Lr,m.sg,P.SK,B.Nx,P.t3,B.Fd,et.Zp,m.Fj,m.Q7,m.JJ,m.u,m.c5,u.O5,u.sg,He],styles:[".result-messages-container[_ngcontent-%COMP%]{width:100%;max-height:200px;overflow-y:auto}"],changeDetection:0}),n})(),ra=(()=>{class n{transform(e,i=""){return console.debug("filter tasks by '%s'",i),[...this.filterByTerm(e,i)]}filterByTerm(e,i){return function*aa(n,o){for(const e of n)o(e)&&(yield e)}(e,s=>""===(i=i.trim())||s.user_info.name.includes(i)||s.room_info.title.toString().includes(i)||s.room_info.area_name.toString().includes(i)||s.room_info.room_id.toString().includes(i)||s.room_info.short_room_id.toString().includes(i))}}return n.\u0275fac=function(e){return new(e||n)},n.\u0275pipe=t.Yjl({name:"filterTasks",type:n,pure:!0}),n})();function la(n,o){if(1&n&&t._UZ(0,"nz-spin",6),2&n){const e=t.oxw();t.Q6J("nzSize","large")("nzSpinning",e.loading)}}function ca(n,o){if(1&n&&(t._UZ(0,"app-task-list",7),t.ALo(1,"filterTasks")),2&n){const e=t.oxw();t.Q6J("dataList",t.xi3(1,1,e.dataList,e.filterTerm))}}const Te="app-tasks-selection",xe="app-tasks-reverse",ua=[{path:":id/detail",component:Ao},{path:"",component:(()=>{class n{constructor(e,i,s,a){this.changeDetector=e,this.notification=i,this.storage=s,this.taskService=a,this.loading=!0,this.dataList=[],this.filterTerm="",this.selection=this.retrieveSelection(),this.reverse=this.retrieveReverse()}ngOnInit(){this.syncTaskData()}ngOnDestroy(){this.desyncTaskData()}onSelectionChanged(e){this.selection=e,this.storeSelection(e),this.desyncTaskData(),this.syncTaskData()}onReverseChanged(e){this.reverse=e,this.storeReverse(e),e&&(this.dataList=[...this.dataList.reverse()],this.changeDetector.markForCheck())}retrieveSelection(){const e=this.storage.getData(Te);return null!==e?e:Z.ALL}retrieveReverse(){return"true"===this.storage.getData(xe)}storeSelection(e){this.storage.setData(Te,e)}storeReverse(e){this.storage.setData(xe,e.toString())}syncTaskData(){this.dataSubscription=(0,A.of)((0,A.of)(0),vt(1e3)).pipe((0,Bt.u)(),(0,nt.w)(()=>this.taskService.getAllTaskData(this.selection)),(0,it.K)(e=>{throw this.notification.error("\u83b7\u53d6\u4efb\u52a1\u6570\u636e\u51fa\u9519",e.message),e}),(0,bt.X)(10,3e3)).subscribe(e=>{this.loading=!1,this.dataList=this.reverse?e.reverse():e,this.changeDetector.markForCheck()},e=>{this.notification.error("\u83b7\u53d6\u4efb\u52a1\u6570\u636e\u51fa\u9519","\u7f51\u7edc\u8fde\u63a5\u5f02\u5e38, \u8bf7\u5f85\u7f51\u7edc\u6b63\u5e38\u540e\u5237\u65b0\u3002",{nzDuration:0})})}desyncTaskData(){var e;null===(e=this.dataSubscription)||void 0===e||e.unsubscribe()}}return n.\u0275fac=function(e){return new(e||n)(t.Y36(t.sBO),t.Y36(Jt.zb),t.Y36(fe.V),t.Y36(St))},n.\u0275cmp=t.Xpm({type:n,selectors:[["app-tasks"]],decls:8,vars:4,consts:[[3,"selection","reverse","selectionChange","reverseChange","filterChange"],["class","spinner",3,"nzSize","nzSpinning",4,"ngIf","ngIfElse"],["elseBlock",""],["nz-button","","nzType","primary","nzSize","large","nzShape","circle","nz-tooltip","","nzTooltipTitle","\u6dfb\u52a0\u4efb\u52a1",1,"add-task-button",3,"click"],["nz-icon","","nzType","plus"],["addTaskDialog",""],[1,"spinner",3,"nzSize","nzSpinning"],[3,"dataList"]],template:function(e,i){if(1&e){const s=t.EpF();t.TgZ(0,"app-toolbar",0),t.NdJ("selectionChange",function(c){return i.onSelectionChanged(c)})("reverseChange",function(c){return i.onReverseChanged(c)})("filterChange",function(c){return i.filterTerm=c}),t.qZA(),t.YNc(1,la,1,2,"nz-spin",1),t.YNc(2,ca,2,4,"ng-template",null,2,t.W1O),t.TgZ(4,"button",3),t.NdJ("click",function(){return t.CHM(s),t.MAs(7).open()}),t._UZ(5,"i",4),t.qZA(),t._UZ(6,"app-add-task-dialog",null,5)}if(2&e){const s=t.MAs(3);t.Q6J("selection",i.selection)("reverse",i.reverse),t.xp6(1),t.Q6J("ngIf",i.loading)("ngIfElse",s)}},directives:[Vo,u.O5,te.W,js,Ct.ix,Hs.dQ,Lt.w,rt.SY,M.Ls,sa],pipes:[ra],styles:["[_nghost-%COMP%]{height:100%;width:100%;position:relative;display:block;margin:0;padding:1rem;background:#f1f1f1;overflow:auto}.spinner[_ngcontent-%COMP%]{height:100%;width:100%}[_nghost-%COMP%]{display:flex;flex-direction:column;padding:0;overflow:hidden}.add-task-button[_ngcontent-%COMP%]{position:fixed;bottom:5vh;right:5vw}"],changeDetection:0}),n})()}];let pa=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[ct.Bz.forChild(ua)],ct.Bz]}),n})(),ga=(()=>{class n{}return n.\u0275fac=function(e){return new(e||n)},n.\u0275mod=t.oAB({type:n}),n.\u0275inj=t.cJS({imports:[[u.ez,m.u5,m.UX,U.xu,dt,P.Jb,D.vh,Ht,at.Rt,M.PV,Ht,rt.cg,G,Ot.m,tt.b1,Ct.sL,V.Qp,B.U5,et.o7,Wt.Wr,Ie,Tt.aF,Xt.S,Kt.Xo,te.j,We,lt.BL,wt.LV,bn,J.HQ,Bn,Ti,Ai.forRoot({echarts:()=>l.e(45).then(l.bind(l,8045))}),pa,Di.m]]}),n})()},855:function(jt){jt.exports=function(){"use strict";var ot=/^(b|B)$/,l={iec:{bits:["b","Kib","Mib","Gib","Tib","Pib","Eib","Zib","Yib"],bytes:["B","KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"]},jedec:{bits:["b","Kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb"],bytes:["B","KB","MB","GB","TB","PB","EB","ZB","YB"]}},u={iec:["","kibi","mebi","gibi","tebi","pebi","exbi","zebi","yobi"],jedec:["","kilo","mega","giga","tera","peta","exa","zetta","yotta"]},m={floor:Math.floor,ceil:Math.ceil};function U(t){var h,q,R,W,dt,P,D,M,_,v,y,L,T,b,Y,X,st,G,at,Mt,$,z=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},g=[],K=0;if(isNaN(t))throw new TypeError("Invalid number");if(R=!0===z.bits,Y=!0===z.unix,L=!0===z.pad,T=void 0!==z.round?z.round:Y?1:2,D=void 0!==z.locale?z.locale:"",M=z.localeOptions||{},X=void 0!==z.separator?z.separator:"",st=void 0!==z.spacer?z.spacer:Y?"":" ",at=z.symbols||{},G=2===(q=z.base||2)&&z.standard||"jedec",y=z.output||"string",dt=!0===z.fullform,P=z.fullforms instanceof Array?z.fullforms:[],h=void 0!==z.exponent?z.exponent:-1,Mt=m[z.roundingMethod]||Math.round,_=(v=Number(t))<0,W=q>2?1e3:1024,$=!1===isNaN(z.precision)?parseInt(z.precision,10):0,_&&(v=-v),(-1===h||isNaN(h))&&(h=Math.floor(Math.log(v)/Math.log(W)))<0&&(h=0),h>8&&($>0&&($+=8-h),h=8),"exponent"===y)return h;if(0===v)g[0]=0,b=g[1]=Y?"":l[G][R?"bits":"bytes"][h];else{K=v/(2===q?Math.pow(2,10*h):Math.pow(1e3,h)),R&&(K*=8)>=W&&h<8&&(K/=W,h++);var mt=Math.pow(10,h>0?T:0);g[0]=Mt(K*mt)/mt,g[0]===W&&h<8&&void 0===z.exponent&&(g[0]=1,h++),b=g[1]=10===q&&1===h?R?"kb":"kB":l[G][R?"bits":"bytes"][h],Y&&(g[1]="jedec"===G?g[1].charAt(0):h>0?g[1].replace(/B$/,""):g[1],ot.test(g[1])&&(g[0]=Math.floor(g[0]),g[1]=""))}if(_&&(g[0]=-g[0]),$>0&&(g[0]=g[0].toPrecision($)),g[1]=at[g[1]]||g[1],!0===D?g[0]=g[0].toLocaleString():D.length>0?g[0]=g[0].toLocaleString(D,M):X.length>0&&(g[0]=g[0].toString().replace(".",X)),L&&!1===Number.isInteger(g[0])&&T>0){var _t=X||".",ht=g[0].toString().split(_t),ft=ht[1]||"",zt=ft.length,Zt=T-zt;g[0]="".concat(ht[0]).concat(_t).concat(ft.padEnd(zt+Zt,"0"))}return dt&&(g[1]=P[h]?P[h]:u[G][h]+(R?"bit":"byte")+(1===g[0]?"":"s")),"array"===y?g:"object"===y?{value:g[0],symbol:g[1],exponent:h,unit:b}:g.join(st)}return U.partial=function(t){return function(h){return U(h,t)}},U}()}}]); \ No newline at end of file diff --git a/src/blrec/data/webapp/index.html b/src/blrec/data/webapp/index.html index f7a268e..c2a2645 100644 --- a/src/blrec/data/webapp/index.html +++ b/src/blrec/data/webapp/index.html @@ -10,6 +10,6 @@ - + \ No newline at end of file diff --git a/src/blrec/data/webapp/ngsw.json b/src/blrec/data/webapp/ngsw.json index 7d96da9..71095c3 100644 --- a/src/blrec/data/webapp/ngsw.json +++ b/src/blrec/data/webapp/ngsw.json @@ -1,6 +1,6 @@ { "configVersion": 1, - "timestamp": 1651824752572, + "timestamp": 1653113686866, "index": "/index.html", "assetGroups": [ { @@ -16,13 +16,13 @@ "/45.c90c3cea2bf1a66e.js", "/474.7f6529972e383566.js", "/66.31f5b9ae46ae9005.js", - "/869.42b1fd9a88732b97.js", + "/869.0ab6b8a3f466df77.js", "/common.858f777e9296e6f2.js", "/index.html", "/main.b9234f0840c7101a.js", "/manifest.webmanifest", "/polyfills.4b08448aee19bb22.js", - "/runtime.3ef3795d150b06f7.js", + "/runtime.8ba8344712d0946d.js", "/styles.1f581691b230dc4d.css" ], "patterns": [] @@ -1639,7 +1639,7 @@ "/45.c90c3cea2bf1a66e.js": "e5bfb8cf3803593e6b8ea14c90b3d3cb6a066764", "/474.7f6529972e383566.js": "1c74b5c6379705a3110c99767f97feddc42a0d54", "/66.31f5b9ae46ae9005.js": "cc22d2582d8e4c2a83e089d5a1ec32619e439ccd", - "/869.42b1fd9a88732b97.js": "ca5c951f04d02218b3fe7dc5c022dad22bf36eca", + "/869.0ab6b8a3f466df77.js": "fd3e32d78790ec916177aa38b49ca32bfe62d0d4", "/assets/animal/panda.js": "fec2868bb3053dd2da45f96bbcb86d5116ed72b1", "/assets/animal/panda.svg": "bebd302cdc601e0ead3a6d2710acf8753f3d83b1", "/assets/fill/.gitkeep": "da39a3ee5e6b4b0d3255bfef95601890afd80709", @@ -3234,11 +3234,11 @@ "/assets/twotone/warning.js": "fb2d7ea232f3a99bf8f080dbc94c65699232ac01", "/assets/twotone/warning.svg": "8c7a2d3e765a2e7dd58ac674870c6655cecb0068", "/common.858f777e9296e6f2.js": "b68ca68e1e214a2537d96935c23410126cc564dd", - "/index.html": "29cb770b0e30ea40770ce8c33c7254da20c3577b", + "/index.html": "da07776fe6f05347b1b8360f61b81c59734dcc54", "/main.b9234f0840c7101a.js": "c8c7b588c070b957a2659f62d6a77de284aa2233", "/manifest.webmanifest": "62c1cb8c5ad2af551a956b97013ab55ce77dd586", "/polyfills.4b08448aee19bb22.js": "8e73f2d42cc13ca353cea5c886d930bd6da08d0d", - "/runtime.3ef3795d150b06f7.js": "c33604f420d675070ccaa5359763e8be3c434c0d", + "/runtime.8ba8344712d0946d.js": "264d0e7e1e88dd1a4383d73d401f5ccd51e40eb7", "/styles.1f581691b230dc4d.css": "6f5befbbad57c2b2e80aae855139744b8010d150" }, "navigationUrls": [ diff --git a/src/blrec/data/webapp/runtime.3ef3795d150b06f7.js b/src/blrec/data/webapp/runtime.8ba8344712d0946d.js similarity index 97% rename from src/blrec/data/webapp/runtime.3ef3795d150b06f7.js rename to src/blrec/data/webapp/runtime.8ba8344712d0946d.js index 16fbceb..6a3807f 100644 --- a/src/blrec/data/webapp/runtime.3ef3795d150b06f7.js +++ b/src/blrec/data/webapp/runtime.8ba8344712d0946d.js @@ -1 +1 @@ -(()=>{"use strict";var e,v={},m={};function r(e){var i=m[e];if(void 0!==i)return i.exports;var t=m[e]={exports:{}};return v[e].call(t.exports,t,t.exports,r),t.exports}r.m=v,e=[],r.O=(i,t,o,f)=>{if(!t){var a=1/0;for(n=0;n=f)&&Object.keys(r.O).every(b=>r.O[b](t[l]))?t.splice(l--,1):(c=!1,f0&&e[n-1][2]>f;n--)e[n]=e[n-1];e[n]=[t,o,f]},r.n=e=>{var i=e&&e.__esModule?()=>e.default:()=>e;return r.d(i,{a:i}),i},r.d=(e,i)=>{for(var t in i)r.o(i,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:i[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((i,t)=>(r.f[t](e,i),i),[])),r.u=e=>(592===e?"common":e)+"."+{45:"c90c3cea2bf1a66e",66:"31f5b9ae46ae9005",103:"5b5d2a6e5a8a7479",146:"92e3b29c4c754544",474:"7f6529972e383566",592:"858f777e9296e6f2",869:"42b1fd9a88732b97"}[e]+".js",r.miniCssF=e=>{},r.o=(e,i)=>Object.prototype.hasOwnProperty.call(e,i),(()=>{var e={},i="blrec:";r.l=(t,o,f,n)=>{if(e[t])e[t].push(o);else{var a,c;if(void 0!==f)for(var l=document.getElementsByTagName("script"),d=0;d{a.onerror=a.onload=null,clearTimeout(p);var _=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),_&&_.forEach(h=>h(b)),g)return g(b)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=s.bind(null,a.onerror),a.onload=s.bind(null,a.onload),c&&document.head.appendChild(a)}}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;r.tu=i=>(void 0===e&&(e={createScriptURL:t=>t},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e.createScriptURL(i))})(),r.p="",(()=>{var e={666:0};r.f.j=(o,f)=>{var n=r.o(e,o)?e[o]:void 0;if(0!==n)if(n)f.push(n[2]);else if(666!=o){var a=new Promise((u,s)=>n=e[o]=[u,s]);f.push(n[2]=a);var c=r.p+r.u(o),l=new Error;r.l(c,u=>{if(r.o(e,o)&&(0!==(n=e[o])&&(e[o]=void 0),n)){var s=u&&("load"===u.type?"missing":u.type),p=u&&u.target&&u.target.src;l.message="Loading chunk "+o+" failed.\n("+s+": "+p+")",l.name="ChunkLoadError",l.type=s,l.request=p,n[1](l)}},"chunk-"+o,o)}else e[o]=0},r.O.j=o=>0===e[o];var i=(o,f)=>{var l,d,[n,a,c]=f,u=0;if(n.some(p=>0!==e[p])){for(l in a)r.o(a,l)&&(r.m[l]=a[l]);if(c)var s=c(r)}for(o&&o(f);u{"use strict";var e,v={},m={};function r(e){var i=m[e];if(void 0!==i)return i.exports;var t=m[e]={exports:{}};return v[e].call(t.exports,t,t.exports,r),t.exports}r.m=v,e=[],r.O=(i,t,o,f)=>{if(!t){var a=1/0;for(n=0;n=f)&&Object.keys(r.O).every(b=>r.O[b](t[l]))?t.splice(l--,1):(c=!1,f0&&e[n-1][2]>f;n--)e[n]=e[n-1];e[n]=[t,o,f]},r.n=e=>{var i=e&&e.__esModule?()=>e.default:()=>e;return r.d(i,{a:i}),i},r.d=(e,i)=>{for(var t in i)r.o(i,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:i[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((i,t)=>(r.f[t](e,i),i),[])),r.u=e=>(592===e?"common":e)+"."+{45:"c90c3cea2bf1a66e",66:"31f5b9ae46ae9005",103:"5b5d2a6e5a8a7479",146:"92e3b29c4c754544",474:"7f6529972e383566",592:"858f777e9296e6f2",869:"0ab6b8a3f466df77"}[e]+".js",r.miniCssF=e=>{},r.o=(e,i)=>Object.prototype.hasOwnProperty.call(e,i),(()=>{var e={},i="blrec:";r.l=(t,o,f,n)=>{if(e[t])e[t].push(o);else{var a,c;if(void 0!==f)for(var l=document.getElementsByTagName("script"),d=0;d{a.onerror=a.onload=null,clearTimeout(p);var _=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),_&&_.forEach(h=>h(b)),g)return g(b)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=s.bind(null,a.onerror),a.onload=s.bind(null,a.onload),c&&document.head.appendChild(a)}}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;r.tu=i=>(void 0===e&&(e={createScriptURL:t=>t},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e.createScriptURL(i))})(),r.p="",(()=>{var e={666:0};r.f.j=(o,f)=>{var n=r.o(e,o)?e[o]:void 0;if(0!==n)if(n)f.push(n[2]);else if(666!=o){var a=new Promise((u,s)=>n=e[o]=[u,s]);f.push(n[2]=a);var c=r.p+r.u(o),l=new Error;r.l(c,u=>{if(r.o(e,o)&&(0!==(n=e[o])&&(e[o]=void 0),n)){var s=u&&("load"===u.type?"missing":u.type),p=u&&u.target&&u.target.src;l.message="Loading chunk "+o+" failed.\n("+s+": "+p+")",l.name="ChunkLoadError",l.type=s,l.request=p,n[1](l)}},"chunk-"+o,o)}else e[o]=0},r.O.j=o=>0===e[o];var i=(o,f)=>{var l,d,[n,a,c]=f,u=0;if(n.some(p=>0!==e[p])){for(l in a)r.o(a,l)&&(r.m[l]=a[l]);if(c)var s=c(r)}for(o&&o(f);u None: super().__init__() - self._source = Subject() + self._source: Subject[Event] = Subject() @property - def events(self) -> Observable: + def events(self) -> Observable[Event]: return self._source def submit(self, event: Event) -> None: diff --git a/src/blrec/exception/exception_center.py b/src/blrec/exception/exception_center.py index 5fbf513..634fd93 100644 --- a/src/blrec/exception/exception_center.py +++ b/src/blrec/exception/exception_center.py @@ -1,20 +1,17 @@ - -from rx.subject import Subject -from rx.core import Observable +from reactivex import Observable, Subject from ..utils.patterns import Singleton - -__all__ = 'ExceptionCenter', +__all__ = ('ExceptionCenter',) class ExceptionCenter(Singleton): def __init__(self) -> None: super().__init__() - self._source = Subject() + self._source: Subject[BaseException] = Subject() @property - def exceptions(self) -> Observable: + def exceptions(self) -> Observable[BaseException]: return self._source def submit(self, exc: BaseException) -> None: diff --git a/src/blrec/flv/common.py b/src/blrec/flv/common.py index c0c34c5..f35ebff 100644 --- a/src/blrec/flv/common.py +++ b/src/blrec/flv/common.py @@ -1,17 +1,16 @@ from __future__ import annotations + from typing import Any, Dict, Iterable, Iterator, Mapping, Optional, Union -from typing_extensions import TypeGuard import attr +from typing_extensions import TypeGuard -from .io import FlvReader -from .io_protocols import RandomIO from . import scriptdata from .avc import extract_resolution +from .io import FlvReader +from .io_protocols import RandomIO +from .models import AudioTag, AVCPacketType, FlvTag, ScriptTag, TagType, VideoTag from .utils import OffsetRepositor -from .models import ( - AVCPacketType, FlvTag, AudioTag, ScriptTag, TagType, VideoTag -) def read_tags( @@ -54,14 +53,14 @@ def read_tags_in_duration( def peek_tags( - file: RandomIO, reader: FlvReader, count: int, *, no_body: bool = False, + file: RandomIO, reader: FlvReader, count: int, *, no_body: bool = False ) -> Iterator[FlvTag]: with OffsetRepositor(file): yield from read_tags(reader, count, no_body=no_body) def rpeek_tags( - file: RandomIO, reader: FlvReader, count: int, *, no_body: bool = False, + file: RandomIO, reader: FlvReader, count: int, *, no_body: bool = False ) -> Iterator[FlvTag]: with OffsetRepositor(file): yield from rread_tags(reader, count, no_body=no_body) @@ -74,9 +73,7 @@ def find_metadata_tag(tags: Iterable[FlvTag]) -> Optional[ScriptTag]: return None -def find_header_tag( - tags: Iterable[FlvTag] -) -> Optional[Union[AudioTag, VideoTag]]: +def find_header_tag(tags: Iterable[FlvTag]) -> Optional[Union[AudioTag, VideoTag]]: for tag in tags: if is_sequence_header(tag): return tag @@ -221,9 +218,7 @@ def enrich_metadata( def update_metadata( - metadata_tag: ScriptTag, - metadata: Mapping[str, Any], - offset: Optional[int] = None, + metadata_tag: ScriptTag, metadata: Mapping[str, Any], offset: Optional[int] = None ) -> ScriptTag: original_tag_size = metadata_tag.tag_size new_tag = enrich_metadata(metadata_tag, metadata, offset) @@ -236,7 +231,8 @@ def ensure_order(metadata: Dict[str, Any]) -> Dict[str, Any]: # some typical properties such as 'keyframes' must be before some custom # properties such as 'Comment' otherwise, it won't take effect in some # players! - from .data_analyser import MetaData + from .operators import MetaData + typical_props = attr.fields_dict(MetaData).keys() return { **{k: v for k, v in metadata.items() if k in typical_props}, diff --git a/src/blrec/flv/exceptions.py b/src/blrec/flv/exceptions.py index 7f3b074..bfe6517 100644 --- a/src/blrec/flv/exceptions.py +++ b/src/blrec/flv/exceptions.py @@ -1,4 +1,3 @@ - class FlvDataError(ValueError): ... @@ -17,23 +16,3 @@ class FlvStreamCorruptedError(Exception): class FlvFileCorruptedError(Exception): ... - - -class AudioParametersChanged(Exception): - ... - - -class VideoParametersChanged(Exception): - ... - - -class FileSizeOverLimit(Exception): - ... - - -class DurationOverLimit(Exception): - ... - - -class CutStream(Exception): - ... diff --git a/src/blrec/flv/helpers.py b/src/blrec/flv/helpers.py index 7547b4f..1e011c4 100644 --- a/src/blrec/flv/helpers.py +++ b/src/blrec/flv/helpers.py @@ -7,7 +7,7 @@ from .common import ( is_audio_tag, is_metadata_tag, is_script_tag, is_video_tag, read_tags, parse_metadata, find_metadata_tag, ) -from .stream_processor import JoinPoint +from .operators import JoinPoint from .utils import format_timestamp from ..path import extra_metadata_path @@ -32,7 +32,7 @@ def make_comment_for_joinpoints(join_points: Iterable[JoinPoint]) -> str: '流中断拼接详情\n' + '\n'.join(( '时间戳:{}, 无缝拼接:{}'.format( - format_timestamp(p.timestamp), + format_timestamp(int(p.timestamp)), '是' if p.seamless else '否', ) for p in join_points diff --git a/src/blrec/flv/limit_checker.py b/src/blrec/flv/limit_checker.py deleted file mode 100644 index 87ed22b..0000000 --- a/src/blrec/flv/limit_checker.py +++ /dev/null @@ -1,84 +0,0 @@ -from __future__ import annotations -import logging -from typing import Optional - - -from .models import FlvHeader, FlvTag, BACK_POINTER_SIZE, VideoTag -from .exceptions import FileSizeOverLimit, DurationOverLimit -from .common import is_video_nalu_keyframe - - -__all__ = 'LimitChecker', - - -logger = logging.getLogger(__name__) - - -class LimitChecker: - def __init__( - self, - filesize_limit: int = 0, # file size in bytes, no limit by default. - duration_limit: int = 0, # duration in seconds, no limit by default. - ) -> None: - self.filesize_limit = filesize_limit - self.duration_limit = duration_limit - self.reset() - - def is_filesize_over_limit(self) -> bool: - return ( - self._filesize + self._max_size_between_keyframes >= - self.filesize_limit - ) - - def is_duration_over_limit(self) -> bool: - return ( - self._duration + self._max_duration_between_keyframes >= - self.duration_limit - ) - - @property - def last_keyframe_tag(self) -> Optional[VideoTag]: - return self._last_keyframe_tag - - def reset(self) -> None: - self._filesize = 0 - self._duration = 0.0 - self._max_size_between_keyframes = 0 - self._max_duration_between_keyframes = 0.0 - self._header_checked = False - self._last_keyframe_tag: Optional[VideoTag] = None - - def check_header(self, header: FlvHeader) -> None: - assert not self._header_checked - self._header_checked = True - self._filesize += header.size + BACK_POINTER_SIZE - - def check_tag(self, tag: FlvTag) -> None: - self._filesize += tag.tag_size + BACK_POINTER_SIZE - self._duration = tag.timestamp / 1000 - - if not is_video_nalu_keyframe(tag): - return - - if self._last_keyframe_tag is not None: - self._max_size_between_keyframes = max( - self._max_size_between_keyframes, - tag.offset - self._last_keyframe_tag.offset, - ) - self._max_duration_between_keyframes = max( - self._max_duration_between_keyframes, - (tag.timestamp - self._last_keyframe_tag.timestamp) / 1000, - ) - self._last_keyframe_tag = tag - - if self.filesize_limit > 0 and self.is_filesize_over_limit(): - logger.debug('File size will be over the limit: {} + {}'.format( - self._filesize, self._max_size_between_keyframes, - )) - raise FileSizeOverLimit() - - if self.duration_limit > 0 and self.is_duration_over_limit(): - logger.debug('Duration will be over the limit: {} + {}'.format( - self._duration, self._max_duration_between_keyframes, - )) - raise DurationOverLimit() diff --git a/src/blrec/flv/metadata_dumper.py b/src/blrec/flv/metadata_dumper.py new file mode 100644 index 0000000..72bf859 --- /dev/null +++ b/src/blrec/flv/metadata_dumper.py @@ -0,0 +1,78 @@ +import json +import logging +from contextlib import suppress +from typing import List, Optional + +import attr + +from ..flv import operators as flv_ops +from ..path import extra_metadata_path +from ..utils.mixins import SwitchableMixin + +__all__ = ('MetadataDumper',) + +logger = logging.getLogger(__name__) + + +class MetadataDumper(SwitchableMixin): + def __init__( + self, + dumper: flv_ops.Dumper, + analyser: flv_ops.Analyser, + joinpoint_extractor: flv_ops.JoinPointExtractor, + ) -> None: + super().__init__() + + self._dumper = dumper + self._analyser = analyser + self._joinpoint_extractor = joinpoint_extractor + + self._last_metadata: Optional[flv_ops.MetaData] = None + self._last_join_points: Optional[List[flv_ops.JoinPoint]] = None + + def _do_enable(self) -> None: + self._metadata_subscription = self._analyser.metadatas.subscribe( + self._update_metadata + ) + self._join_points_subscription = ( + self._joinpoint_extractor.join_points.subscribe(self._update_join_points) + ) + self._file_closed_subscription = self._dumper.file_closed.subscribe( + self._dump_metadata + ) + logger.debug('Enabled metadata dumper') + + def _do_disable(self) -> None: + with suppress(Exception): + self._metadata_subscription.dispose() + with suppress(Exception): + self._join_points_subscription.dispose() + with suppress(Exception): + self._file_closed_subscription.dispose() + logger.debug('Disabled metadata dumper') + + def _update_metadata(self, metadata: flv_ops.MetaData) -> None: + self._last_metadata = metadata + + def _update_join_points(self, join_points: List[flv_ops.JoinPoint]) -> None: + self._last_join_points = join_points + + def _dump_metadata(self, video_path: str) -> None: + path = extra_metadata_path(video_path) + logger.debug(f"Dumping metadata to file: '{path}'") + + assert self._last_metadata is not None + assert self._last_join_points is not None + + data = attr.asdict(self._last_metadata, filter=lambda a, v: v is not None) + data['joinpoints'] = list( + map(lambda p: p.to_metadata_value(), self._last_join_points) + ) + + try: + with open(path, 'wt', encoding='utf8') as file: + json.dump(data, file) + except Exception as e: + logger.error(f'Failed to dump metadata: {e}') + else: + logger.debug(f"Successfully dumped metadata to file: '{path}'") diff --git a/src/blrec/flv/metadata_injection.py b/src/blrec/flv/metadata_injection.py new file mode 100644 index 0000000..746eb29 --- /dev/null +++ b/src/blrec/flv/metadata_injection.py @@ -0,0 +1,60 @@ +import logging +import os +from datetime import datetime +from typing import Any, Dict + +import attr +from reactivex import Observable +from reactivex import operators as ops + +from ..utils import operators as utils_ops +from . import operators as flv_ops +from .helpers import make_comment_for_joinpoints +from .operators import JoinPoint +from .operators.helpers import from_file + +__all__ = 'InjectingProgress', 'inject_metadata' + + +logger = logging.getLogger(__name__) + + +@attr.s(auto_attribs=True, slots=True, frozen=True) +class InjectingProgress: + count: int + total: int + + +def inject_metadata( + path: str, metadata: Dict[str, Any], *, show_progress: bool = False +) -> Observable[InjectingProgress]: + filesize = os.path.getsize(path) + append_comment_for_joinpoints(metadata) + + root, ext = os.path.splitext(path) + temp_path = f'{root}_injecting{ext}' + filename = os.path.basename(path) + + return from_file(path).pipe( + flv_ops.Injector(lambda: metadata), + flv_ops.Dumper(lambda: (temp_path, int(datetime.now().timestamp()))), + flv_ops.ProgressBar( + desc='Injecting', + postfix=filename, + total=filesize, + disable=not show_progress, + ), + ops.map(lambda i: len(i)), + ops.scan(lambda acc, x: acc + x, 0), + ops.map(lambda s: InjectingProgress(s, filesize)), + utils_ops.replace(temp_path, path), + ) + + +def append_comment_for_joinpoints(metadata: Dict[str, Any]) -> None: + if join_points := metadata.get('joinpoints'): + join_points = map(JoinPoint.from_metadata_value, join_points) + if 'Comment' in metadata: + metadata['Comment'] += '\n\n' + make_comment_for_joinpoints(join_points) + else: + metadata['Comment'] = make_comment_for_joinpoints(join_points) diff --git a/src/blrec/flv/metadata_injector.py b/src/blrec/flv/metadata_injector.py deleted file mode 100644 index 59a7637..0000000 --- a/src/blrec/flv/metadata_injector.py +++ /dev/null @@ -1,148 +0,0 @@ -import os -import logging -from typing import Any, Dict, Optional - -import attr -from rx import create, operators as op -from rx.subject import Subject -from rx.core import Observable -from rx.core.typing import Observer, Scheduler, Disposable -from rx.scheduler.currentthreadscheduler import CurrentThreadScheduler -from tqdm import tqdm - -from .stream_processor import StreamProcessor, BaseOutputFileManager, JoinPoint -from .helpers import get_metadata, make_comment_for_joinpoints -from ..logging.room_id import with_room_id - - -__all__ = 'MetadataInjector', 'InjectProgress', 'inject_metadata' - - -logger = logging.getLogger(__name__) - - -@attr.s(auto_attribs=True, slots=True, frozen=True) -class InjectProgress: - time: int - duration: int - - -class MetadataInjector: - def __init__(self, in_path: str, out_path: str) -> None: - self._in_path = in_path - self._file_manager = OutputFileManager(out_path) - self._duration: int = 0 - self._progress_updates = Subject() - - @property - def progress_updates(self) -> Observable: - return self._progress_updates - - def inject(self, in_metadata: Dict[str, Any]) -> None: - metadata = get_metadata(self._in_path) - metadata.update(in_metadata) - - self._duration = int(float(metadata['duration']) * 1000) - self._progress_updates.on_next(InjectProgress(0, self._duration)) - - self._append_comment_for_joinpoints(metadata) - - processor = StreamProcessor( - self._file_manager, - metadata=metadata, - disable_limit=True, - ) - - def update_progress(time: int) -> None: - progress = InjectProgress(time, self._duration) - self._progress_updates.on_next(progress) - - processor.time_updates.subscribe(update_progress) - - with open(self._in_path, 'rb') as in_file: - processor.process_stream(in_file) - processor.finalize() - - progress = InjectProgress(self._duration, self._duration) - self._progress_updates.on_next(progress) - - @staticmethod - def _append_comment_for_joinpoints(metadata: Dict[str, Any]) -> None: - if (join_points := metadata.get('joinpoints')): - join_points = map(JoinPoint.from_metadata_value, join_points) - if 'Comment' in metadata: - metadata['Comment'] += '\n\n' + \ - make_comment_for_joinpoints(join_points) - else: - metadata['Comment'] = make_comment_for_joinpoints(join_points) - - -class OutputFileManager(BaseOutputFileManager): - def __init__(self, out_path: str) -> None: - super().__init__() - self._out_path = out_path - - def _make_path(self) -> str: - return self._out_path - - -def inject_metadata( - path: str, - metadata: Dict[str, Any], - *, - report_progress: bool = False, - room_id: Optional[int] = None, -) -> Observable: - def subscribe( - observer: Observer[InjectProgress], - scheduler: Optional[Scheduler] = None, - ) -> Disposable: - _scheduler = scheduler or CurrentThreadScheduler.singleton() - - def action(scheduler, state): # type: ignore - root, ext = os.path.splitext(path) - out_path = f'{root}_inject_metadata{ext}' - injector = MetadataInjector(path, out_path) - file_name = os.path.basename(path) - - with tqdm(desc='Injecting', unit='ms', postfix=file_name) as pbar: - def reset(progress: InjectProgress) -> None: - pbar.reset(progress.duration) - - def update(progress: InjectProgress) -> None: - pbar.update(progress.time - pbar.n) - - injector.progress_updates.pipe(op.first()).subscribe(reset) - injector.progress_updates.pipe(op.skip(1)).subscribe(update) - - if report_progress: - injector.progress_updates.subscribe( - lambda p: observer.on_next(p) - ) - - try: - logger.info(f"Injecting metadata for '{path}' ...") - injector.inject(metadata) - except Exception as e: - logger.error( - f"Failed to inject metadata for '{path}': {repr(e)}" - ) - observer.on_error(e) - else: - logger.info(f"Successfully inject metadata for '{path}'") - try: - os.replace(out_path, path) - except Exception as e: - logger.error( - f"Failed to replace file {path} with '{out_path}'" - ) - observer.on_error(e) - else: - observer.on_completed() - - if room_id is not None: - return _scheduler.schedule(with_room_id(room_id)(action)) - else: - return _scheduler.schedule(action) - - return create(subscribe) diff --git a/src/blrec/flv/models.py b/src/blrec/flv/models.py index b133e34..daa9df2 100644 --- a/src/blrec/flv/models.py +++ b/src/blrec/flv/models.py @@ -106,6 +106,9 @@ class FlvHeader: else: self.type_flag &= ~0b0000_0100 + def __len__(self) -> int: + return self.size + @property def size(self) -> int: return self.data_offset @@ -151,6 +154,9 @@ class FlvTag(ABC, FlvTagHeader): offset: int = attr.ib(validator=[non_negative_integer_validator]) body: Optional[bytes] = attr.ib(default=None, repr=cksum) + def __len__(self) -> int: + return self.tag_size + @property @abstractmethod def header_size(self) -> int: diff --git a/src/blrec/flv/operators/__init__.py b/src/blrec/flv/operators/__init__.py new file mode 100644 index 0000000..8da49f2 --- /dev/null +++ b/src/blrec/flv/operators/__init__.py @@ -0,0 +1,38 @@ +from .analyse import Analyser, KeyFrames, MetaData +from .concat import JoinPoint, JoinPointData, JoinPointExtractor, concat +from .correct import correct +from .cut import Cutter +from .defragment import defragment +from .dump import Dumper +from .fix import fix +from .inject import Injector +from .limit import Limiter +from .parse import parse +from .probe import Prober, StreamProfile +from .process import process +from .progress import ProgressBar +from .split import split + +__all__ = ( + 'Analyser', + 'concat', + 'concat', + 'correct', + 'Cutter', + 'defragment', + 'Dumper', + 'fix', + 'Injector', + 'JoinPoint', + 'JoinPointData', + 'JoinPointExtractor', + 'KeyFrames', + 'Limiter', + 'MetaData', + 'parse', + 'Prober', + 'process', + 'ProgressBar', + 'split', + 'StreamProfile', +) diff --git a/src/blrec/flv/data_analyser.py b/src/blrec/flv/operators/analyse.py similarity index 62% rename from src/blrec/flv/data_analyser.py rename to src/blrec/flv/operators/analyse.py index 3e90179..7464aef 100644 --- a/src/blrec/flv/data_analyser.py +++ b/src/blrec/flv/operators/analyse.py @@ -1,237 +1,331 @@ -""" -Analyse FLV file to make MetaData -ref: https://github.com/ioppermann/yamdi/blob/master/yamdi.c -""" -from __future__ import annotations -from typing import List, Optional - -import attr - -from .models import ( - AudioTag, FlvHeader, FlvTag, SoundType, VideoTag, ScriptTag, - BACK_POINTER_SIZE -) -from .common import Resolution, is_audio_tag, is_script_tag, is_video_tag - - -__all__ = 'DataAnalyser', 'MetaData', 'KeyFrames' - - -@attr.s(auto_attribs=True, slots=True, frozen=True, kw_only=True) -class KeyFrames: - times: List[float] - filepositions: List[float] - - -@attr.s(auto_attribs=True, slots=True, frozen=True, kw_only=True) -class MetaData: - hasAudio: bool - hasVideo: bool - hasMetadata: bool - hasKeyframes: bool - canSeekToEnd: bool - duration: float - datasize: float - filesize: float - - audiosize: Optional[float] = None - audiocodecid: Optional[float] = None - audiodatarate: Optional[float] = None - audiosamplerate: Optional[float] = None - audiosamplesize: Optional[float] = None - stereo: Optional[bool] = None - - videosize: float - framerate: float - videocodecid: float - videodatarate: float - width: float - height: float - - lasttimestamp: float - lastkeyframelocation: float - lastkeyframetimestamp: float - keyframes: KeyFrames - - -class DataAnalyser: - def __init__(self) -> None: - self.reset() - - def reset(self) -> None: - self._num_of_tags = 0 - self._num_of_audio_tags = 0 - self._num_of_video_tags = 0 - self._size_of_tags = 0 - self._size_of_audio_tags = 0 - self._size_of_video_tags = 0 - self._size_of_data = 0 - self._size_of_audio_data = 0 - self._size_of_video_data = 0 - self._last_timestamp = 0 - self._last_timestamp_of_audio = 0 - self._last_timestamp_of_video = 0 - self._keyframe_timestamps: List[int] = [] - self._keyframe_filepositions: List[int] = [] - self._resolution: Optional[Resolution] = None - - self._has_audio = False - self._has_video = False - self._header_analysed = False - self._audio_analysed = False - self._video_analysed = False - - @property - def last_timestamp(self) -> int: - return self._last_timestamp - - def analyse_header(self, header: FlvHeader) -> None: - assert not self._header_analysed - self._header_analysed = True - self._size_of_flv_header = header.size - - def analyse_tag(self, tag: FlvTag) -> None: - if is_audio_tag(tag): - self._analyse_audio_tag(tag) - elif is_video_tag(tag): - self._analyse_video_tag(tag) - elif is_script_tag(tag): - self._analyse_script_tag(tag) - else: - raise ValueError('Invalid tag type') - - self._num_of_tags += 1 - self._size_of_tags += tag.tag_size - self._size_of_data += tag.data_size - self._last_timestamp = tag.timestamp - - def get_real_resolution(self) -> Optional[Resolution]: - return self._resolution - - def calc_frame_rate(self) -> float: - try: - return ( - self._num_of_video_tags / self._last_timestamp_of_video * 1000 - ) - except ZeroDivisionError: - return 0.0 - - def calc_audio_data_rate(self) -> float: - try: - return self._size_of_audio_data * 8 / self._last_timestamp_of_audio - except ZeroDivisionError: - return 0.0 - - def calc_video_data_rate(self) -> float: - try: - return self._size_of_video_data * 8 / self._last_timestamp_of_video - except ZeroDivisionError: - return 0.0 - - def calc_data_size(self) -> int: - return ( - self._size_of_audio_tags + - self._num_of_audio_tags * BACK_POINTER_SIZE + - self._size_of_video_tags + - self._num_of_video_tags * BACK_POINTER_SIZE - ) - - def calc_file_size(self) -> int: - return ( - self._size_of_flv_header + BACK_POINTER_SIZE + - self._size_of_tags + self._num_of_tags * BACK_POINTER_SIZE - ) - - def make_keyframes(self) -> KeyFrames: - return KeyFrames( - times=list(map(lambda t: t / 1000, self._keyframe_timestamps)), - filepositions=list(map(float, self._keyframe_filepositions)), - ) - - def make_metadata(self) -> MetaData: - assert self._header_analysed - assert self._has_audio == self._audio_analysed - assert self._has_video and self._video_analysed - assert self._resolution is not None - - if not self._has_audio: - audiosize = None - audiocodecid = None - audiodatarate = None - audiosamplerate = None - audiosamplesize = None - stereo = None - else: - audiosize = float(self._size_of_audio_tags) - audiocodecid = float(self._audio_codec_id) - audiodatarate = self.calc_audio_data_rate() - audiosamplerate = float(self._audio_sample_rate) - audiosamplesize = float(self._audio_sample_size) - stereo = self._stereo - - keyframes = self.make_keyframes() - - return MetaData( - hasAudio=self._has_audio, - hasVideo=self._has_video, - hasMetadata=True, - hasKeyframes=len(self._keyframe_timestamps) != 0, - canSeekToEnd=( - self._last_timestamp_of_video == self._keyframe_timestamps[-1] - ), - duration=self._last_timestamp / 1000, - datasize=float(self.calc_data_size()), - filesize=float(self.calc_file_size()), - audiosize=audiosize, - audiocodecid=audiocodecid, - audiodatarate=audiodatarate, - audiosamplerate=audiosamplerate, - audiosamplesize=audiosamplesize, - stereo=stereo, - videosize=float(self._size_of_video_tags), - framerate=self.calc_frame_rate(), - videocodecid=float(self._video_codec_id), - videodatarate=self.calc_video_data_rate(), - width=float(self._resolution.width), - height=float(self._resolution.height), - lasttimestamp=self._last_timestamp / 1000, - lastkeyframelocation=keyframes.filepositions[-1], - lastkeyframetimestamp=keyframes.times[-1], - keyframes=keyframes, - ) - - def _analyse_audio_tag(self, tag: AudioTag) -> None: - if not self._audio_analysed: - self._has_audio = True - self._audio_analysed = True - self._audio_codec_id = tag.sound_format.value - self._audio_sample_rate = tag.sound_rate.value - self._audio_sample_size = tag.sound_size.value - self._stereo = tag.sound_type == SoundType.STEREO - - self._num_of_audio_tags += 1 - self._size_of_audio_tags += tag.tag_size - self._size_of_audio_data += tag.data_size - self._last_timestamp_of_audio = tag.timestamp - - def _analyse_video_tag(self, tag: VideoTag) -> None: - if tag.is_keyframe(): - self._keyframe_timestamps.append(tag.timestamp) - self._keyframe_filepositions.append(tag.offset) - if tag.is_avc_header(): - self._resolution = Resolution.from_aac_sequence_header(tag) - else: - pass - - if not self._video_analysed: - self._has_video = True - self._video_analysed = True - self._video_codec_id = tag.codec_id.value - - self._num_of_video_tags += 1 - self._size_of_video_tags += tag.tag_size - self._size_of_video_data += tag.data_size - self._last_timestamp_of_video = tag.timestamp - - def _analyse_script_tag(self, tag: ScriptTag) -> None: - pass +""" +Analyse the FLV stream to make MetaData +ref: https://github.com/ioppermann/yamdi/blob/master/yamdi.c +""" +from __future__ import annotations + +import logging +from typing import List, Optional, TypedDict + +import attr +from reactivex import Observable, Subject, abc +from reactivex.disposable import CompositeDisposable, Disposable, SerialDisposable + +from ..common import Resolution, is_audio_tag, is_script_tag, is_video_tag +from ..models import ( + BACK_POINTER_SIZE, + AudioTag, + FlvHeader, + FlvTag, + ScriptTag, + SoundType, + VideoTag, +) +from .typing import FLVStream, FLVStreamItem + +__all__ = 'Analyser', 'MetaData', 'KeyFrames' + +logger = logging.getLogger(__name__) + + +@attr.s(auto_attribs=True, slots=True, frozen=True, kw_only=True) +class KeyFrames: + times: List[float] + filepositions: List[float] + + +class KeyFramesDict(TypedDict): + times: List[float] + filepositions: List[float] + + +@attr.s(auto_attribs=True, slots=True, frozen=True, kw_only=True) +class MetaData: + hasAudio: bool + hasVideo: bool + hasMetadata: bool + hasKeyframes: bool + canSeekToEnd: bool + duration: float + datasize: float + filesize: float + + audiosize: Optional[float] = None + audiocodecid: Optional[float] = None + audiodatarate: Optional[float] = None + audiosamplerate: Optional[float] = None + audiosamplesize: Optional[float] = None + stereo: Optional[bool] = None + + videosize: float + framerate: float + videocodecid: float + videodatarate: float + width: float + height: float + + lasttimestamp: float + lastkeyframelocation: float + lastkeyframetimestamp: float + keyframes: KeyFrames + + +class MetaDataDict: + hasAudio: bool + hasVideo: bool + hasMetadata: bool + hasKeyframes: bool + canSeekToEnd: bool + duration: float + datasize: float + filesize: float + + audiosize: Optional[float] = None + audiocodecid: Optional[float] = None + audiodatarate: Optional[float] = None + audiosamplerate: Optional[float] = None + audiosamplesize: Optional[float] = None + stereo: Optional[bool] = None + + videosize: float + framerate: float + videocodecid: float + videodatarate: float + width: float + height: float + + lasttimestamp: float + lastkeyframelocation: float + lastkeyframetimestamp: float + keyframes: KeyFramesDict + + +class Analyser: + def __init__(self) -> None: + self._metadatas: Subject[MetaData] = Subject() + self._reset() + + def _reset(self) -> None: + self._num_of_tags = 0 + self._num_of_audio_tags = 0 + self._num_of_video_tags = 0 + self._size_of_tags = 0 + self._size_of_audio_tags = 0 + self._size_of_video_tags = 0 + self._size_of_data = 0 + self._size_of_audio_data = 0 + self._size_of_video_data = 0 + self._last_timestamp = 0 + self._last_timestamp_of_audio = 0 + self._last_timestamp_of_video = 0 + self._keyframe_timestamps: List[int] = [] + self._keyframe_filepositions: List[int] = [] + self._resolution: Optional[Resolution] = None + + self._has_audio = False + self._has_video = False + self._audio_analysed = False + self._video_analysed = False + + @property + def metadatas(self) -> Observable[MetaData]: + return self._metadatas + + def __call__(self, source: FLVStream) -> FLVStream: + return self._analyse(source) + + def get_real_resolution(self) -> Optional[Resolution]: + return self._resolution + + def calc_frame_rate(self) -> float: + try: + return self._num_of_video_tags / self._last_timestamp_of_video * 1000 + except ZeroDivisionError: + return 0.0 + + def calc_audio_data_rate(self) -> float: + try: + return self._size_of_audio_data * 8 / self._last_timestamp_of_audio + except ZeroDivisionError: + return 0.0 + + def calc_video_data_rate(self) -> float: + try: + return self._size_of_video_data * 8 / self._last_timestamp_of_video + except ZeroDivisionError: + return 0.0 + + def calc_data_size(self) -> int: + return ( + self._size_of_audio_tags + + self._num_of_audio_tags * BACK_POINTER_SIZE + + self._size_of_video_tags + + self._num_of_video_tags * BACK_POINTER_SIZE + ) + + def calc_file_size(self) -> int: + return ( + self._size_of_flv_header + + BACK_POINTER_SIZE + + self._size_of_tags + + self._num_of_tags * BACK_POINTER_SIZE + ) + + def make_keyframes(self) -> KeyFrames: + return KeyFrames( + times=list(map(lambda t: t / 1000, self._keyframe_timestamps)), + filepositions=list(map(float, self._keyframe_filepositions)), + ) + + def make_metadata(self) -> MetaData: + assert self._has_audio == self._audio_analysed + assert self._has_video and self._video_analysed + assert self._resolution is not None + + if not self._has_audio: + audiosize = None + audiocodecid = None + audiodatarate = None + audiosamplerate = None + audiosamplesize = None + stereo = None + else: + audiosize = float(self._size_of_audio_tags) + audiocodecid = float(self._audio_codec_id) + audiodatarate = self.calc_audio_data_rate() + audiosamplerate = float(self._audio_sample_rate) + audiosamplesize = float(self._audio_sample_size) + stereo = self._stereo + + keyframes = self.make_keyframes() + + return MetaData( + hasAudio=self._has_audio, + hasVideo=self._has_video, + hasMetadata=True, + hasKeyframes=len(self._keyframe_timestamps) != 0, + canSeekToEnd=( + self._last_timestamp_of_video == self._keyframe_timestamps[-1] + ), + duration=self._last_timestamp / 1000, + datasize=float(self.calc_data_size()), + filesize=float(self.calc_file_size()), + audiosize=audiosize, + audiocodecid=audiocodecid, + audiodatarate=audiodatarate, + audiosamplerate=audiosamplerate, + audiosamplesize=audiosamplesize, + stereo=stereo, + videosize=float(self._size_of_video_tags), + framerate=self.calc_frame_rate(), + videocodecid=float(self._video_codec_id), + videodatarate=self.calc_video_data_rate(), + width=float(self._resolution.width), + height=float(self._resolution.height), + lasttimestamp=self._last_timestamp / 1000, + lastkeyframelocation=keyframes.filepositions[-1], + lastkeyframetimestamp=keyframes.times[-1], + keyframes=keyframes, + ) + + def _analyse(self, source: FLVStream) -> FLVStream: + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + stream_index: int = -1 + subscription = SerialDisposable() + + def push_metadata() -> None: + try: + metadata = self.make_metadata() + except Exception as e: + logger.warning(f'Failed to make metadata: {repr(e)}') + pass + else: + self._metadatas.on_next(metadata) + + def on_next(item: FLVStreamItem) -> None: + nonlocal stream_index + if isinstance(item, FlvHeader): + stream_index += 1 + if stream_index > 0: + push_metadata() + self._reset() + self._analyse_flv_header(item) + else: + self._analyse_tag(item) + observer.on_next(item) + + def on_completed() -> None: + push_metadata() + observer.on_completed() + + def on_error(e: Exception) -> None: + push_metadata() + observer.on_error(e) + + def dispose() -> None: + push_metadata() + + subscription.disposable = source.subscribe( + on_next, on_error, on_completed, scheduler=scheduler + ) + + return CompositeDisposable(subscription, Disposable(dispose)) + + return Observable(subscribe) + + def _analyse_flv_header(self, header: FlvHeader) -> None: + self._size_of_flv_header = header.size + + def _analyse_tag(self, tag: FlvTag) -> None: + if is_audio_tag(tag): + self._analyse_audio_tag(tag) + elif is_video_tag(tag): + self._analyse_video_tag(tag) + elif is_script_tag(tag): + self._analyse_script_tag(tag) + else: + logger.warning(f'Invalid tag type: {tag}') + + self._num_of_tags += 1 + self._size_of_tags += tag.tag_size + self._size_of_data += tag.data_size + self._last_timestamp = tag.timestamp + + def _analyse_audio_tag(self, tag: AudioTag) -> None: + if not self._audio_analysed: + self._has_audio = True + self._audio_analysed = True + self._audio_codec_id = tag.sound_format.value + self._audio_sample_rate = tag.sound_rate.value + self._audio_sample_size = tag.sound_size.value + self._stereo = tag.sound_type == SoundType.STEREO + + self._num_of_audio_tags += 1 + self._size_of_audio_tags += tag.tag_size + self._size_of_audio_data += tag.data_size + self._last_timestamp_of_audio = tag.timestamp + + def _analyse_video_tag(self, tag: VideoTag) -> None: + if tag.is_keyframe(): + self._keyframe_timestamps.append(tag.timestamp) + self._keyframe_filepositions.append(self.calc_file_size()) + if tag.is_avc_header(): + self._resolution = Resolution.from_aac_sequence_header(tag) + else: + pass + + if not self._video_analysed: + self._has_video = True + self._video_analysed = True + self._video_codec_id = tag.codec_id.value + + self._num_of_video_tags += 1 + self._size_of_video_tags += tag.tag_size + self._size_of_video_data += tag.data_size + self._last_timestamp_of_video = tag.timestamp + + def _analyse_script_tag(self, tag: ScriptTag) -> None: + pass diff --git a/src/blrec/flv/operators/concat.py b/src/blrec/flv/operators/concat.py new file mode 100644 index 0000000..b772cac --- /dev/null +++ b/src/blrec/flv/operators/concat.py @@ -0,0 +1,406 @@ +from __future__ import annotations + +import logging +from enum import IntEnum, auto +from typing import Callable, List, Optional, TypedDict, cast + +import attr +from reactivex import Observable, Subject, abc +from reactivex.disposable import CompositeDisposable, Disposable, SerialDisposable +from typing_extensions import TypeGuard + +from ...utils.hash import cksum +from ..common import ( + create_script_tag, + is_audio_sequence_header, + is_metadata_tag, + is_script_tag, + is_sequence_header, + is_video_sequence_header, + parse_scriptdata, +) +from ..models import AudioTag, FlvHeader, FlvTag, ScriptTag, VideoTag +from ..scriptdata import ScriptData +from ..utils import format_timestamp +from .typing import FLVStream, FLVStreamItem + +__all__ = ('concat', 'JoinPointExtractor', 'JoinPoint', 'JoinPointData') + +logger = logging.getLogger(__name__) + + +@attr.s(auto_attribs=True, slots=True, frozen=True) +class JoinPoint: + seamless: bool + timestamp: float # timestamp of previous tag in milliseconds + crc32: str # crc32 of the previous tag + + @classmethod + def from_metadata_value(cls, value: JoinPointData) -> JoinPoint: + return cls( + timestamp=int(value['timestamp']), + seamless=value['seamless'], + crc32=value['crc32'], + ) + + def to_metadata_value(self) -> JoinPointData: + return dict( + timestamp=float(self.timestamp), seamless=self.seamless, crc32=self.crc32 + ) + + def __str__(self) -> str: + return 'seamless: {}, timestamp: {}, crc32: {}'.format( + 'yes' if self.seamless else 'no', + format_timestamp(int(self.timestamp)), + self.crc32, + ) + + +class JoinPointData(TypedDict): + seamless: bool + timestamp: float + crc32: str + + +class ACTION(IntEnum): + NOOP = auto() + CORRECT = auto() + GATHER = auto() + CANCEL = auto() + CONCAT = auto() + CONCAT_AND_GATHER = auto() + + +def concat( + num_of_last_tags: int = 3, max_duration: int = 20_000 +) -> Callable[[FLVStream], FLVStream]: + """Concat FLV streams. + + num_of_last_tags: Number of tags for determining whether or not tags are duplicated + max_duration: Max duration in milliseconds the duplicated tags might last + """ + + def _concat(source: FLVStream) -> FLVStream: + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + delta: int = 0 + action: ACTION = ACTION.NOOP + last_tags: List[FlvTag] = [] + gathered_tags: List[FlvTag] = [] + last_flv_header: Optional[FlvHeader] = None + last_audio_sequence_header: Optional[AudioTag] = None + last_video_sequence_header: Optional[VideoTag] = None + + def update_last_tags(tag: FlvTag) -> None: + nonlocal last_audio_sequence_header, last_video_sequence_header + last_tags.append(tag) + if len(last_tags) > num_of_last_tags: + last_tags.pop(0) + if is_audio_sequence_header(tag): + last_audio_sequence_header = tag + elif is_video_sequence_header(tag): + last_video_sequence_header = tag + + def gather_tags(tag: FlvTag) -> None: + nonlocal gathered_tags + nonlocal action + nonlocal last_audio_sequence_header, last_video_sequence_header + if is_audio_sequence_header(tag): + if last_audio_sequence_header is None: + logger.debug( + 'Cancel concat due to no last audio sequence header' + ) + action = ACTION.CANCEL + else: + if not tag.is_the_same_as(last_audio_sequence_header): + action = ACTION.CANCEL + logger.debug( + 'Cancel concat due to audio sequence header changed' + ) + last_audio_sequence_header = tag + elif is_video_sequence_header(tag): + if last_video_sequence_header is None: + logger.debug( + 'Cancel concat due to no last video sequence header' + ) + action = ACTION.CANCEL + else: + if not tag.is_the_same_as(last_video_sequence_header): + action = ACTION.CANCEL + logger.debug( + 'Cancel concat due to video sequence header changed' + ) + last_video_sequence_header = tag + gathered_tags.append(tag) + + def has_gathering_completed() -> bool: + # XXX: timestamp MUST start from 0 and continuous! + # put the correct and fix operator on upstream of this operator + # to ensure timestamp start from 0 and continuous! + return gathered_tags[-1].timestamp >= max_duration + + def find_last_duplicated_tag(tags: List[FlvTag]) -> int: + logger.debug('Finding duplicated tags...') + + last_out_tag = last_tags[-1] + logger.debug(f'The last output tag is {last_out_tag}') + + for idx, tag in enumerate(tags): + if not tag.is_the_same_as(last_out_tag): + continue + + if not all( + map( + lambda t: t[0].is_the_same_as(t[1]), + zip( + tags[max(0, idx - (len(last_tags) - 1)) : idx], + last_tags[:-1], + ), + ) + ): + continue + + logger.debug(f'The last duplicated tag found at {idx} is {tag}') + return idx + + logger.debug('No duplicated tag found') + return -1 + + def update_delta_duplicated(last_duplicated_tag: FlvTag) -> None: + nonlocal delta + delta = last_tags[-1].timestamp - last_duplicated_tag.timestamp + + def update_delta_no_duplicated(first_data_tag: FlvTag) -> None: + nonlocal delta + delta = last_tags[-1].timestamp - first_data_tag.timestamp + 10 + + def correct_ts(tag: FlvTag) -> FlvTag: + if delta == 0: + return tag + return tag.evolve(timestamp=tag.timestamp + delta) + + def make_join_point_tag(tag: FlvTag, seamless: bool) -> ScriptTag: + assert tag.body is not None + join_point = JoinPoint( + seamless=seamless, + timestamp=float(tag.timestamp), + crc32=cksum(tag.body), + ) + script_data = ScriptData( + name='onJoinPoint', value=attr.asdict(join_point) + ) + script_tag = create_script_tag(script_data) + return script_tag + + def do_concat() -> None: + logger.debug( + 'Concatenating... gathered {} tags, total size: {}'.format( + len(gathered_tags), sum(t.tag_size for t in gathered_tags) + ) + ) + + tags = list( + filter( + lambda tag: not is_metadata_tag(tag) + and not is_sequence_header(tag), + gathered_tags, + ) + ) + logger.debug( + '{} data tags, total size: {}'.format( + len(tags), sum(t.tag_size for t in tags) + ) + ) + + if not tags: + return + + if (index := find_last_duplicated_tag(tags)) >= 0: + seamless = True + update_delta_duplicated(tags[index]) + logger.debug(f'Updated delta: {delta}, seamless: {seamless}') + tags = tags[index + 1 :] + else: + seamless = False + update_delta_no_duplicated(tags[0]) + logger.debug(f'Updated delta: {delta}, seamless: {seamless}') + + join_point_tag = make_join_point_tag(last_tags[-1], seamless) + observer.on_next(join_point_tag) + + for tag in tags: + tag = correct_ts(tag) + update_last_tags(tag) + observer.on_next(tag) + gathered_tags.clear() + + def do_cancel() -> None: + logger.debug( + 'Cancelling... gathered {} tags, total size: {}'.format( + len(gathered_tags), sum(t.tag_size for t in gathered_tags) + ) + ) + assert last_flv_header is not None + observer.on_next(last_flv_header) + for tag in gathered_tags: + update_last_tags(tag) + observer.on_next(tag) + gathered_tags.clear() + + def on_next(item: FLVStreamItem) -> None: + nonlocal action + nonlocal last_flv_header + + if isinstance(item, FlvHeader): + if last_flv_header is None: + logger.debug('No operation needed for the first stream') + last_flv_header = item + action = ACTION.NOOP + observer.on_next(item) + else: + logger.debug('Gathering tags for deduplication...') + last_flv_header = item + if action == ACTION.GATHER: + action = ACTION.CONCAT_AND_GATHER + else: + action = ACTION.GATHER + return + + tag = item + + while True: + if action == ACTION.NOOP: + update_last_tags(tag) + observer.on_next(tag) + return + + if action == ACTION.CORRECT: + tag = correct_ts(tag) + update_last_tags(tag) + observer.on_next(tag) + return + + if action in (ACTION.CONCAT, ACTION.CONCAT_AND_GATHER): + do_concat() + if action == ACTION.CONCAT_AND_GATHER: + action = ACTION.GATHER + else: + action = ACTION.CORRECT + return + + if action == ACTION.GATHER: + gather_tags(tag) + if action == ACTION.CANCEL: + do_cancel() + action = ACTION.NOOP + return + if has_gathering_completed(): + action = ACTION.CONCAT + continue + + break + + def on_completed() -> None: + if action == ACTION.GATHER: + do_concat() + observer.on_completed() + + def on_error(e: Exception) -> None: + if action == ACTION.GATHER: + do_concat() + observer.on_error(e) + + return source.subscribe( + on_next, on_error, on_completed, scheduler=scheduler + ) + + return Observable(subscribe) + + return _concat + + +class JoinPointExtractor: + def __init__(self) -> None: + self._join_points: Subject[List[JoinPoint]] = Subject() + + @property + def join_points(self) -> Observable[List[JoinPoint]]: + return self._join_points + + def __call__(self, source: FLVStream) -> FLVStream: + return self._extract(source) + + def _extract(self, source: FLVStream) -> FLVStream: + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + stream_index: int = -1 + subscription = SerialDisposable() + join_points: List[JoinPoint] = [] + join_point_tag: Optional[ScriptTag] = None + + def push_join_points() -> None: + self._join_points.on_next(join_points.copy()) + + def on_next(item: FLVStreamItem) -> None: + nonlocal stream_index + nonlocal join_point_tag + + if isinstance(item, FlvHeader): + stream_index += 1 + if stream_index > 0: + push_join_points() + join_points.clear() + join_point_tag = None + observer.on_next(item) + return + + if join_point_tag: + join_point = self._make_join_point(join_point_tag, item) + join_points.append(join_point) + logger.debug(f'{repr(join_point)}; {join_point}') + join_point_tag = None + + if self._is_join_point_tag(item): + join_point_tag = item + return + + observer.on_next(item) + + def on_completed() -> None: + push_join_points() + observer.on_completed() + + def on_error(e: Exception) -> None: + push_join_points() + observer.on_error(e) + + def dispose() -> None: + push_join_points() + + subscription.disposable = source.subscribe( + on_next, on_error, on_completed, scheduler=scheduler + ) + + return CompositeDisposable(subscription, Disposable(dispose)) + + return Observable(subscribe) + + def _is_join_point_tag(self, tag: FlvTag) -> TypeGuard[ScriptTag]: + if is_script_tag(tag): + script_data = parse_scriptdata(tag) + return script_data['name'] == 'onJoinPoint' + return False + + def _make_join_point(self, join_point_tag: ScriptTag, tag: FlvTag) -> JoinPoint: + assert tag.body is not None + script_data = parse_scriptdata(join_point_tag) + join_point_data = cast(JoinPointData, script_data['value']) + return JoinPoint( + seamless=join_point_data['seamless'], + timestamp=tag.timestamp, + crc32=cksum(tag.body), + ) diff --git a/src/blrec/flv/operators/correct.py b/src/blrec/flv/operators/correct.py new file mode 100644 index 0000000..cf60dec --- /dev/null +++ b/src/blrec/flv/operators/correct.py @@ -0,0 +1,64 @@ +import logging +from typing import Callable, Optional + +from reactivex import Observable, abc + +from ..common import is_script_tag, is_sequence_header +from ..models import FlvHeader, FlvTag +from .typing import FLVStream, FLVStreamItem + +__all__ = ('correct',) + +logger = logging.getLogger(__name__) + + +def correct() -> Callable[[FLVStream], FLVStream]: + def _correct(source: FLVStream) -> FLVStream: + """Correct the timestamp offset of the FLV tags.""" + + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + delta: Optional[int] = None + + def correct_ts(tag: FlvTag, delta: int) -> FlvTag: + if delta == 0: + return tag + return tag.evolve(timestamp=tag.timestamp + delta) + + def on_next(item: FLVStreamItem) -> None: + nonlocal delta + + if isinstance(item, FlvHeader): + delta = None + observer.on_next(item) + return + + tag = item + + if is_script_tag(tag): + tag = correct_ts(tag, -tag.timestamp) + observer.on_next(tag) + return + + if delta is None: + if is_sequence_header(tag): + tag = correct_ts(tag, -tag.timestamp) + else: + logger.debug(f'The first data tag: {tag}') + delta = -tag.timestamp + logger.debug(f'Timestamp delta: {delta}') + tag = correct_ts(tag, delta) + else: + tag = correct_ts(tag, delta) + + observer.on_next(tag) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) + + return _correct diff --git a/src/blrec/flv/operators/cut.py b/src/blrec/flv/operators/cut.py new file mode 100644 index 0000000..8386132 --- /dev/null +++ b/src/blrec/flv/operators/cut.py @@ -0,0 +1,113 @@ +from __future__ import annotations + +import logging +from typing import Optional + +from reactivex import Observable, abc + +from ..common import ( + is_audio_sequence_header, + is_metadata_tag, + is_video_nalu_keyframe, + is_video_sequence_header, +) +from ..models import AudioTag, FlvHeader, FlvTag, ScriptTag, VideoTag +from ..utils import format_timestamp +from .correct import correct +from .typing import FLVStream, FLVStreamItem + +__all__ = ('Cutter',) + + +logger = logging.getLogger(__name__) + + +class Cutter: + def __init__(self, min_duration: int = 5_000) -> None: + self._min_duration = min_duration # milliseconds + self._reset() + + def _reset(self) -> None: + self._cutting: bool = False + self._triggered: bool = False + self._last_timestamp: int = 0 + self._last_flv_header: Optional[FlvHeader] = None + self._last_metadata_tag: Optional[ScriptTag] = None + self._last_audio_sequence_header: Optional[AudioTag] = None + self._last_video_sequence_header: Optional[VideoTag] = None + + def is_cutting(self) -> bool: + return self._cutting + + def can_cut_stream(self) -> bool: + if self._triggered or self._cutting: + return False + return self._last_timestamp >= self._min_duration + + def cut_stream(self) -> bool: + if self.can_cut_stream(): + self._triggered = True + return True + return False + + def __call__(self, source: FLVStream) -> FLVStream: + return self._cut(source).pipe(correct()) + + def _cut(self, source: FLVStream) -> FLVStream: + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + def on_next(item: FLVStreamItem) -> None: + if isinstance(item, FlvHeader): + self._reset() + self._update_flv_header(item) + else: + self._update_meta_tags(item) + self._check_cut_point(item) + if self._cutting: + self._insert_header_and_tags(observer) + self._cutting = False + self._triggered = False + observer.on_next(item) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) + + def _update_flv_header(self, header: FlvHeader) -> None: + self._last_flv_header = header + + def _update_meta_tags(self, tag: FlvTag) -> None: + if is_metadata_tag(tag): + self._last_metadata_tag = tag + elif is_audio_sequence_header(tag): + self._last_audio_sequence_header = tag + elif is_video_sequence_header(tag): + self._last_video_sequence_header = tag + + def _insert_header_and_tags( + self, observer: abc.ObserverBase[FLVStreamItem] + ) -> None: + assert self._last_flv_header is not None + assert self._last_audio_sequence_header is not None + assert self._last_video_sequence_header is not None + observer.on_next(self._last_flv_header) + if self._last_metadata_tag is not None: + observer.on_next(self._last_metadata_tag) + observer.on_next(self._last_audio_sequence_header) + observer.on_next(self._last_video_sequence_header) + + def _check_cut_point(self, tag: FlvTag) -> None: + self._last_timestamp = tag.timestamp + + if not self._triggered: + return + + if not is_video_nalu_keyframe(tag): + return + + self._cutting = True + logger.info(f'Cut stream at {format_timestamp(tag.timestamp)}') diff --git a/src/blrec/flv/operators/defragment.py b/src/blrec/flv/operators/defragment.py new file mode 100644 index 0000000..5482b6b --- /dev/null +++ b/src/blrec/flv/operators/defragment.py @@ -0,0 +1,56 @@ +import logging +from typing import Callable, List, Optional + +from reactivex import Observable, abc + +from ..models import FlvHeader +from .typing import FLVStream, FLVStreamItem + +__all__ = ('defragment',) + +logger = logging.getLogger(__name__) + + +def defragment(min_tags: int = 10) -> Callable[[FLVStream], FLVStream]: + def _defragment(source: FLVStream) -> FLVStream: + """Discard fragmented FLV streams.""" + + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + gathering: bool = False + gathered_items: List[FLVStreamItem] = [] + + def on_next(item: FLVStreamItem) -> None: + nonlocal gathering + + if isinstance(item, FlvHeader): + if gathered_items: + logger.debug( + 'Discarded {} items, total size: {}'.format( + len(gathered_items), sum(len(t) for t in gathered_items) + ) + ) + gathered_items.clear() + gathering = True + logger.debug('Gathering items...') + + if gathering: + gathered_items.append(item) + if len(gathered_items) > min_tags: + for item in gathered_items: + observer.on_next(item) + gathered_items.clear() + gathering = False + logger.debug('Not a fragmented stream, stopped the gathering') + else: + observer.on_next(item) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) + + return _defragment diff --git a/src/blrec/flv/operators/dump.py b/src/blrec/flv/operators/dump.py new file mode 100644 index 0000000..42b3d29 --- /dev/null +++ b/src/blrec/flv/operators/dump.py @@ -0,0 +1,123 @@ +import io +import logging +from typing import Callable, Optional, Tuple + +from reactivex import Observable, Subject, abc +from reactivex.disposable import CompositeDisposable, Disposable, SerialDisposable + +from ..io import FlvWriter +from ..models import FlvHeader +from .typing import FLVStream, FLVStreamItem + +__all__ = ('Dumper',) + +logger = logging.getLogger(__name__) + + +class Dumper: + def __init__( + self, + path_provider: Callable[..., Tuple[str, int]], + buffer_size: Optional[int] = None, + ) -> None: + self.buffer_size = buffer_size or io.DEFAULT_BUFFER_SIZE # bytes + self._path_provider = path_provider + self._file_opened: Subject[Tuple[str, int]] = Subject() + self._file_closed: Subject[str] = Subject() + self._size_updates: Subject[int] = Subject() + self._timestamp_updates: Subject[int] = Subject() + self._reset() + + def _reset(self) -> None: + self._path: str = '' + self._file: Optional[io.BufferedReader] = None + self._flv_writer: Optional[FlvWriter] = None + + @property + def path(self) -> str: + return self._path + + @property + def file_opened(self) -> Observable[Tuple[str, int]]: + return self._file_opened + + @property + def file_closed(self) -> Observable[str]: + return self._file_closed + + @property + def size_updates(self) -> Observable[int]: + return self._size_updates + + @property + def timestamp_updates(self) -> Observable[int]: + return self._timestamp_updates + + def __call__(self, source: FLVStream) -> FLVStream: + return self._dump(source) + + def _open_file(self) -> None: + self._path, timestamp = self._path_provider() + self._file = open(self._path, 'wb', buffering=self.buffer_size) # type: ignore + logger.debug(f'Opened file: {self._path}') + self._file_opened.on_next((self._path, timestamp)) + + def _close_file(self) -> None: + if self._file is not None and not self._file.closed: + self._file.close() + logger.debug(f'Closed file: {self._path}') + self._file_closed.on_next(self._path) + + def _dump(self, source: FLVStream) -> FLVStream: + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + subscription = SerialDisposable() + + self._close_file() + self._reset() + + def on_next(item: FLVStreamItem) -> None: + try: + if isinstance(item, FlvHeader): + self._close_file() + self._open_file() + assert self._file is not None + self._flv_writer = FlvWriter(self._file) + size = self._flv_writer.write_header(item) + self._size_updates.on_next(size) + self._timestamp_updates.on_next(0) + else: + if self._flv_writer is not None: + size = self._flv_writer.write_tag(item) + self._size_updates.on_next(size) + self._timestamp_updates.on_next(item.timestamp) + + observer.on_next(item) + except Exception as e: + self._close_file() + self._reset() + observer.on_error(e) + + def on_completed() -> None: + self._close_file() + self._reset() + observer.on_completed() + + def on_error(e: Exception) -> None: + self._close_file() + self._reset() + observer.on_error(e) + + def dispose() -> None: + self._close_file() + self._reset() + + subscription.disposable = source.subscribe( + on_next, on_error, on_completed, scheduler=scheduler + ) + + return CompositeDisposable(subscription, Disposable(dispose)) + + return Observable(subscribe) diff --git a/src/blrec/flv/operators/fix.py b/src/blrec/flv/operators/fix.py new file mode 100644 index 0000000..c844071 --- /dev/null +++ b/src/blrec/flv/operators/fix.py @@ -0,0 +1,152 @@ +import logging +import math +from typing import Callable, Optional + +from reactivex import Observable, abc + +from ..common import ( + is_audio_tag, + is_metadata_tag, + is_script_tag, + is_video_tag, + parse_metadata, +) +from ..models import AudioTag, FlvHeader, FlvTag, ScriptTag, VideoTag +from .typing import FLVStream, FLVStreamItem + +__all__ = ('fix',) + +logger = logging.getLogger(__name__) + + +def fix() -> Callable[[FLVStream], FLVStream]: + def _fix(source: FLVStream) -> FLVStream: + """Fix broken timestamps of the FLV tags.""" + + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + delta: int = 0 + last_tag: Optional[FlvTag] = None + last_audio_tag: Optional[AudioTag] = None + last_video_tag: Optional[VideoTag] = None + frame_rate = 30.0 + video_frame_interval = math.ceil(1000 / frame_rate) + sound_sample_interval = math.ceil(1000 / 44) + + def reset() -> None: + nonlocal delta, last_tag, last_audio_tag, last_video_tag + nonlocal frame_rate, video_frame_interval, sound_sample_interval + delta = 0 + last_tag = None + last_audio_tag = None + last_video_tag = None + frame_rate = 30.0 + video_frame_interval = math.ceil(1000 / frame_rate) + sound_sample_interval = math.ceil(1000 / 44) + + def update_parameters(tag: ScriptTag) -> None: + nonlocal frame_rate, video_frame_interval + metadata = parse_metadata(tag) + fps = metadata.get('fps') or metadata.get('framerate') + + if not fps: + return + + frame_rate = fps + video_frame_interval = math.ceil(1000 / frame_rate) + + logger.debug( + 'frame rate: {}, video frame interval: {}'.format( + frame_rate, video_frame_interval + ) + ) + + def update_last_tags(tag: FlvTag) -> None: + nonlocal last_tag, last_audio_tag, last_video_tag + last_tag = tag + if is_audio_tag(tag): + last_audio_tag = tag + elif is_video_tag(tag): + last_video_tag = tag + + def update_delta(tag: FlvTag) -> None: + nonlocal delta + assert last_tag is not None + delta = last_tag.timestamp + delta - tag.timestamp + calc_interval(tag) + + def correct_ts(tag: FlvTag) -> FlvTag: + if delta == 0: + return tag + return tag.evolve(timestamp=tag.timestamp + delta) + + def calc_interval(tag: FlvTag) -> int: + if is_audio_tag(tag): + return sound_sample_interval + elif is_video_tag(tag): + return video_frame_interval + else: + logger.warning(f'Unexpected tag type: {tag}') + return min(sound_sample_interval, video_frame_interval) + + def is_ts_rebounded(tag: FlvTag) -> bool: + if is_audio_tag(tag): + if last_audio_tag is None: + return False + return tag.timestamp < last_audio_tag.timestamp + elif is_video_tag(tag): + if last_video_tag is None: + return False + return tag.timestamp < last_video_tag.timestamp + else: + return False + + def is_ts_incontinuous(tag: FlvTag) -> bool: + if last_tag is None: + return False + return tag.timestamp - last_tag.timestamp > max( + sound_sample_interval, video_frame_interval + ) + + def on_next(item: FLVStreamItem) -> None: + if isinstance(item, FlvHeader): + reset() + observer.on_next(item) + return + + tag = item + + if is_script_tag(tag): + if is_metadata_tag(tag): + update_parameters(tag) + observer.on_next(tag) + return + + if is_ts_rebounded(tag): + update_delta(tag) + logger.warning( + f'Timestamp rebounded, updated delta: {delta}\n' + f'last audio tag: {last_audio_tag}\n' + f'last video tag: {last_video_tag}\n' + f'current tag: {tag}' + ) + elif is_ts_incontinuous(tag): + update_delta(tag) + logger.warning( + f'Timestamp incontinuous, updated delta: {delta}\n' + f'last tag: {last_tag}\n' + f'current tag: {tag}' + ) + + update_last_tags(tag) + tag = correct_ts(tag) + observer.on_next(tag) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) + + return _fix diff --git a/src/blrec/flv/operators/helpers.py b/src/blrec/flv/operators/helpers.py new file mode 100644 index 0000000..4a0c981 --- /dev/null +++ b/src/blrec/flv/operators/helpers.py @@ -0,0 +1,38 @@ +import io +import logging + +from reactivex import of + +from .parse import parse +from .typing import FLVStream + +__all__ = ('from_file', 'from_stream') + +logger = logging.getLogger(__name__) + + +def from_stream( + stream: io.RawIOBase, + *, + complete_on_eof: bool = False, + backup_timestamp: bool = False, + restore_timestamp: bool = False, +) -> FLVStream: + return of(stream).pipe( + parse( + complete_on_eof=complete_on_eof, + backup_timestamp=backup_timestamp, + restore_timestamp=restore_timestamp, + ) + ) + + +def from_file( + path: str, *, backup_timestamp: bool = False, restore_timestamp: bool = False +) -> FLVStream: + return from_stream( + open(path, 'rb'), + complete_on_eof=True, + backup_timestamp=backup_timestamp, + restore_timestamp=restore_timestamp, + ) diff --git a/src/blrec/flv/operators/inject.py b/src/blrec/flv/operators/inject.py new file mode 100644 index 0000000..d2e7e03 --- /dev/null +++ b/src/blrec/flv/operators/inject.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +import logging +from typing import Any, Callable, Dict, Optional, cast + +from reactivex import Observable, abc + +from ..common import ( + create_metadata_tag, + enrich_metadata, + is_metadata_tag, + parse_metadata, + update_metadata, +) +from ..models import FlvHeader, ScriptTag +from .analyse import KeyFramesDict +from .typing import FLVStream, FLVStreamItem + +__all__ = ('Injector',) + + +logger = logging.getLogger(__name__) + + +class Injector: + def __init__(self, metadata_provider: Callable[..., Dict[str, Any]]) -> None: + self._metadata_provider = metadata_provider + + def __call__(self, source: FLVStream) -> FLVStream: + return self._inject(source) + + def _inject(self, source: FLVStream) -> FLVStream: + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + index = 0 + + def on_next(item: FLVStreamItem) -> None: + nonlocal index + if isinstance(item, FlvHeader): + index = 0 + observer.on_next(item) + return + + tag = item + index += 1 + + if index == 1: + if is_metadata_tag(tag): + tag = self._inject_metadata(tag) + logger.debug('Injected metadata into the metadata tag') + else: + logger.debug('No metadata tag in the stream') + tag = self._make_metadata_tag() + logger.debug('Maked a metadata tag for metadata injection') + observer.on_next(tag) + logger.debug('Inserted the artificial metadata tag') + + observer.on_next(tag) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) + + def _inject_metadata(self, tag: ScriptTag) -> ScriptTag: + old_metadata = parse_metadata(tag) + new_metadata = self._metadata_provider() + final_metadata = { + **{'duration': 0.0, 'filesize': 0.0}, + **old_metadata, + **new_metadata, + } + new_tag = enrich_metadata(tag, final_metadata, offset=tag.offset) + + if 'keyframes' in final_metadata: + keyframes = cast(KeyFramesDict, final_metadata['keyframes']) + offset = new_tag.tag_size - tag.tag_size + keyframes['filepositions'] = list( + map(lambda p: p + offset, keyframes['filepositions']) + ) + if 'lastkeyframelocation' in final_metadata: + final_metadata['lastkeyframelocation'] = keyframes['filepositions'][-1] + new_tag = update_metadata(new_tag, final_metadata) + + return new_tag + + def _make_metadata_tag(self) -> ScriptTag: + metadata = self._metadata_provider() + metadata = {'duration': 0.0, 'filesize': 0.0, **metadata} + return create_metadata_tag(metadata) diff --git a/src/blrec/flv/operators/limit.py b/src/blrec/flv/operators/limit.py new file mode 100644 index 0000000..4db22f0 --- /dev/null +++ b/src/blrec/flv/operators/limit.py @@ -0,0 +1,153 @@ +from __future__ import annotations + +import logging +from typing import Optional + +from reactivex import Observable, abc + +from ..common import ( + is_audio_sequence_header, + is_metadata_tag, + is_video_nalu_keyframe, + is_video_sequence_header, +) +from ..models import BACK_POINTER_SIZE, AudioTag, FlvHeader, FlvTag, ScriptTag, VideoTag +from .correct import correct +from .typing import FLVStream, FLVStreamItem + +__all__ = ('Limiter',) + + +logger = logging.getLogger(__name__) + + +class Limiter: + def __init__( + self, + filesize_limit: int = 0, # file size in bytes, no limit by default. + duration_limit: int = 0, # duration in seconds, no limit by default. + ) -> None: + self.filesize_limit = filesize_limit + self.duration_limit = duration_limit + self._reset() + + def _reset(self) -> None: + self._filesize: int = 0 + self._duration: float = 0.0 + self._max_size_between_keyframes: int = 0 + self._max_duration_between_keyframes: float = 0.0 + self._first_keyframe_tag: Optional[VideoTag] = None + self._last_keyframe_tag: Optional[VideoTag] = None + self._last_flv_header: Optional[FlvHeader] = None + self._last_metadata_tag: Optional[ScriptTag] = None + self._last_audio_sequence_header: Optional[AudioTag] = None + self._last_video_sequence_header: Optional[VideoTag] = None + + def __call__(self, source: FLVStream) -> FLVStream: + return self._limit(source).pipe(correct()) + + def _limit(self, source: FLVStream) -> FLVStream: + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + def on_next(item: FLVStreamItem) -> None: + if isinstance(item, FlvHeader): + self._reset() + self._update_flv_header(item) + else: + self._update_meta_tags(item) + will_over_limts = self._check_limits(item) + if will_over_limts: + self._insert_header_and_tags(observer) + observer.on_next(item) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) + + def _insert_header_and_tags( + self, observer: abc.ObserverBase[FLVStreamItem] + ) -> None: + assert self._last_flv_header is not None + assert self._last_audio_sequence_header is not None + assert self._last_video_sequence_header is not None + observer.on_next(self._last_flv_header) + if self._last_metadata_tag is not None: + observer.on_next(self._last_metadata_tag) + observer.on_next(self._last_audio_sequence_header) + observer.on_next(self._last_video_sequence_header) + + self._filesize = ( + self._last_flv_header.size + + self._last_audio_sequence_header.tag_size + + self._last_video_sequence_header.tag_size + ) + if self._last_metadata_tag is not None: + self._filesize += self._last_metadata_tag.tag_size + self._duration = 0.0 + self._first_keyframe_tag = self._last_keyframe_tag + + def _will_filesize_over_limit(self) -> bool: + return self._filesize + self._max_size_between_keyframes >= self.filesize_limit + + def _will_duration_over_limit(self) -> bool: + return ( + self._duration + self._max_duration_between_keyframes >= self.duration_limit + ) + + def _update_flv_header(self, header: FlvHeader) -> None: + self._filesize += header.size + BACK_POINTER_SIZE + self._last_flv_header = header + + def _update_meta_tags(self, tag: FlvTag) -> None: + if is_metadata_tag(tag): + self._last_metadata_tag = tag + elif is_audio_sequence_header(tag): + self._last_audio_sequence_header = tag + elif is_video_sequence_header(tag): + self._last_video_sequence_header = tag + + def _check_limits(self, tag: FlvTag) -> bool: + self._filesize += tag.tag_size + BACK_POINTER_SIZE + + if not is_video_nalu_keyframe(tag): + return False + + if self._first_keyframe_tag is None: + self._first_keyframe_tag = tag + + if self._last_keyframe_tag is not None: + self._max_size_between_keyframes = max( + self._max_size_between_keyframes, + tag.offset - self._last_keyframe_tag.offset, + ) + self._max_duration_between_keyframes = max( + self._max_duration_between_keyframes, + (tag.timestamp - self._last_keyframe_tag.timestamp) / 1000, + ) + + self._last_keyframe_tag = tag + self._duration = ( + self._last_keyframe_tag.timestamp - self._first_keyframe_tag.timestamp + ) / 1000 + + if self.filesize_limit > 0 and self._will_filesize_over_limit(): + logger.debug( + 'File size will be over the limit: {} + {}'.format( + self._filesize, self._max_size_between_keyframes + ) + ) + return True + + if self.duration_limit > 0 and self._will_duration_over_limit(): + logger.debug( + 'Duration will be over the limit: {} + {}'.format( + self._duration, self._max_duration_between_keyframes + ) + ) + return True + + return False diff --git a/src/blrec/flv/operators/parse.py b/src/blrec/flv/operators/parse.py new file mode 100644 index 0000000..84dc7cc --- /dev/null +++ b/src/blrec/flv/operators/parse.py @@ -0,0 +1,68 @@ +import io +import logging +from typing import Callable, Optional + +from reactivex import Observable, abc +from reactivex.disposable import CompositeDisposable, Disposable, SerialDisposable + +from ..io import FlvReader +from .typing import FLVStream, FLVStreamItem + +__all__ = ('parse',) + +logger = logging.getLogger(__name__) + + +def parse( + *, + ignore_eof: bool = False, + complete_on_eof: bool = False, + backup_timestamp: bool = False, + restore_timestamp: bool = False, +) -> Callable[[Observable[io.RawIOBase]], FLVStream]: + def _parse(source: Observable[io.RawIOBase]) -> FLVStream: + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + disposed = False + subscription = SerialDisposable() + + def on_next(stream: io.RawIOBase) -> None: + try: + try: + reader = FlvReader( + stream, + backup_timestamp=backup_timestamp, + restore_timestamp=restore_timestamp, + ) + observer.on_next(reader.read_header()) + while not disposed: + tag = reader.read_tag() + observer.on_next(tag) + finally: + stream.close() + except EOFError as e: + if complete_on_eof: + observer.on_completed() + else: + if not ignore_eof: + observer.on_error(e) + except Exception as e: + observer.on_error(e) + else: + observer.on_completed() + + subscription.disposable = source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + def dispose() -> None: + nonlocal disposed + disposed = True + + return CompositeDisposable(subscription, Disposable(dispose)) + + return Observable(subscribe) + + return _parse diff --git a/src/blrec/flv/operators/probe.py b/src/blrec/flv/operators/probe.py new file mode 100644 index 0000000..091a383 --- /dev/null +++ b/src/blrec/flv/operators/probe.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import io +import logging +from typing import List, Optional, cast + +from reactivex import Observable, Subject, abc + +from ...utils.ffprobe import StreamProfile, ffprobe +from ..io import FlvWriter +from ..models import FlvHeader, FlvTag +from .typing import FLVStream, FLVStreamItem + +__all__ = ('Prober', 'StreamProfile') + + +logger = logging.getLogger(__name__) + + +class Prober: + def __init__(self) -> None: + self._profiles: Subject[StreamProfile] = Subject() + + def _reset(self) -> None: + self._gathering: bool = False + self._gathered_items: List[FLVStreamItem] = [] + + @property + def profiles(self) -> Observable[StreamProfile]: + return self._profiles + + def __call__(self, source: FLVStream) -> FLVStream: + return self._probe(source) + + def _probe(self, source: FLVStream) -> FLVStream: + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + self._reset() + + def on_next(item: FLVStreamItem) -> None: + if isinstance(item, FlvHeader): + self._gathered_items.clear() + self._gathering = True + + if self._gathering: + self._gathered_items.append(item) + if len(self._gathered_items) >= 10: + try: + self._do_probe() + except Exception as e: + logger.warning(f'Failed to probe stream: {repr(e)}') + finally: + self._gathered_items.clear() + self._gathering = False + + observer.on_next(item) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe) + + def _do_probe(self) -> None: + bytes_io = io.BytesIO() + writer = FlvWriter(bytes_io) + writer.write_header(cast(FlvHeader, self._gathered_items[0])) + for tag in self._gathered_items[1:]: + writer.write_tag(cast(FlvTag, tag)) + + def on_next(profile: StreamProfile) -> None: + self._profiles.on_next(profile) + + def on_error(e: Exception) -> None: + logger.warning(f'Failed to probe stream by ffprobe: {repr(e)}') + + ffprobe(bytes_io.getvalue()).subscribe(on_next, on_error) diff --git a/src/blrec/flv/operators/process.py b/src/blrec/flv/operators/process.py new file mode 100644 index 0000000..d69dfab --- /dev/null +++ b/src/blrec/flv/operators/process.py @@ -0,0 +1,19 @@ +import logging +from typing import Callable + +from .concat import concat +from .defragment import defragment +from .fix import fix +from .split import split +from .typing import FLVStream + +__all__ = ('process',) + +logger = logging.getLogger(__name__) + + +def process() -> Callable[[FLVStream], FLVStream]: + def _process(source: FLVStream) -> FLVStream: + return source.pipe(defragment(), split(), fix(), concat()) + + return _process diff --git a/src/blrec/flv/operators/progress.py b/src/blrec/flv/operators/progress.py new file mode 100644 index 0000000..f15c321 --- /dev/null +++ b/src/blrec/flv/operators/progress.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import logging +from typing import Optional + +from reactivex import Observable, abc +from reactivex.disposable import CompositeDisposable, Disposable, SerialDisposable +from tqdm import tqdm + +from .typing import FLVStream, FLVStreamItem + +__all__ = ('ProgressBar',) + + +logger = logging.getLogger(__name__) + + +class ProgressBar: + def __init__( + self, + desc: str, + postfix: Optional[str] = None, + total: Optional[int] = None, + disable: Optional[bool] = False, + ) -> None: + self._desc = desc + self._postfix = postfix + self._total = total + self._disable = disable + self._pbar: Optional[tqdm] = None + + def __call__(self, source: FLVStream) -> FLVStream: + return self._progress(source) + + def _progress(self, source: FLVStream) -> FLVStream: + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + subscription = SerialDisposable() + + self._pbar = tqdm( + disable=self._disable, + desc=self._desc, + total=self._total, + unit='B', + unit_scale=True, + unit_divisor=1024, + postfix=self._postfix, + ) + self._pbar.disable + + def on_next(item: FLVStreamItem) -> None: + if self._pbar is not None: + self._pbar.update(len(item)) + observer.on_next(item) + + def on_completed() -> None: + if self._pbar is not None: + self._pbar.close() + self._pbar = None + observer.on_completed() + + def dispose() -> None: + if self._pbar is not None: + self._pbar.close() + self._pbar = None + + subscription.disposable = source.subscribe( + on_next, observer.on_error, on_completed, scheduler=scheduler + ) + + return CompositeDisposable(subscription, Disposable(dispose)) + + return Observable(subscribe) diff --git a/src/blrec/flv/operators/split.py b/src/blrec/flv/operators/split.py new file mode 100644 index 0000000..df8d1c8 --- /dev/null +++ b/src/blrec/flv/operators/split.py @@ -0,0 +1,100 @@ +import logging +from typing import Callable, Optional + +from reactivex import Observable, abc + +from ..common import is_audio_sequence_header, is_metadata_tag, is_video_sequence_header +from ..models import AudioTag, FlvHeader, ScriptTag, VideoTag +from .correct import correct +from .typing import FLVStream, FLVStreamItem + +__all__ = ('split',) + +logger = logging.getLogger(__name__) + + +def split() -> Callable[[FLVStream], FLVStream]: + def _split(source: FLVStream) -> FLVStream: + """Split the FLV stream when av parameters are changed.""" + + def subscribe( + observer: abc.ObserverBase[FLVStreamItem], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + changed: bool = False + last_flv_header: Optional[FlvHeader] = None + last_metadata_tag: Optional[ScriptTag] = None + last_audio_sequence_header: Optional[AudioTag] = None + last_video_sequence_header: Optional[VideoTag] = None + + def reset() -> None: + nonlocal changed + nonlocal last_flv_header + nonlocal last_metadata_tag + nonlocal last_audio_sequence_header, last_video_sequence_header + changed = False + last_flv_header = None + last_metadata_tag = None + last_audio_sequence_header = last_video_sequence_header = None + + def insert_header_and_tags() -> None: + assert last_flv_header is not None + assert last_audio_sequence_header is not None + assert last_video_sequence_header is not None + observer.on_next(last_flv_header) + if last_metadata_tag is not None: + observer.on_next(last_metadata_tag) + observer.on_next(last_audio_sequence_header) + observer.on_next(last_video_sequence_header) + + def on_next(item: FLVStreamItem) -> None: + nonlocal changed + nonlocal last_flv_header + nonlocal last_metadata_tag + nonlocal last_audio_sequence_header, last_video_sequence_header + + if isinstance(item, FlvHeader): + reset() + last_flv_header = item + observer.on_next(item) + return + + tag = item + + if is_metadata_tag(tag): + logger.debug(f'Metadata tag: {tag}') + last_metadata_tag = tag + elif is_audio_sequence_header(tag): + logger.debug(f'Audio sequence header: {tag}') + if last_audio_sequence_header is not None: + if not tag.is_the_same_as(last_audio_sequence_header): + logger.warning('Audio parameters changed') + changed = True + last_audio_sequence_header = tag + return + last_audio_sequence_header = tag + elif is_video_sequence_header(tag): + logger.debug(f'Video sequence header: {tag}') + if last_video_sequence_header is not None: + if not tag.is_the_same_as(last_video_sequence_header): + logger.warning('Video parameters changed') + changed = True + last_video_sequence_header = tag + return + last_video_sequence_header = tag + else: + if changed: + logger.debug('Splitting stream...') + changed = False + insert_header_and_tags() + logger.debug('Splitted stream') + + observer.on_next(tag) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, scheduler=scheduler + ) + + return Observable(subscribe).pipe(correct()) + + return _split diff --git a/src/blrec/flv/operators/typing.py b/src/blrec/flv/operators/typing.py new file mode 100644 index 0000000..274302b --- /dev/null +++ b/src/blrec/flv/operators/typing.py @@ -0,0 +1,8 @@ +from typing import Union + +from reactivex import Observable + +from ..models import FlvHeader, FlvTag + +FLVStreamItem = Union[FlvHeader, FlvTag] +FLVStream = Observable[FLVStreamItem] diff --git a/src/blrec/flv/parameters_checker.py b/src/blrec/flv/parameters_checker.py deleted file mode 100644 index f40a6fe..0000000 --- a/src/blrec/flv/parameters_checker.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import annotations -import logging -from typing import Optional - - -from .models import AudioTag, FlvTag, ScriptTag, VideoTag -from .common import ( - is_audio_sequence_header, is_video_sequence_header, is_metadata_tag -) -from .exceptions import AudioParametersChanged, VideoParametersChanged - - -__all__ = 'ParametersChecker', - - -logger = logging.getLogger(__name__) - - -class ParametersChecker: - def __init__(self) -> None: - self.reset() - - @property - def last_metadata_tag(self) -> Optional[ScriptTag]: - return self._last_metadata_tag - - @property - def last_audio_header_tag(self) -> Optional[AudioTag]: - return self._last_audio_header_tag - - @property - def last_video_header_tag(self) -> Optional[VideoTag]: - return self._last_video_header_tag - - def reset(self) -> None: - self._last_metadata_tag: Optional[ScriptTag] = None - self._last_audio_header_tag: Optional[AudioTag] = None - self._last_video_header_tag: Optional[VideoTag] = None - - def check_tag(self, tag: FlvTag) -> None: - if is_audio_sequence_header(tag): - if self._last_audio_header_tag is not None: - if not tag.is_the_same_as(self._last_audio_header_tag): - logger.debug(f'Audio parameters changed: {tag}') - self._last_audio_header_tag = tag - raise AudioParametersChanged() - self._last_audio_header_tag = tag - elif is_video_sequence_header(tag): - if self._last_video_header_tag is not None: - if not tag.is_the_same_as(self._last_video_header_tag): - logger.debug(f'Video parameters changed: {tag}') - self._last_video_header_tag = tag - raise VideoParametersChanged() - self._last_video_header_tag = tag - elif is_metadata_tag(tag): - self._last_metadata_tag = tag - else: - pass diff --git a/src/blrec/flv/stream_cutter.py b/src/blrec/flv/stream_cutter.py deleted file mode 100644 index 64a0747..0000000 --- a/src/blrec/flv/stream_cutter.py +++ /dev/null @@ -1,60 +0,0 @@ -from __future__ import annotations -import logging -from typing import Optional - - -from .models import FlvTag, VideoTag -from .exceptions import CutStream -from .common import is_video_nalu_keyframe -from .utils import format_timestamp - - -__all__ = 'StreamCutter', - - -logger = logging.getLogger(__name__) - - -class StreamCutter: - def __init__(self, min_duration: int = 5_000) -> None: - self._min_duration = min_duration # milliseconds - self._last_position: int = 0 - self.reset() - - @property - def last_keyframe_tag(self) -> Optional[VideoTag]: - return self._last_keyframe_tag - - def is_cutting(self) -> bool: - return self._cutting - - def can_cut_stream(self) -> bool: - return self._timestamp >= self._min_duration - - def cut_stream(self) -> bool: - if self.can_cut_stream(): - self._triggered = True - return True - return False - - def reset(self) -> None: - self._cutting = False - self._triggered = False - self._timestamp: int = 0 - self._last_keyframe_tag: Optional[VideoTag] = None - - def check_tag(self, tag: FlvTag) -> None: - self._timestamp = tag.timestamp - - if not self._triggered: - return - - if not is_video_nalu_keyframe(tag): - return - - self._last_keyframe_tag = tag - self._last_position += self._timestamp - self._cutting = True - - logger.info(f'Cut stream at: {format_timestamp(self._last_position)}') - raise CutStream() diff --git a/src/blrec/flv/stream_processor.py b/src/blrec/flv/stream_processor.py deleted file mode 100644 index 5b4b3a9..0000000 --- a/src/blrec/flv/stream_processor.py +++ /dev/null @@ -1,943 +0,0 @@ -from __future__ import annotations -import io -import math -from abc import ABC, abstractmethod -import json -import logging -from typing import ( - Any, BinaryIO, Dict, List, Final, Iterable, Iterator, Optional, Tuple, - Protocol, TypedDict, Union, cast, TYPE_CHECKING -) - -import attr -from rx.subject import Subject -from rx.core import Observable - -from .models import FlvHeader, FlvTag, ScriptTag, VideoTag, AudioTag -from .data_analyser import DataAnalyser, MetaData -from .stream_cutter import StreamCutter -from .limit_checker import LimitChecker -from .parameters_checker import ParametersChecker -from .io import FlvReader, FlvWriter -from .io_protocols import RandomIO -from .utils import format_offest, format_timestamp -from .exceptions import ( - FlvTagError, - FlvStreamCorruptedError, - AudioParametersChanged, - VideoParametersChanged, - FileSizeOverLimit, - DurationOverLimit, - CutStream, -) -from .common import ( - is_metadata_tag, parse_metadata, is_audio_tag, is_video_tag, - is_video_sequence_header, is_audio_sequence_header, - enrich_metadata, update_metadata, is_data_tag, read_tags_in_duration, -) -from ..path import extra_metadata_path -if TYPE_CHECKING: - from ..core.stream_analyzer import StreamProfile - - -__all__ = 'StreamProcessor', 'BaseOutputFileManager', 'JoinPoint' - - -logger = logging.getLogger(__name__) - - -class StreamProcessor: - # Number of tags for determining whether or not tags are duplicated - _TAG_SEQUENCE_COUNT: Final[int] = 3 - # Max duration in milliseconds the duplicated tags might last - _MAX_DURATION: Final[int] = 20000 - - def __init__( - self, - file_manager: OutputFileManager, - *, - metadata: Optional[Dict[str, Any]] = None, - filesize_limit: int = 0, - duration_limit: int = 0, - disable_limit: bool = False, - analyse_data: bool = False, - dedup_join: bool = False, - save_extra_metadata: bool = False, - backup_timestamp: bool = False, - restore_timestamp: bool = False, - ) -> None: - self._file_manager = file_manager - self._parameters_checker = ParametersChecker() - self._stream_cutter = StreamCutter() - if not disable_limit: - self._limit_checker = LimitChecker(filesize_limit, duration_limit) - if analyse_data: - self._data_analyser = DataAnalyser() - - self._metadata = metadata.copy() if metadata else {} - self._metadata_tag: ScriptTag - - self._disable_limit = disable_limit - self._analyse_data = analyse_data - self._dedup_join = dedup_join - self._save_x_metadata = save_extra_metadata - self._backup_timestamp = backup_timestamp - self._restore_timestamp = restore_timestamp - - self._cancelled: bool = False - self._finalized: bool = False - self._stream_count: int = 0 - self._size_updates = Subject() - self._time_updates = Subject() - self._stream_profile_updates = Subject() - - self._delta: int = 0 - self._has_audio: bool = False - self._last_tags: List[FlvTag] = [] - self._join_points: List[JoinPoint] = [] - self._resetting_file: bool = False - - @property - def filesize_limit(self) -> int: - if self._disable_limit: - return 0 - return self._limit_checker.filesize_limit - - @filesize_limit.setter - def filesize_limit(self, value: int) -> None: - if not self._disable_limit: - self._limit_checker.filesize_limit = value - - @property - def duration_limit(self) -> int: - if self._disable_limit: - return 0 - return self._limit_checker.duration_limit - - @duration_limit.setter - def duration_limit(self, value: int) -> None: - if not self._disable_limit: - self._limit_checker.duration_limit = value - - @property - def join_points(self) -> Iterator[JoinPoint]: - for point in self._join_points: - yield point - - @property - def metadata(self) -> Optional[MetaData]: - if not self._analyse_data: - return None - try: - return self._data_analyser.make_metadata() - except AssertionError: - return None - except Exception as e: - logger.debug(f'Failed to make metadata data, due to: {repr(e)}') - return None - - @property - def size_updates(self) -> Observable: - return self._size_updates - - @property - def time_updates(self) -> Observable: - return self._time_updates - - @property - def stream_profile_updates(self) -> Observable: - return self._stream_profile_updates - - @property - def cancelled(self) -> bool: - return self._cancelled - - @property - def finalized(self) -> bool: - return self._finalized - - def cancel(self) -> None: - self._cancelled = True - - def set_metadata(self, metadata: Dict[str, Any]) -> None: - self._metadata = metadata.copy() - - def process_stream(self, stream: RandomIO) -> None: - assert not self._cancelled and not self._finalized, \ - 'should not be called after the processing cancelled or finalized' - self._stream_count += 1 - self._process_stream(stream) - - def can_cut_stream(self) -> bool: - return self._stream_cutter.can_cut_stream() - - def cut_stream(self) -> bool: - return self._stream_cutter.cut_stream() - - def finalize(self) -> None: - assert not self._finalized, \ - 'should not be called after the processing finalized' - self._finalized = True - - if not self._need_to_finalize(): - logger.debug('No need to finalize stream processing') - return - - self._complete_file() - logger.debug('Finalized stream processing') - - def _need_to_finalize(self) -> bool: - return self._stream_count > 0 and len(self._last_tags) > 0 - - def _reset_params(self) -> None: - self._delta = 0 - self._has_audio = False - self._last_tags = [] - self._join_points = [] - self._resetting_file = False - - self._stream_cutter.reset() - if not self._disable_limit: - self._limit_checker.reset() - if self._analyse_data: - self._data_analyser.reset() - - def _new_file(self) -> None: - self._reset_params() - self._out_file = self._file_manager.create_file() - self._out_writer = FlvWriter(self._out_file) - logger.debug(f'New file: {self._file_manager.curr_path}') - - def _reset_file(self) -> None: - self._reset_params() - self._out_file.truncate(0) - logger.debug(f'Reset file: {self._file_manager.curr_path}') - - def _complete_file(self) -> None: - curr_path = self._file_manager.curr_path - - if self._save_x_metadata: - self._save_extra_metadata() - - self._update_metadata_tag() - self._file_manager.close_file() - - logger.debug(f'Complete file: {curr_path}') - - def _process_stream(self, stream: RandomIO) -> None: - logger.debug(f'Processing the {self._stream_count}th stream...') - - self._in_reader = FlvReaderWithTimestampFix( - stream, - backup_timestamp=self._backup_timestamp, - restore_timestamp=self._restore_timestamp, - ) - - flv_header = self._read_header() - self._has_audio = flv_header.has_audio() - - try: - first_data_tag = self._read_first_data_tag() - if not self._last_tags: - self._process_initial_stream(flv_header, first_data_tag) - else: - self._process_subsequent_stream(first_data_tag) - except ( - AudioParametersChanged, VideoParametersChanged, - FileSizeOverLimit, DurationOverLimit, CutStream, - ): - self._process_split_stream(flv_header) - - logger.debug(f'Completed processing the {self._stream_count}th stream') - - def _process_initial_stream( - self, flv_header: FlvHeader, first_data_tag: FlvTag - ) -> None: - if self._resetting_file: - self._reset_file() - else: - self._new_file() - - try: - self._write_header(self._ensure_header_correct(flv_header)) - self._transfer_meta_tags() - self._transfer_first_data_tag(first_data_tag) - self._update_stream_profile(flv_header, first_data_tag) - except Exception: - self._last_tags = [] - self._resetting_file = True - raise - else: - del flv_header, first_data_tag - - self._transfer_tags_until_complete() - - def _process_subsequent_stream(self, first_data_tag: FlvTag) -> None: - tags: List[FlvTag] = [] - - if self._dedup_join: - tags, exc = self._read_tags_for_deduplication() - - if (index := self._find_last_duplicated_tag(tags)) >= 0: - seamless = True - self._delta = self._calc_delta_duplicated(tags[index]) - tags = tags[index + 1:] - if not tags: - tags = [self._read_first_data_tag()] - - if not self._dedup_join or index == -1: - seamless = False - self._delta = self._calc_delta_no_duplicated(first_data_tag) - tags.insert(0, first_data_tag) - - offset = self._out_file.tell() - timestamp = tags[0].timestamp + self._delta - self._add_join_point(offset, timestamp, seamless) - - self._transfer_tags(tags) - del first_data_tag, tags - - if self._dedup_join and exc: - raise exc - - self._transfer_tags_until_complete() - - def _process_split_stream(self, flv_header: FlvHeader) -> None: - self._complete_file() - - first_data_tag: FlvTag - - if self._stream_cutter.is_cutting(): - assert self._stream_cutter.last_keyframe_tag is not None - last_keyframe_tag = self._stream_cutter.last_keyframe_tag - original_ts = last_keyframe_tag.timestamp - self._delta - first_data_tag = last_keyframe_tag.evolve(timestamp=original_ts) - elif ( - not self._disable_limit and ( - self._limit_checker.is_filesize_over_limit() or - self._limit_checker.is_duration_over_limit() - ) - ): - assert self._limit_checker.last_keyframe_tag is not None - last_keyframe_tag = self._limit_checker.last_keyframe_tag - original_ts = last_keyframe_tag.timestamp - self._delta - first_data_tag = last_keyframe_tag.evolve(timestamp=original_ts) - else: - first_data_tag = self._read_first_data_tag() - - try: - self._process_initial_stream(flv_header, first_data_tag) - except ( - AudioParametersChanged, VideoParametersChanged, - FileSizeOverLimit, DurationOverLimit, CutStream, - ): - self._process_split_stream(flv_header) - - def _transfer_meta_tags(self) -> None: - logger.debug('Transfering meta tags...') - - if self._parameters_checker.last_metadata_tag is None: - raise FlvStreamCorruptedError('No metadata tag in the stream') - if self._parameters_checker.last_video_header_tag is None: - raise FlvStreamCorruptedError('No video header tag in the stream') - if self._has_audio: - if self._parameters_checker.last_audio_header_tag is None: - raise FlvStreamCorruptedError( - 'No audio header tag in the stream' - ) - metadata_tag = self._parameters_checker.last_metadata_tag - video_header_tag = self._parameters_checker.last_video_header_tag - audio_header_tag = self._parameters_checker.last_audio_header_tag - - offset = self._out_file.tell() - self._metadata_tag = self._enrich_metadata(metadata_tag, offset) - - offset_delta = self._metadata_tag.tag_size - metadata_tag.tag_size - self._update_injected_metadata(offset_delta) - - self._write_tag( - self._correct_ts(self._metadata_tag, -self._metadata_tag.timestamp) - ) - self._write_tag( - self._correct_ts(video_header_tag, -video_header_tag.timestamp) - ) - if audio_header_tag is not None: - self._write_tag( - self._correct_ts(audio_header_tag, -audio_header_tag.timestamp) - ) - - logger.debug('Meta tags have been transfered') - - def _update_stream_profile( - self, flv_header: FlvHeader, first_data_tag: FlvTag - ) -> None: - from ..core.stream_analyzer import ffprobe - - if self._parameters_checker.last_metadata_tag is None: - return - if self._parameters_checker.last_video_header_tag is None: - return - - bytes_io = io.BytesIO() - writer = FlvWriter(bytes_io) - writer.write_header(flv_header) - writer.write_tag( - self._correct_ts( - self._parameters_checker.last_metadata_tag, - -self._parameters_checker.last_metadata_tag.timestamp, - ) - ) - writer.write_tag( - self._correct_ts( - self._parameters_checker.last_video_header_tag, - -self._parameters_checker.last_video_header_tag.timestamp, - ) - ) - if self._parameters_checker.last_audio_header_tag is not None: - writer.write_tag( - self._correct_ts( - self._parameters_checker.last_audio_header_tag, - -self._parameters_checker.last_audio_header_tag.timestamp, - ) - ) - writer.write_tag(self._correct_ts(first_data_tag, -first_data_tag.timestamp)) - - def on_next(profile: StreamProfile) -> None: - self._stream_profile_updates.on_next(profile) - - def on_error(e: Exception) -> None: - logger.warning(f'Failed to analyse stream: {repr(e)}') - - ffprobe(bytes_io.getvalue()).subscribe(on_next, on_error) - - def _transfer_first_data_tag(self, tag: FlvTag) -> None: - logger.debug(f'Transfer the first data tag: {tag}') - self._delta = -tag.timestamp - self._transfer_tags([tag]) - logger.debug('The first data tag has been transfered') - - def _read_tags_for_deduplication( - self - ) -> Tuple[List[FlvTag], Optional[Exception]]: - logger.debug('Reading tags for tag deduplication...') - tags: List[FlvTag] = [] - try: - for tag in read_tags_in_duration( - self._in_reader, self._MAX_DURATION - ): - tags.append(tag) - except Exception as exc: - logger.debug(f'Failed to read data, due to: {repr(exc)}') - return tags, exc - else: - return tags, None - finally: - logger.debug('Read {} tags, total size: {}'.format( - len(tags), sum(t.tag_size for t in tags) - )) - - def _transfer_tags_until_complete(self) -> None: - logger.debug('Transfering tags until complete...') - self._transfer_tags(self._read_tags_from_in_stream()) - - def _read_tags_from_in_stream(self) -> Iterator[FlvTag]: - while not self._cancelled: - try: - tag = self._in_reader.read_tag() - self._parameters_checker.check_tag(tag) - yield tag - except EOFError: - logger.debug('The input stream exhausted') - break - except AudioParametersChanged: - if self._analyse_data: - logger.warning('Audio parameters changed at {}'.format( - format_timestamp(self._data_analyser.last_timestamp), - )) - yield tag - except VideoParametersChanged: - if self._analyse_data: - logger.warning('Video parameters changed at {}'.format( - format_timestamp(self._data_analyser.last_timestamp), - )) - raise - except Exception as e: - logger.debug(f'Failed to read data, due to: {repr(e)}') - raise - else: - logger.debug('Cancelled reading tags from the input stream') - - def _transfer_tags(self, tags: Iterable[FlvTag]) -> None: - logger.debug(f'Transfering tags... timestamp delta: {self._delta}') - - try: - count: int = 0 - for tag in tags: - self._ensure_ts_correct(tag) - self._write_tag(self._correct_ts(tag, self._delta)) - count += 1 - finally: - logger.debug(f'{count} tags have been transfered') - - def _add_join_point( - self, offset: int, timestamp: int, seamless: bool - ) -> None: - join_point = JoinPoint(offset, timestamp, seamless) - self._join_points.append(join_point) - logger.debug(f'{repr(join_point)}; {join_point}') - - def _find_last_duplicated_tag(self, tags: List[FlvTag]) -> int: - logger.debug('Finding duplicated tags...') - - last_out_tag = self._last_tags[0] - logger.debug(f'The last output tag is {last_out_tag}') - - for idx, tag in enumerate(tags): - if not tag.is_the_same_as(last_out_tag): - continue - - if not all( - map( - lambda t: t[0].is_the_same_as(t[1]), - zip(reversed(tags[:idx]), self._last_tags[1:]) - ) - ): - continue - - logger.debug(f'The last duplicated tag found at {idx} is {tag}') - return idx - - logger.debug('No duplicated tags found') - return -1 - - def _read_header(self) -> FlvHeader: - try: - return self._in_reader.read_header() - except Exception as exc: - raise FlvStreamCorruptedError(repr(exc)) - - def _read_first_data_tag(self) -> Union[AudioTag, VideoTag]: - for tag in self._read_tags_from_in_stream(): - if is_data_tag(tag): - return tag - raise FlvStreamCorruptedError('No data tag found in the stream!') - - def _read_tags(self, count: int) -> Iterator[FlvTag]: - assert count > 0, 'count must greater than 0' - for c, tag in enumerate(self._read_tags_from_in_stream(), start=1): - yield tag - if c >= count: - break - - def _write_header(self, header: FlvHeader) -> None: - try: - size = self._out_writer.write_header(header) - logger.debug('The flv header has been copied') - except Exception as exc: - logger.debug(f'Failed to write data, due to: {repr(exc)}') - raise - - if not self._disable_limit: - self._limit_checker.check_header(header) - if self._analyse_data: - self._data_analyser.analyse_header(header) - self._size_updates.on_next(size) - - def _write_tag(self, tag: FlvTag) -> None: - offset = self._out_file.tell() - tag = tag.evolve(offset=offset) - - try: - size = self._out_writer.write_tag(tag) - except Exception as exc: - logger.debug(f'Failed to write data, due to: {repr(exc)}') - raise - else: - self._last_tags.insert(0, tag) - if len(self._last_tags) > self._TAG_SEQUENCE_COUNT: - self._last_tags.pop() - - self._stream_cutter.check_tag(tag) - if not self._disable_limit: - self._limit_checker.check_tag(tag) - if self._analyse_data: - self._data_analyser.analyse_tag(tag) - - self._size_updates.on_next(size) - self._time_updates.on_next(tag.timestamp) - - def _ensure_header_correct(self, header: FlvHeader) -> FlvHeader: - header.set_video_flag( - self._parameters_checker.last_video_header_tag is not None - ) - header.set_audio_flag( - self._parameters_checker.last_audio_header_tag is not None - ) - return header - - def _ensure_ts_correct(self, tag: FlvTag) -> None: - if not tag.timestamp + self._delta < 0: - return - logger.warning( - f'Incorrect timestamp: {tag.timestamp + self._delta}\n' - f'last output tag: {self._last_tags[0]}\n' - f'current tag: {tag}' - ) - if tag.is_audio_tag() or tag.is_video_tag(): - self._delta = ( - self._last_tags[0].timestamp + - self._in_reader.calc_interval(tag) - tag.timestamp - ) - logger.debug(f'Updated delta: {self._delta}') - elif tag.is_script_tag(): - self._delta = ( - self._last_tags[0].timestamp - tag.timestamp - ) - logger.debug(f'Updated delta: {self._delta}') - else: - pass - - def _correct_ts(self, tag: FlvTag, delta: int) -> FlvTag: - if delta == 0 and tag.timestamp >= 0: - return tag - return tag.evolve(timestamp=tag.timestamp + delta) - - def _calc_delta_duplicated(self, last_duplicated_tag: FlvTag) -> int: - return self._last_tags[0].timestamp - last_duplicated_tag.timestamp - - def _calc_delta_no_duplicated(self, first_data_tag: FlvTag) -> int: - return ( - self._last_tags[0].timestamp - first_data_tag.timestamp + - self._in_reader.calc_interval(first_data_tag) - ) - - def _enrich_metadata( - self, old_metadata_tag: ScriptTag, offset: int - ) -> ScriptTag: - # ensure nesessary properties exists in the metadata and init them. - metadata = parse_metadata(old_metadata_tag) - self._metadata.update({ - 'duration': 0.0, - 'filesize': 0.0, - 'framerate': metadata.get('framerate', metadata.get('fps', 0.0)), - }) - # merge the metadata into the metadata tag - return enrich_metadata(old_metadata_tag, self._metadata, offset) - - def _update_injected_metadata(self, offset_delta: int) -> None: - updated = False - - if (keyframes := self._metadata.get('keyframes')): - keyframes['filepositions'] = list( - map(lambda p: p + offset_delta, keyframes['filepositions']) - ) - if 'lastkeyframelocation' in self._metadata: - self._metadata['lastkeyframelocation'] = \ - keyframes['filepositions'][-1] - updated = True - - if (join_points := self._metadata.get('joinpoints')): - join_points = cast(List[JoinPointData], join_points) - self._metadata['joinpoints'] = list( - {**p, 'offset': p['offset'] + offset_delta} - for p in join_points - ) - updated = True - - if updated: - self._metadata_tag = \ - update_metadata(self._metadata_tag, self._metadata) - - def _update_metadata_tag(self) -> None: - last_tag = self._last_tags[0] - duration = last_tag.timestamp / 1000 - filesize = float(last_tag.next_tag_offset) - updates = { - 'duration': duration, - 'filesize': filesize, - } - if self._analyse_data: - updates.update({ - 'framerate': self._data_analyser.calc_frame_rate(), - }) - self._metadata_tag = update_metadata(self._metadata_tag, updates) - self._out_file.seek(self._metadata_tag.offset) - self._out_writer.write_tag(self._metadata_tag) - logger.debug('The metadata tag has been updated') - - def _save_extra_metadata(self) -> None: - if self._analyse_data: - metadata = attr.asdict( - self._data_analyser.make_metadata(), - filter=lambda a, v: v is not None, - ) - else: - metadata = {} - - metadata['joinpoints'] = list( - map(lambda p: p.to_metadata_value(), self._join_points) - ) - - assert self._file_manager.curr_path is not None - path = extra_metadata_path(self._file_manager.curr_path) - with open(path, 'wt', encoding='utf8') as file: - json.dump(metadata, file) - - logger.debug('The extra metadata has been saved') - - -@attr.s(auto_attribs=True, slots=True, frozen=True) -class JoinPoint: - offset: int - timestamp: int # milliseconds - seamless: bool - - @classmethod - def from_metadata_value(cls, value: JoinPointData) -> JoinPoint: - return cls( - offset=int(value['offset']), - timestamp=int(value['timestamp']), - seamless=value['seamless'], - ) - - def to_metadata_value(self) -> JoinPointData: - return dict( - offset=float(self.offset), - timestamp=float(self.timestamp), - seamless=self.seamless, - ) - - def __str__(self) -> str: - return 'offset: {}, timestamp: {}, seamless: {}'.format( - format_offest(self.offset), - format_timestamp(self.timestamp), - 'yes' if self.seamless else 'no', - ) - - -class JoinPointData(TypedDict): - offset: float - timestamp: float - seamless: bool - - -class OutputFileManager(Protocol): - @property - def curr_path(self) -> Optional[str]: - ... - - @property - def curr_file(self) -> Optional[BinaryIO]: - ... - - def create_file(self) -> BinaryIO: - ... - - def close_file(self) -> None: - ... - - -class BaseOutputFileManager(ABC): - def __init__(self, buffer_size: Optional[int] = None) -> None: - super().__init__() - self.buffer_size = buffer_size or io.DEFAULT_BUFFER_SIZE # bytes - self._paths: List[str] = [] - self._curr_path: Optional[str] = None - self._curr_file: Optional[BinaryIO] = None - - @property - def curr_path(self) -> Optional[str]: - return self._curr_path - - @property - def curr_file(self) -> Optional[BinaryIO]: - return self._curr_file - - def has_file(self) -> bool: - return len(self._paths) > 1 - - def get_files(self) -> Iterator[str]: - for file in self._paths: - yield file - - def clear_files(self) -> None: - self._paths.clear() - - def create_file(self) -> BinaryIO: - assert self._curr_file is None - path = self._make_path() - file = open(path, mode='w+b', buffering=self.buffer_size) - self._paths.append(path) - self._curr_path = path - self._curr_file = file - return file - - def close_file(self) -> None: - assert self._curr_file is not None - self._curr_file.close() - self._curr_path = None - self._curr_file = None - - @abstractmethod - def _make_path(self) -> str: - ... - - -class RobustFlvReader(FlvReader): - def read_tag(self, *, no_body: bool = False) -> FlvTag: - count = 0 - while True: - try: - tag = super().read_tag(no_body=no_body) - except FlvTagError as e: - logger.warning(f'Invalid tag: {repr(e)}') - self._parser.parse_previous_tag_size() - count += 1 - if count > 3: - raise - else: - count = 0 - return tag - - -class FlvReaderWithTimestampFix(RobustFlvReader): - def __init__( - self, - stream: RandomIO, - backup_timestamp: bool = False, - restore_timestamp: bool = False, - ) -> None: - super().__init__( - stream, - backup_timestamp=backup_timestamp, - restore_timestamp=restore_timestamp, - ) - self._last_tag: Optional[FlvTag] = None - self._last_video_tag: Optional[VideoTag] = None - self._last_audio_tag: Optional[AudioTag] = None - self._delta = 0 - # 15 is probably the minimal frame rate - self._frame_rate = 15.0 - self._video_frame_interval = math.ceil(1000 / self._frame_rate) - # AAC SoundRate always is 44 KHz - self._sound_sample_interval = math.ceil(1000 / 44) - - @property - def frame_rate(self) -> float: - return self._frame_rate - - @property - def sound_sample_interval(self) -> int: - return self._sound_sample_interval - - @property - def video_frame_interval(self) -> int: - return self._video_frame_interval - - def read_tag(self, *, no_body: bool = False) -> FlvTag: - while True: - tag = super().read_tag(no_body=no_body) - - if self._last_tag is None: - if is_video_tag(tag) or is_audio_tag(tag): - self._update_last_tags(tag) - elif is_metadata_tag(tag): - self._update_parameters(tag) - return tag - - if not is_video_tag(tag) and not is_audio_tag(tag): - return tag - - if self._is_ts_rebounded(tag): - self._update_delta(tag) - logger.warning( - f'Timestamp rebounded, updated delta: {self._delta}\n' - f'last video tag: {self._last_video_tag}\n' - f'last audio tag: {self._last_audio_tag}\n' - f'current tag: {tag}' - ) - - if self._is_ts_jumped(tag): - self._update_delta(tag) - logger.warning( - f'Timestamp jumped, updated delta: {self._delta}\n' - f'last tag: {self._last_tag}\n' - f'current tag: {tag}' - ) - - self._update_last_tags(tag) - - return self._correct_ts(tag) - - def rread_tag(self, *, no_body: bool = False) -> FlvTag: - raise NotImplementedError() - - def read_body(self, tag: FlvTag) -> bytes: - raise NotImplementedError() - - def calc_interval(self, tag: FlvTag) -> int: - if is_audio_tag(tag): - return self._sound_sample_interval - elif is_video_tag(tag): - return self._video_frame_interval - else: - logger.warning(f'Unexpected tag type: {tag}') - return min(self._sound_sample_interval, self._video_frame_interval) - - def _is_ts_rebounded(self, tag: FlvTag) -> bool: - if is_video_tag(tag): - if self._last_video_tag is None: - return False - if is_video_sequence_header(self._last_video_tag): - return tag.timestamp < self._last_video_tag.timestamp - else: - return tag.timestamp <= self._last_video_tag.timestamp - elif is_audio_tag(tag): - if self._last_audio_tag is None: - return False - if is_audio_sequence_header(self._last_audio_tag): - return tag.timestamp < self._last_audio_tag.timestamp - else: - return tag.timestamp <= self._last_audio_tag.timestamp - else: - return False - - def _is_ts_jumped(self, tag: FlvTag) -> bool: - assert self._last_tag is not None - return ( - tag.timestamp - self._last_tag.timestamp > - max(self._sound_sample_interval, self._video_frame_interval) - ) - - def _update_last_tags(self, tag: FlvTag) -> None: - self._last_tag = tag - if is_video_tag(tag): - self._last_video_tag = tag - elif is_audio_tag(tag): - self._last_audio_tag = tag - - def _update_parameters(self, tag: ScriptTag) -> None: - metadata = parse_metadata(tag) - frame_rate = metadata.get('fps') or metadata.get('framerate') - - if not frame_rate: - return - - self._frame_rate = frame_rate - self._video_frame_interval = math.ceil(1000 / frame_rate) - - logger.debug('frame rate: {}, video frame interval: {}'.format( - frame_rate, self._video_frame_interval - )) - - def _update_delta(self, tag: FlvTag) -> None: - assert self._last_tag is not None - self._delta = ( - self._last_tag.timestamp + self._delta - tag.timestamp + - self.calc_interval(tag) - ) - - def _correct_ts(self, tag: FlvTag) -> FlvTag: - if self._delta == 0: - return tag - return tag.evolve(timestamp=tag.timestamp + self._delta) diff --git a/src/blrec/postprocess/ffmpeg_metadata.py b/src/blrec/postprocess/ffmpeg_metadata.py index 8b801f1..d3d08d0 100644 --- a/src/blrec/postprocess/ffmpeg_metadata.py +++ b/src/blrec/postprocess/ffmpeg_metadata.py @@ -1,13 +1,12 @@ -import logging import json +import logging from typing import Iterable, cast import aiofiles -from .helpers import get_metadata, get_extra_metadata -from ..flv.stream_processor import JoinPoint from ..flv.helpers import make_comment_for_joinpoints - +from ..flv.operators import JoinPoint +from .helpers import get_extra_metadata, get_metadata logger = logging.getLogger(__name__) @@ -27,7 +26,7 @@ async def _make_metadata_content(flv_path: str) -> str: comment = cast(str, metadata.get('Comment', '')) chapters = '' - if (join_points := extra_metadata.get('joinpoints')): + if join_points := extra_metadata.get('joinpoints'): join_points = list(map(JoinPoint.from_metadata_value, join_points)) comment += '\n\n' + make_comment_for_joinpoints(join_points) duration = int(cast(float, metadata['duration']) * 1000) @@ -49,9 +48,7 @@ Comment={comment} """ -def _make_chapters( - join_points: Iterable[JoinPoint], duration: int -) -> str: +def _make_chapters(join_points: Iterable[JoinPoint], duration: int) -> str: join_points = filter(lambda p: not p.seamless, join_points) timestamps = list(map(lambda p: p.timestamp, join_points)) if not timestamps: diff --git a/src/blrec/postprocess/postprocessor.py b/src/blrec/postprocess/postprocessor.py index 7de84ce..8544251 100644 --- a/src/blrec/postprocess/postprocessor.py +++ b/src/blrec/postprocess/postprocessor.py @@ -1,28 +1,27 @@ from __future__ import annotations + import asyncio import logging -from pathlib import PurePath from contextlib import suppress +from pathlib import PurePath from typing import Any, Awaitable, Dict, Iterator, List, Optional, Union -from rx.core.typing import Scheduler -from rx.scheduler.threadpoolscheduler import ThreadPoolScheduler +from reactivex.scheduler import ThreadPoolScheduler -from .models import PostprocessorStatus, DeleteStrategy -from .typing import Progress -from .remuxer import remux_video, RemuxProgress, RemuxResult -from .helpers import discard_file, get_extra_metadata -from .ffmpeg_metadata import make_metadata_file -from ..event.event_emitter import EventListener, EventEmitter from ..bili.live import Live from ..core import Recorder, RecorderEventListener -from ..exception import submit_exception -from ..utils.mixins import AsyncStoppableMixin, AsyncCooperationMixin -from ..path import extra_metadata_path -from ..flv.metadata_injector import inject_metadata, InjectProgress +from ..event.event_emitter import EventEmitter, EventListener +from ..exception import exception_callback, submit_exception from ..flv.helpers import is_valid_flv_file +from ..flv.metadata_injection import InjectingProgress, inject_metadata from ..logging.room_id import aio_task_with_room_id - +from ..path import extra_metadata_path +from ..utils.mixins import AsyncCooperationMixin, AsyncStoppableMixin, SupportDebugMixin +from .ffmpeg_metadata import make_metadata_file +from .helpers import discard_file, get_extra_metadata +from .models import DeleteStrategy, PostprocessorStatus +from .remux import RemuxingProgress, RemuxingResult, remux_video +from .typing import Progress __all__ = ( 'Postprocessor', @@ -47,6 +46,7 @@ class Postprocessor( RecorderEventListener, AsyncStoppableMixin, AsyncCooperationMixin, + SupportDebugMixin, ): def __init__( self, @@ -58,6 +58,7 @@ class Postprocessor( delete_source: DeleteStrategy = DeleteStrategy.AUTO, ) -> None: super().__init__() + self._init_for_debug(live.room_id) self._live = live self._recorder = recorder @@ -94,14 +95,10 @@ class Postprocessor( # clear completed files of previous recording self._completed_files.clear() - async def on_video_file_completed( - self, recorder: Recorder, path: str - ) -> None: + async def on_video_file_completed(self, recorder: Recorder, path: str) -> None: self._queue.put_nowait(path) - async def on_danmaku_file_completed( - self, recorder: Recorder, path: str - ) -> None: + async def on_danmaku_file_completed(self, recorder: Recorder, path: str) -> None: self._completed_files.append(path) async def _do_start(self) -> None: @@ -110,6 +107,7 @@ class Postprocessor( self._queue: asyncio.Queue[str] = asyncio.Queue() self._scheduler = ThreadPoolScheduler() self._task = asyncio.create_task(self._worker()) + self._task.add_done_callback(exception_callback) logger.debug('Started postprocessor') @@ -150,12 +148,11 @@ class Postprocessor( else: result_path = video_path - await discard_file(extra_metadata_path(video_path), 'DEBUG') + if not self._debug: + await discard_file(extra_metadata_path(video_path), 'DEBUG') self._completed_files.append(result_path) - await self._emit( - 'video_postprocessing_completed', self, result_path, - ) + await self._emit('video_postprocessing_completed', self, result_path) except Exception as exc: submit_exception(exc) finally: @@ -164,9 +161,13 @@ class Postprocessor( async def _inject_extra_metadata(self, path: str) -> str: try: metadata = await get_extra_metadata(path) - await self._inject_metadata(path, metadata, self._scheduler) + logger.info(f"Injecting metadata for '{path}' ...") + await self._inject_metadata(path, metadata) except Exception as e: logger.error(f"Failed to inject metadata for '{path}': {repr(e)}") + submit_exception(e) + else: + logger.info(f"Successfully injected metadata for '{path}'") return path async def _remux_flv_to_mp4(self, in_path: str) -> str: @@ -174,12 +175,10 @@ class Postprocessor( logger.info(f"Remuxing '{in_path}' to '{out_path}' ...") metadata_path = await make_metadata_file(in_path) - remux_result = await self._remux_video( - in_path, out_path, metadata_path, self._scheduler - ) + remux_result = await self._remux_video(in_path, out_path, metadata_path) if remux_result.is_successful(): - logger.info(f"Successfully remux '{in_path}' to '{out_path}'") + logger.info(f"Successfully remuxed '{in_path}' to '{out_path}'") result_path = out_path elif remux_result.is_warned(): logger.warning('Remuxing done, but ran into problems.') @@ -192,65 +191,54 @@ class Postprocessor( logger.debug(f'ffmpeg output:\n{remux_result.output}') - await discard_file(metadata_path, 'DEBUG') + if not self._debug: + await discard_file(metadata_path, 'DEBUG') if self._should_delete_source_files(remux_result): await discard_file(in_path) return result_path - def _inject_metadata( - self, - path: str, - metadata: Dict[str, Any], - scheduler: Scheduler, - ) -> Awaitable[None]: + def _inject_metadata(self, path: str, metadata: Dict[str, Any]) -> Awaitable[None]: future: asyncio.Future[None] = asyncio.Future() self._postprocessing_path = path - def on_next(value: InjectProgress) -> None: + def on_next(value: InjectingProgress) -> None: self._postprocessing_progress = value - inject_metadata( - path, - metadata, - report_progress=True, - room_id=self._live.room_id, - ).subscribe( - on_next, - lambda e: future.set_exception(e), - lambda: future.set_result(None), - scheduler=scheduler, + subscription = inject_metadata(path, metadata, show_progress=True).subscribe( + on_next=on_next, + on_error=lambda e: future.set_exception(e), + on_completed=lambda: future.set_result(None), + scheduler=self._scheduler, ) + future.add_done_callback(lambda f: subscription.dispose()) return future def _remux_video( - self, - in_path: str, - out_path: str, - metadata_path: str, - scheduler: Scheduler, - ) -> Awaitable[RemuxResult]: - future: asyncio.Future[RemuxResult] = asyncio.Future() + self, in_path: str, out_path: str, metadata_path: str + ) -> Awaitable[RemuxingResult]: + future: asyncio.Future[RemuxingResult] = asyncio.Future() self._postprocessing_path = in_path - def on_next(value: Union[RemuxProgress, RemuxResult]) -> None: - if isinstance(value, RemuxProgress): + def on_next(value: Union[RemuxingProgress, RemuxingResult]) -> None: + if isinstance(value, RemuxingProgress): self._postprocessing_progress = value - elif isinstance(value, RemuxResult): + elif isinstance(value, RemuxingResult): future.set_result(value) - remux_video( + subscription = remux_video( in_path, out_path, metadata_path, - report_progress=True, + show_progress=True, remove_filler_data=True, ).subscribe( - on_next, - lambda e: future.set_exception(e), - scheduler=scheduler, + on_next=on_next, + on_error=lambda e: future.set_exception(e), + scheduler=self._scheduler, ) + future.add_done_callback(lambda f: subscription.dispose()) return future @@ -258,9 +246,7 @@ class Postprocessor( loop = asyncio.get_running_loop() return await loop.run_in_executor(None, is_valid_flv_file, video_path) - def _should_delete_source_files( - self, remux_result: RemuxResult - ) -> bool: + def _should_delete_source_files(self, remux_result: RemuxingResult) -> bool: if self.delete_source == DeleteStrategy.AUTO: if not remux_result.is_failed(): return True diff --git a/src/blrec/postprocess/remux.py b/src/blrec/postprocess/remux.py new file mode 100644 index 0000000..5054232 --- /dev/null +++ b/src/blrec/postprocess/remux.py @@ -0,0 +1,158 @@ +import os +import re +import shlex +from subprocess import PIPE, Popen +from typing import Any, Final, List, Optional, Union + +import attr +from reactivex import Observable, abc, create +from reactivex.disposable import CompositeDisposable, Disposable, SerialDisposable +from reactivex.scheduler.currentthreadscheduler import CurrentThreadScheduler +from tqdm import tqdm + +__all__ = 'RemuxingResult', 'remux_video' + + +@attr.s(auto_attribs=True, slots=True, frozen=True) +class RemuxingProgress: + count: int + total: int + + +@attr.s(auto_attribs=True, slots=True, frozen=True) +class RemuxingResult: + return_code: int + output: str + + def is_done(self) -> bool: + return self.return_code == 0 + + def is_successful(self) -> bool: + return self.is_done() and not self.may_timestamps_incorrect() + + def is_warned(self) -> bool: + return self.is_done() and self.may_timestamps_incorrect() + + def is_failed(self) -> bool: + return not self.is_done() + + def may_timestamps_incorrect(self) -> bool: + return 'Non-monotonous DTS in output stream' in self.output + + +def remux_video( + in_path: str, + out_path: str, + metadata_path: Optional[str] = None, + *, + show_progress: bool = False, + remove_filler_data: bool = False, +) -> Observable[Union[RemuxingProgress, RemuxingResult]]: + SIZE_PATTERN: Final = re.compile(r'size=\s*(?P\d+)(?P[a-zA-Z]?B)') + filesize = os.path.getsize(in_path) + filename = os.path.basename(in_path) + + def parse_size(line: str) -> int: + match = SIZE_PATTERN.search(line) + assert match is not None + result = match.groupdict() + + unit = result['unit'] + number = int(result['number']) + + if unit == 'B': + size = number + elif unit == 'kB': + size = 1024 * number + elif unit == 'MB': + size = 1024**2 * number + elif unit == 'GB': + size = 1024**3 * number + else: + raise ValueError(unit) + + return size + + def should_output_line(line: str) -> bool: + line = line.strip() + return not (line.startswith('frame=') or line.startswith('Press [q]')) + + def subscribe( + observer: abc.ObserverBase[Union[RemuxingProgress, RemuxingResult]], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + _scheduler = scheduler or CurrentThreadScheduler.singleton() + + disposed = False + cancelable = SerialDisposable() + + def action(scheduler: abc.SchedulerBase, state: Optional[Any] = None) -> None: + if disposed: + return + + with tqdm( + desc='Remuxing', + total=filesize, + unit='B', + unit_scale=True, + unit_divisor=1024, + postfix=filename, + disable=not show_progress, + ) as pbar: + cmd = f'ffmpeg -i "{in_path}"' + if metadata_path is not None: + cmd += f' -i "{metadata_path}" -map_metadata 1' + cmd += ' -codec copy' + if remove_filler_data: + # https://forum.doom9.org/showthread.php?t=152051 + # ISO_IEC_14496-10_2020(E) + # Table 7-1 – NAL unit type codes, syntax element categories, and NAL unit type classes # noqa + # 7.4.2.7 Filler data RBSP semantics + cmd += ' -bsf:v filter_units=remove_types=12' + cmd += f' "{out_path}" -y' + + args = shlex.split(cmd) + out_lines: List[str] = [] + + try: + with Popen( + args, stderr=PIPE, encoding='utf8', errors='backslashreplace' + ) as process: + assert process.stderr is not None + while not disposed: + line = process.stderr.readline() + if not line: + if process.poll() is not None: + break + else: + continue + + if line.startswith('frame='): + size = parse_size(line) + pbar.update(size - pbar.n) + progress = RemuxingProgress(size, filesize) + observer.on_next(progress) + + if should_output_line(line): + out_lines.append(line) + + if not disposed and process.returncode == 0: + pbar.update(filesize) + progress = RemuxingProgress(filesize, filesize) + observer.on_next(progress) + except Exception as e: + observer.on_error(e) + else: + result = RemuxingResult(process.returncode, ''.join(out_lines)) + observer.on_next(result) + observer.on_completed() + + cancelable.disposable = _scheduler.schedule(action) + + def dispose() -> None: + nonlocal disposed + disposed = True + + return CompositeDisposable(cancelable, Disposable(dispose)) + + return create(subscribe) diff --git a/src/blrec/postprocess/remuxer.py b/src/blrec/postprocess/remuxer.py deleted file mode 100644 index 28aa184..0000000 --- a/src/blrec/postprocess/remuxer.py +++ /dev/null @@ -1,193 +0,0 @@ -import os -import re -import shlex -from subprocess import Popen, PIPE -from typing import Final, List, Match, Optional, Union - -import attr -from rx import create, operators as op -from rx.subject import Subject -from rx.core import Observable -from rx.core.typing import Observer, Scheduler, Disposable -from rx.scheduler.currentthreadscheduler import CurrentThreadScheduler -from tqdm import tqdm - - -__all__ = 'VideoRemuxer', 'RemuxResult', 'remux_video' - - -@attr.s(auto_attribs=True, slots=True, frozen=True) -class RemuxProgress: - time: int - duration: int - - -@attr.s(auto_attribs=True, slots=True, frozen=True) -class RemuxResult: - return_code: int - output: str - - def is_done(self) -> bool: - return self.return_code == 0 - - def is_successful(self) -> bool: - return self.is_done() and not self.may_timestamps_incorrect() - - def is_warned(self) -> bool: - return self.is_done() and self.may_timestamps_incorrect() - - def is_failed(self) -> bool: - return not self.is_done() - - def may_timestamps_incorrect(self) -> bool: - return 'Non-monotonous DTS in output stream' in self.output - - -class VideoRemuxer: - _TIME_PATTERN: Final = re.compile( - r'(?P\d+):(?P\d+):(?P\d+).(?P\d+)' - ) - - def __init__(self) -> None: - self._duration: int = 0 - self._progress_updates = Subject() - - @property - def progress_updates(self) -> Observable: - return self._progress_updates - - def remux( - self, - in_path: str, - out_path: str, - metadata_path: Optional[str] = None, - *, - remove_filler_data: bool = False, - ) -> RemuxResult: - cmd = f'ffmpeg -i "{in_path}"' - if metadata_path is not None: - cmd += f' -i "{metadata_path}" -map_metadata 1' - cmd += ' -codec copy' - if remove_filler_data: - # https://forum.doom9.org/showthread.php?t=152051 - # ISO_IEC_14496-10_2020(E) - # Table 7-1 – NAL unit type codes, syntax element categories, and NAL unit type classes # noqa - # 7.4.2.7 Filler data RBSP semantics - cmd += ' -bsf:v filter_units=remove_types=12' - cmd += f' "{out_path}" -y' - - args = shlex.split(cmd) - out_lines: List[str] = [] - - with Popen( - args, stderr=PIPE, encoding='utf8', errors='backslashreplace' - ) as process: - assert process.stderr is not None - while True: - line = process.stderr.readline() - if not line: - if process.poll() is not None: - break - else: - continue - self._parse_line(line) - if self._should_output_line(line): - out_lines.append(line) - - if process.returncode == 0: - self._complete_progress_updates() - - return RemuxResult(process.returncode, ''.join(out_lines)) - - def _parse_line(self, line: str) -> None: - line = line.strip() - if line.startswith('frame='): - self._parse_time(line) - elif line.startswith('Duration:'): - self._parse_duration(line) - else: - pass - - def _parse_duration(self, line: str) -> None: - match = self._TIME_PATTERN.search(line) - if match is not None: - self._duration = self._calc_time(match) - self._progress_updates.on_next(RemuxProgress(0, self._duration)) - - def _parse_time(self, line: str) -> None: - match = self._TIME_PATTERN.search(line) - if match is not None: - progress = RemuxProgress(self._calc_time(match), self._duration) - self._progress_updates.on_next(progress) - - def _complete_progress_updates(self) -> None: - progress = RemuxProgress(self._duration, self._duration) - self._progress_updates.on_next(progress) - self._progress_updates.on_completed() - - def _calc_time(self, match: Match[str]) -> int: - result = match.groupdict() - return ( - int(result['hour']) * 60 * 60 * 1000 + - int(result['minute']) * 60 * 1000 + - int(result['second']) * 1000 + - int(result['millisecond']) - ) - - def _should_output_line(self, line: str) -> bool: - line = line.strip() - return not ( - line.startswith('frame=') or - line.startswith('Press [q]') - ) - - -def remux_video( - in_path: str, - out_path: str, - metadata_path: Optional[str] = None, - *, - report_progress: bool = False, - remove_filler_data: bool = False, -) -> Observable: - def subscribe( - observer: Observer[Union[RemuxProgress, RemuxResult]], - scheduler: Optional[Scheduler] = None, - ) -> Disposable: - _scheduler = scheduler or CurrentThreadScheduler.singleton() - - def action(scheduler, state): # type: ignore - remuxer = VideoRemuxer() - file_name = os.path.basename(in_path) - - with tqdm(desc='Remuxing', unit='ms', postfix=file_name) as pbar: - def reset(progress: RemuxProgress) -> None: - pbar.reset(progress.duration) - - def update(progress: RemuxProgress) -> None: - pbar.update(progress.time - pbar.n) - - remuxer.progress_updates.pipe(op.first()).subscribe(reset) - remuxer.progress_updates.pipe(op.skip(1)).subscribe(update) - - if report_progress: - remuxer.progress_updates.subscribe( - lambda p: observer.on_next(p) - ) - - try: - result = remuxer.remux( - in_path, - out_path, - metadata_path, - remove_filler_data=remove_filler_data, - ) - except Exception as e: - observer.on_error(e) - else: - observer.on_next(result) - observer.on_completed() - - return _scheduler.schedule(action) - - return create(subscribe) diff --git a/src/blrec/postprocess/typing.py b/src/blrec/postprocess/typing.py index 5bdba55..8248599 100644 --- a/src/blrec/postprocess/typing.py +++ b/src/blrec/postprocess/typing.py @@ -1,9 +1,6 @@ - from typing import Union +from ..flv.metadata_injection import InjectingProgress +from .remux import RemuxingProgress -from .remuxer import RemuxProgress -from ..flv.metadata_injector import InjectProgress - - -Progress = Union[RemuxProgress, InjectProgress] +Progress = Union[RemuxingProgress, InjectingProgress] diff --git a/src/blrec/task/task.py b/src/blrec/task/task.py index 7d0de84..51dd891 100644 --- a/src/blrec/task/task.py +++ b/src/blrec/task/task.py @@ -18,12 +18,11 @@ from ..bili.danmaku_client import DanmakuClient from ..bili.live_monitor import LiveMonitor from ..bili.typing import StreamFormat, QualityNumber from ..core import Recorder -from ..core.stream_analyzer import StreamProfile +from ..flv.operators import MetaData, StreamProfile from ..core.cover_downloader import CoverSaveStrategy from ..postprocess import Postprocessor, PostprocessorStatus, DeleteStrategy -from ..postprocess.remuxer import RemuxProgress -from ..flv.metadata_injector import InjectProgress -from ..flv.data_analyser import MetaData +from ..postprocess.remux import RemuxingProgress +from ..flv.metadata_injection import InjectingProgress from ..event.event_submitters import ( LiveEventSubmitter, RecorderEventSubmitter, PostprocessorEventSubmitter ) @@ -151,10 +150,15 @@ class RecordTask: status = VideoFileStatus.RECORDING elif path == self._postprocessor.postprocessing_path: progress = self._postprocessor.postprocessing_progress - if isinstance(progress, RemuxProgress): + if isinstance(progress, RemuxingProgress): status = VideoFileStatus.REMUXING - elif isinstance(progress, InjectProgress): + elif isinstance(progress, InjectingProgress): status = VideoFileStatus.INJECTING + else: + if self._postprocessor.remux_to_mp4: + status = VideoFileStatus.REMUXING + else: + status = VideoFileStatus.INJECTING else: # disabling recorder by force or stoping task by force status = VideoFileStatus.BROKEN diff --git a/src/blrec/task/task_manager.py b/src/blrec/task/task_manager.py index 1218a32..120cdcc 100644 --- a/src/blrec/task/task_manager.py +++ b/src/blrec/task/task_manager.py @@ -13,8 +13,7 @@ from tenacity import ( from .task import RecordTask from .models import TaskData, TaskParam, VideoFileDetail, DanmakuFileDetail -from ..flv.data_analyser import MetaData -from ..core.stream_analyzer import StreamProfile +from ..flv.operators import MetaData, StreamProfile from ..exception import submit_exception, NotFoundError from ..bili.exceptions import ApiRequestError if TYPE_CHECKING: diff --git a/src/blrec/core/stream_analyzer.py b/src/blrec/utils/ffprobe.py similarity index 53% rename from src/blrec/core/stream_analyzer.py rename to src/blrec/utils/ffprobe.py index 8f72a14..3297e74 100644 --- a/src/blrec/core/stream_analyzer.py +++ b/src/blrec/utils/ffprobe.py @@ -1,31 +1,25 @@ +from __future__ import annotations + import json -import logging -from subprocess import Popen, PIPE -from typing import Dict, Any, Optional +from subprocess import PIPE, Popen +from typing import Any, Dict, Optional -from rx import create -from rx.core import Observable -from rx.core.typing import Observer, Scheduler, Disposable -from rx.scheduler.newthreadscheduler import NewThreadScheduler - - -logger = logging.getLogger(__name__) - - -__all__ = 'ffprobe', 'StreamProfile' +from reactivex import Observable, abc +from reactivex.scheduler import NewThreadScheduler +__all__ = ('ffprobe', 'StreamProfile') StreamProfile = Dict[str, Any] -def ffprobe(data: bytes) -> Observable: +def ffprobe(data: bytes) -> Observable[StreamProfile]: def subscribe( - observer: Observer[StreamProfile], - scheduler: Optional[Scheduler] = None, - ) -> Disposable: + observer: abc.ObserverBase[StreamProfile], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: _scheduler = scheduler or NewThreadScheduler() - def action(scheduler, state): # type: ignore + def action(scheduler: abc.SchedulerBase, state: Optional[Any] = None) -> None: args = [ 'ffprobe', '-show_streams', @@ -35,9 +29,7 @@ def ffprobe(data: bytes) -> Observable: 'pipe:0', ] - with Popen( - args, stdin=PIPE, stdout=PIPE, stderr=PIPE - ) as process: + with Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) as process: try: stdout, stderr = process.communicate(data, timeout=10) except Exception as e: @@ -51,4 +43,4 @@ def ffprobe(data: bytes) -> Observable: return _scheduler.schedule(action) - return create(subscribe) + return Observable(subscribe) diff --git a/src/blrec/utils/mixins.py b/src/blrec/utils/mixins.py index ce9972a..f507a09 100644 --- a/src/blrec/utils/mixins.py +++ b/src/blrec/utils/mixins.py @@ -120,7 +120,7 @@ class AsyncCooperationMixin(ABC): super().__init__() self._loop = asyncio.get_running_loop() - def _handle_exception(self, exc: BaseException) -> None: + def _submit_exception(self, exc: BaseException) -> None: from ..exception import submit_exception async def wrapper() -> None: diff --git a/src/blrec/utils/operators/__init__.py b/src/blrec/utils/operators/__init__.py new file mode 100644 index 0000000..1c9d7f8 --- /dev/null +++ b/src/blrec/utils/operators/__init__.py @@ -0,0 +1,4 @@ +from .replace import replace +from .retry import retry + +__all__ = ('replace', 'retry') diff --git a/src/blrec/utils/operators/replace.py b/src/blrec/utils/operators/replace.py new file mode 100644 index 0000000..dc5575a --- /dev/null +++ b/src/blrec/utils/operators/replace.py @@ -0,0 +1,29 @@ +import os +from typing import Callable, Optional, TypeVar + +from reactivex import Observable, abc + +_T = TypeVar('_T') + + +def replace(src_path: str, dst_path: str) -> Callable[[Observable[_T]], Observable[_T]]: + def _replace(source: Observable[_T]) -> Observable[_T]: + def subscribe( + observer: abc.ObserverBase[_T], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + def on_completed() -> None: + try: + os.replace(src_path, dst_path) + except Exception as e: + observer.on_error(e) + else: + observer.on_completed() + + return source.subscribe( + observer.on_next, observer.on_error, on_completed, scheduler=scheduler + ) + + return Observable(subscribe) + + return _replace diff --git a/src/blrec/utils/operators/retry.py b/src/blrec/utils/operators/retry.py new file mode 100644 index 0000000..45536a4 --- /dev/null +++ b/src/blrec/utils/operators/retry.py @@ -0,0 +1,46 @@ +import time +from typing import Callable, Iterator, Optional, TypeVar + +from reactivex import Observable, abc, catch_with_iterable +from reactivex import operators as ops + +_T = TypeVar('_T') + + +def retry( + count: Optional[int] = None, + delay: Optional[float] = None, + should_retry: Callable[[Exception], bool] = lambda _: True, +) -> Callable[[Observable[_T]], Observable[_T]]: + def _retry(source: Observable[_T]) -> Observable[_T]: + def subscribe( + observer: abc.ObserverBase[_T], + scheduler: Optional[abc.SchedulerBase] = None, + ) -> abc.DisposableBase: + exception: Optional[Exception] = None + + def counter() -> Iterator[int]: + n = 0 + while True: + if exception is not None: + if not should_retry(exception): + break + if count is not None and n > count: + break + if delay: + time.sleep(delay) + yield n + n += 1 + + def on_error(e: Exception) -> None: + nonlocal exception + exception = e + + _source = source.pipe(ops.do_action(on_error=on_error)) + return catch_with_iterable(_source for _ in counter()).subscribe( + observer, scheduler=scheduler + ) + + return Observable(subscribe) + + return _retry diff --git a/webapp/src/app/shared/pipes/progress.pipe.ts b/webapp/src/app/shared/pipes/progress.pipe.ts index 64954cc..7d4daac 100644 --- a/webapp/src/app/shared/pipes/progress.pipe.ts +++ b/webapp/src/app/shared/pipes/progress.pipe.ts @@ -7,9 +7,9 @@ import { Progress } from 'src/app/tasks/shared/task.model'; }) export class ProgressPipe implements PipeTransform { transform(progress: Progress): number { - if (!progress || progress.duration === 0) { + if (!progress || progress.total === 0) { return 0; } - return Math.round((progress.time / progress.duration) * 100); + return Math.round((progress.count / progress.total) * 100); } } diff --git a/webapp/src/app/tasks/shared/task.model.ts b/webapp/src/app/tasks/shared/task.model.ts index 72b1176..2bf0bf4 100644 --- a/webapp/src/app/tasks/shared/task.model.ts +++ b/webapp/src/app/tasks/shared/task.model.ts @@ -74,8 +74,8 @@ export enum PostprocessorStatus { } export interface Progress { - time: number; - duration: number; + count: number; + total: number; } export interface TaskStatus {