diff --git a/.github/workflows/build-nuitka.yml b/.github/workflows/build-nuitka.yml index 8fd5ba9..37fa64e 100644 --- a/.github/workflows/build-nuitka.yml +++ b/.github/workflows/build-nuitka.yml @@ -2,7 +2,9 @@ name: build LDDC on: push: - branches: main + branches: + - main + - dev paths: - "**.py" - ".github/workflows/build-nuitka.yml" @@ -58,7 +60,7 @@ jobs: sudo apt-get update sudo apt-get install libegl1 upx - - name: Install create-dmg/upx + - name: Install create-dmg if: ${{ runner.os == 'macOS' }} run: | brew install create-dmg @@ -105,7 +107,7 @@ jobs: disable-console: true - macos-app-icon: "res/img/icon/logo.png" + macos-app-icon: "res/img/icon/logo.icns" macos-app-version: ${{ steps.info.outputs.version }} macos-create-app-bundle: true macos-target-arch: ${{ steps.arch.outputs.nuitka_arch }} @@ -125,7 +127,7 @@ jobs: product-version: ${{ steps.info.outputs.version }} copyright: ${{ steps.info.outputs.copyright }} - windows-icon-from-ico: "res/img/icon/logo.png" + windows-icon-from-ico: "res/img/icon/logo.ico" mingw64: true windows-console-mode: 'attach' @@ -252,7 +254,6 @@ jobs: cd build 7z a -tzip -mx=9 ../upload/LDDC-${{ steps.info.outputs.version }}-windows-${{ steps.arch.outputs.arch }}.zip LDDC - - name: Upload uses: actions/upload-artifact@v4 with: diff --git a/LDDC.py b/LDDC.py index f9386e5..41d33e5 100644 --- a/LDDC.py +++ b/LDDC.py @@ -2,32 +2,30 @@ # SPDX-License-Identifier: GPL-3.0-only import sys -from PySide6.QtCore import ( - Qt, - QThread, -) +from PySide6.QtCore import QThread from PySide6.QtWidgets import QApplication -import res.resource_rc -from backend.service import ( - LDDCService, -) +from backend.service import LDDCService +from res import resource_rc from utils.args import args from utils.exit_manager import exit_manager from utils.translator import load_translation from utils.version import __version__ -res.resource_rc.qInitResources() +resource_rc.qInitResources() if __name__ == "__main__": app = QApplication(sys.argv) if args.get_service_port: + # 如果是获取服务端口,则只需实例化LDDCService(会获取并打印另一个LDDC进程服务端口),然后退出 LDDCService() sys.exit() show = args.show + + # 启动服务线程 service = LDDCService() service_thread = QThread(app) exit_manager.threads.append(service_thread) @@ -35,14 +33,18 @@ service_thread.start() service.instance_del.connect(exit_manager.close_event) - exit_manager.close_signal.connect(service.stop_service, Qt.ConnectionType.BlockingQueuedConnection) - + # 加载翻译 load_translation(False) + # 显示主窗口(如果需要) if show: from view.main_window import main_window main_window.show() + + # 检查更新 from utils.data import cfg if cfg["auto_check_update"]: from view.update import check_update check_update(True, QApplication.translate("CheckUpdate", "LDDC主程序"), "chenmozhijin/LDDC", __version__) + + # 进入事件循环 sys.exit(app.exec()) diff --git a/backend/api.py b/backend/api.py index 4a9d6a3..d47f3b6 100644 --- a/backend/api.py +++ b/backend/api.py @@ -366,6 +366,13 @@ def qm_search(keyword: str, search_type: SearchType, page: int | str = 1) -> lis if search_type not in (SearchType.SONG, SearchType.ARTIST, SearchType.ALBUM, SearchType.SONGLIST): msg = f"搜索类型错误,类型为{search_type}" raise ValueError(msg) + search_type_mapping = { + SearchType.SONG: 0, + SearchType.ARTIST: 1, + SearchType.ALBUM: 2, + SearchType.SONGLIST: 3, + SearchType.LYRICS: 7, + } data = json.dumps({ "comm": { "g_tk": 997034911, @@ -386,7 +393,7 @@ def qm_search(keyword: str, search_type: SearchType, page: int | str = 1) -> lis "num_per_page": 20, "page_num": int(page), "query": keyword, - "search_type": search_type.value, + "search_type": search_type_mapping[search_type], }, }, }, ensure_ascii=False).encode("utf-8") diff --git a/backend/decryptor/eapi.py b/backend/decryptor/eapi.py index a3ea2e4..420a35a 100644 --- a/backend/decryptor/eapi.py +++ b/backend/decryptor/eapi.py @@ -6,29 +6,40 @@ import json from base64 import b64decode, b64encode -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import padding -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from pyaes import AESModeOfOperationECB + + +def pkcs7_pad(data: bytes, block_size: int = 16) -> bytes: + pad_len = block_size - (len(data) % block_size) + padding = bytes([pad_len] * pad_len) + return data + padding + + +def pkcs7_unpad(data: bytes) -> bytes: + pad_len = data[-1] + if pad_len < 1 or pad_len > 16: + msg = "Invalid padding encountered." + raise ValueError(msg) + return data[:-pad_len] def aes_encrypt(data: str | bytes, key: bytes) -> bytes: if isinstance(data, str): data = data.encode() - backend = default_backend() - cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend) # noqa: S305 - encryptor = cipher.encryptor() - padder = padding.PKCS7(128).padder() - padded_data = padder.update(data) + padder.finalize() - return encryptor.update(padded_data) + encryptor.finalize() + padded_data = pkcs7_pad(data) # Ensure the data is padded + aes = AESModeOfOperationECB(key) # Using ECB mode + encrypted_data = b'' + for i in range(0, len(padded_data), 16): + encrypted_data += aes.encrypt(padded_data[i:i + 16]) # Encrypt in 16-byte blocks + return encrypted_data def aes_decrypt(cipher_buffer: bytes, key: bytes) -> bytes: - backend = default_backend() - cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend) # noqa: S305 - decryptor = cipher.decryptor() - unpadder = padding.PKCS7(128).unpadder() - decrypted_data = decryptor.update(cipher_buffer) + decryptor.finalize() - return unpadder.update(decrypted_data) + unpadder.finalize() + aes = AESModeOfOperationECB(key) # Using ECB mode + decrypted_data = b'' + for i in range(0, len(cipher_buffer), 16): + decrypted_data += aes.decrypt(cipher_buffer[i:i + 16]) # Decrypt in 16-byte blocks + return pkcs7_unpad(decrypted_data) # Remove padding after decryption def eapi_params_encrypt(path: bytes, params: dict) -> str: diff --git a/backend/decryptor/qmc1.py b/backend/decryptor/qmc1.py index b2358a1..da50e9c 100644 --- a/backend/decryptor/qmc1.py +++ b/backend/decryptor/qmc1.py @@ -4,7 +4,7 @@ # SPDX-FileCopyrightText: Copyright (c) 2024 沉默の金 # SPDX-License-Identifier: GPL-3.0-only -PRIVKey = [ +PRIVKEY = ( 0xc3, 0x4a, 0xd6, 0xca, 0x90, 0x67, 0xf7, 0x52, 0xd8, 0xa1, 0x66, 0x62, 0x9f, 0x5b, 0x09, 0x00, @@ -28,9 +28,9 @@ 0xc3, 0x00, 0x09, 0x5b, 0x9f, 0x62, 0x66, 0xa1, 0xd8, 0x52, 0xf7, 0x67, 0x90, 0xca, 0xd6, 0x4a, -] +) def qmc1_decrypt(data: bytearray) -> None: for i, _value in enumerate(data): - data[i] ^= PRIVKey[(i % 0x7FFF) & 0x7F] if i > 0x7FFF else PRIVKey[i & 0x7F] + data[i] ^= PRIVKEY[(i % 0x7FFF) & 0x7F] if i > 0x7FFF else PRIVKEY[i & 0x7F] diff --git a/backend/lyrics.py b/backend/lyrics.py index 3f476c9..e1e2c96 100644 --- a/backend/lyrics.py +++ b/backend/lyrics.py @@ -41,6 +41,7 @@ def get_full_timestamps_lyrics_data(data: LyricsData, duration: int | None, only if only_line: result.append(LyricsLine((line_start_time, line_end_time, line[2]))) + continue words = [] for j, word in enumerate(line[2]): diff --git a/backend/service.py b/backend/service.py index 56a6085..a11ad65 100644 --- a/backend/service.py +++ b/backend/service.py @@ -136,6 +136,9 @@ def __init__(self) -> None: self.clients: dict[int, Client] = {} self.start_service() + from utils.exit_manager import exit_manager + exit_manager.close_signal.connect(self.stop_service, Qt.ConnectionType.BlockingQueuedConnection) + def start_service(self) -> None: if args.get_service_port and not self.shared_memory.attach(): @@ -312,6 +315,7 @@ def handle_socket_message(self, message_data: bytes, client_id: int) -> None: instance_dict[json_data["id"]].signals.handle_task.emit(json_data) def socket_send_message(self, client_id: int, response: str) -> None: + """向客户端发送消息(前4字节应为消息长度)""" logger.debug("%s 发送响应:%s", client_id, response) if client_id not in self.clients: logger.error("客户端ID不存在:%s, self.clients: %s", client_id, self.clients) @@ -396,7 +400,7 @@ def to_select(self, if_show: bool = False) -> None: keyword += get_artist_str(self.song_info["artist"]) if self.song_info.get("title"): if keyword: - if len(keyword) > len(self.song_info["title"]): + if len(keyword) > len(self.song_info["title"]) * 2: keyword = "" else: keyword += " - " @@ -423,51 +427,46 @@ def send_task(self, task: str) -> None: def handle_task(self, task: dict) -> None: """处理客户端发送的任务""" + # 同步播放时间 + if isinstance((playback_time := task.get("playback_time")), int): # 单位为毫秒 + if isinstance((send_time := task.get("send_time")), float): # 单位为秒 + delay = (time.time() - send_time) * 1000 + + if delay > 0: + playback_time -= round(delay) + last_start_time = self.start_time + self.start_time = int(time.time() * 1000) - playback_time + logger.info("同步播放时间 playback_time:%s|%s| delay:%s", playback_time, last_start_time - self.start_time, delay) + + # 处理任务 + logger.debug("instance %s handle task: %s", self.instance_id, task) match task["task"]: case "start": # 开始播放 - logger.debug("start") - if (playback_time := self.get_playback_time(task)) is not None: - self.start_time = int(time.time() * 1000) - playback_time - else: - self.start_time = int(time.time() * 1000) - self.current_time if not self.timer.isActive(): self.timer.start() self.widget.set_playing(True) case "chang_music": # 更换歌曲 - logger.debug("chang_music") self.taskid += 1 # 防止自动搜索把上一首歌的歌词关联到这一首歌上 - title = task.get("title") - artist = task.get("artist") - album = task.get("album") - duration = task.get("duration") - song_path = task.get("path") - track = task.get("track") lyrics = None self.reset() self.widget.new_lyrics.emit({}) - if (not isinstance(title, str | None) or - not isinstance(artist, str | None) or - not isinstance(album, str | None) or - not isinstance(duration, int) or - not isinstance(song_path, str | None) or - not isinstance(track, int | str | None)): + # 处理歌曲信息 + song_info = {k: task.get(k) for k in ("title", "artist", "album", "duration", "path", "track")} + + if (any((not isinstance(song_info[k], str | None)) for k in ("title", "artist", "album", "path")) or + not isinstance(song_info["duration"], int) or not isinstance(song_info["track"], int | str | None)): logger.error("task:chang_music, invalid data") return - if (playback_time := self.get_playback_time(task)) is not None: - self.start_time = int(time.time() * 1000) - playback_time - - self.song_info = {"title": title, "artist": artist, "album": album, "duration": duration, "song_path": song_path, - "track_number": str(track) if isinstance(track, int) else track} + self.song_info = {**{k: v for k, v in song_info.items() if k in ("title", "artist", "album", "duration")}, "song_path": song_info["path"], + "track_number": str(song_info["track"]) if isinstance(song_info["track"], int) else song_info["track"]} # 先在关联数据库中查找 self.to_select(True) - query_result = local_song_lyrics.query(**self.song_info) - - if query_result is not None: + if (query_result := local_song_lyrics.query(**self.song_info)) is not None: lyrics_path, config = query_result try: @@ -481,64 +480,54 @@ def handle_task(self, task: dict) -> None: if self.config.get("inst"): self.set_inst() - in_main_thread(self.widget.menu.actino_set_auto_search.setChecked, bool(self.config.get("disable_auto_search"))) - return - - try: - lyrics, _from_cache = get_lyrics(Source.Local, path=lyrics_path) - self.lyrics_path = lyrics_path - except Exception: - logger.exception("读取歌词时错误,lyrics_path: %s", lyrics_path) + else: + try: + lyrics, _from_cache = get_lyrics(Source.Local, path=lyrics_path) + self.lyrics_path = lyrics_path + except Exception: + logger.exception("读取歌词时错误,lyrics_path: %s", lyrics_path) if isinstance(lyrics, Lyrics): self.set_lyrics(lyrics) elif not self.config.get("disable_auto_search"): - if isinstance(title, str): + if isinstance(self.song_info["title"], str): # 如果找不到,则自动获取歌词 - self.widget.update_lyrics.emit(DesktopLyrics(([("自动获取歌词中...", "", 0, "", 255, [])], []))) + self.widget.update_lyrics.emit( + DesktopLyrics(([(QCoreApplication.translate("DesktopLyrics", "自动获取歌词中..."), "", 0, "", 255, [])], [])), + ) self.taskid += 1 - worker = AutoLyricsFetcher({"title": title, "artist": artist, "album": album, "duration": duration / 1000}, - min_score=55, source=[Source[s] for s in cfg["desktop_lyrics_sources"]], taskid=self.taskid) + info = self.song_info.copy() + info["duration"] = info["duration"] / 1000 + worker = AutoLyricsFetcher(info, + source=[Source[s] for s in cfg["desktop_lyrics_sources"]], taskid=self.taskid) worker.signals.result.connect(self.handle_fetch_result) threadpool.start(worker) else: # 没有标题无法自动获取歌词 - self.auto_search_fail("无法自动获取歌词") + self.auto_search_fail(QCoreApplication.translate("DesktopLyrics", "没有获取到标题信息, 无法自动获取歌词")) else: self.show_artist_title() in_main_thread(self.widget.menu.actino_set_auto_search.setChecked, bool(self.config.get("disable_auto_search"))) case "sync": - # 同步当前播放时间 - playback_time = self.get_playback_time(task) - a = self.start_time - self.start_time = int(time.time() * 1000) - playback_time - logger.debug("sync, self.start_time: %s | %s", self.start_time, self.start_time - a) + # 同步歌词 self.update_lyrics() case "pause": # 暂停歌词 - logger.debug("pause") if self.timer.isActive(): self.timer.stop() self.widget.set_playing(False) case "proceed": # 继续歌词 - logger.debug("proceed") - playback_time = self.get_playback_time(task) - if playback_time is not None: - self.start_time = int(time.time() * 1000) - playback_time - else: - self.start_time = int(time.time() * 1000) - self.current_time if not self.timer.isActive(): self.timer.start() logger.debug("proceed, self.start_time: %s", self.start_time) self.widget.set_playing(True) case "stop": - logger.debug("stop") # 停止歌词 self.reset() if self.timer.isActive(): @@ -559,7 +548,7 @@ def handle_fetch_result(self, result: dict[str, dict | Lyrics | str | int | bool return result['status'] = QCoreApplication.translate("DesktopLyrics", "自动获取的歌词为纯文本,无法显示") - self.auto_search_fail(f"自动获取歌词失败:{result.get('status')}") + self.auto_search_fail(QCoreApplication.translate("DesktopLyrics", "自动获取歌词失败:{0}").format(result.get("status"))) logger.error("自动获取歌词失败: %s", result.get("status")) def select_lyrics(self, lyrics: Lyrics, path: str | None = None, langs: list[str] | None = None, offset: int = 0, is_inst: bool = False) -> None: @@ -815,27 +804,12 @@ def _add(_line: tuple[int, int, list[tuple[int, int, str]]], rubys: list | None add(current_lines[0], "current") else: - # 找到多行歌词 + # 找到多行歌词(显示两行以上会导致淡入淡出错误) for line in current_lines: add(line, "current") self.widget.update_lyrics.emit(DesktopLyrics((left, right))) - def get_playback_time(self, task: dict) -> float: - # 获取当前播放时间 - playback_time = task.get("playback_time") # 单位为毫秒 - send_time = task.get("send_time") - if isinstance(playback_time, int) and isinstance(send_time, float): - # 补偿网络延迟 - delay = (time.time() - send_time) * 1000 - logger.debug("delay: %s ms", delay) - if delay > 0: - playback_time = playback_time - round(delay) - else: - msg = "playback_time or send_time is not int or float" - raise TypeError(msg) - return playback_time - def update_refresh_rate(self) -> None: """更新刷新率,使其与屏幕刷新率同步""" refresh_rate = self.widget.screen().refreshRate() if cfg["desktop_lyrics_refresh_rate"] == -1 else cfg["desktop_lyrics_refresh_rate"] @@ -845,6 +819,7 @@ def update_refresh_rate(self) -> None: self.timer.setInterval(interval) def cfg_changed_slot(self, k_v: tuple) -> None: + """处理配置变更""" key, value = k_v match key: case "desktop_lyrics_default_langs": diff --git a/backend/song_info.py b/backend/song_info.py index 89fa2c5..611233c 100644 --- a/backend/song_info.py +++ b/backend/song_info.py @@ -214,19 +214,19 @@ def parse_cue(data: str, file_dir: str, file_path: str | None = None) -> tuple[l # 处理音频文件路径 audio_file_path = os.path.join(file_dir, file["filename"]) - if not os.path.exists(audio_file_path): + if not os.path.isfile(audio_file_path): for file_extension in file_extensions: if file_path and ( (audio_file_path := os.path.join(os.path.dirname(file_path), os.path.splitext(file["filename"])[0] + "." + file_extension)) - and os.path.exists(audio_file_path) or + and os.path.isfile(audio_file_path) or (audio_file_path := os.path.splitext(file_path)[0] + "." + file_extension) - and os.path.exists(audio_file_path)): + and os.path.isfile(audio_file_path)): break else: logger.warning("未找到音频文件: %s", file["filename"]) audio_file_path = "" - if os.path.exists(audio_file_path): + if os.path.isfile(audio_file_path): audio_file_paths.append(audio_file_path) for i, track in enumerate(file["tracks"]): diff --git a/backend/worker.py b/backend/worker.py index 18e2736..eae9a68 100644 --- a/backend/worker.py +++ b/backend/worker.py @@ -635,6 +635,8 @@ def handle_search_result(self, taskid: int, search_type: SearchType, infos: list for info in infos: if duration and abs(info.get('duration', -100) - duration) > 3: continue + if not duration: + logger.warning("没有获取到 %s - %s 的时长, 跳过时长匹配检查", get_artist_str(self.info.get('artist')), self.info['title'].strip()) artist_score = None title_score = calculate_title_score(self.info['title'], info.get('title', '')) diff --git a/pyproject.toml b/pyproject.toml index 82a97ae..43510ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,8 +7,9 @@ dependencies = [ "mutagen", "diskcache", "chardet", - "cryptography", + "pyaes", "psutil", + "pyobjc; sys_platform == 'darwin'", ] requires-python = ">= 3.10" description = "A lyrics acquisition tool" @@ -73,6 +74,7 @@ ignore = [ "Q000", # bad-quotes-inline-string "RUF001", # ambiguous-unicode-character-string "N802", # invalid-function-name + "N999", # invalid-name "PTH", # flake8-use-pathlib "FBT", # flake8-boolean-trap diff --git a/requirements.txt b/requirements.txt index 54be0d3..ff875ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,8 @@ requests -PySide6 +PySide6-Essentials mutagen diskcache -chardet -cryptography -psutil \ No newline at end of file +charset-normalizer +pyaes +psutil +pyobjc; sys_platform == 'darwin' \ No newline at end of file diff --git a/res/i18n/LDDC_en.qm b/res/i18n/LDDC_en.qm index 3ca727f..19e82de 100644 Binary files a/res/i18n/LDDC_en.qm and b/res/i18n/LDDC_en.qm differ diff --git a/res/i18n/LDDC_en.ts b/res/i18n/LDDC_en.ts index 9732050..1620424 100644 --- a/res/i18n/LDDC_en.ts +++ b/res/i18n/LDDC_en.ts @@ -4,7 +4,7 @@ AboutWidget - + LDDC主程序 LDDC main program @@ -54,12 +54,27 @@ DesktopLyrics - + + 自动获取歌词中... + Automatically getting lyrics... + + + + 没有获取到标题信息, 无法自动获取歌词 + The song title was not obtained, so the lyrics cannot be automatically obtained + + + 自动获取的歌词为纯文本,无法显示 The automatically acquired lyrics are in plain text and cannot be displayed - + + 自动获取歌词失败:{0} + Failed to automatically obtain lyrics: {0} + + + 纯音乐,请欣赏 Pure music, please enjoy @@ -67,17 +82,17 @@ DesktopLyricsControlBar - + 逐字 verbatim - + 逐行 line by line - + 纯文本 Plain Text @@ -110,42 +125,42 @@ DesktopLyricsMenu - + 选择歌词 Select Lyrics - + 标记为纯音乐 Mark as instrumental music - + 禁用自动搜索(仅本曲) Disable automatic search (this song only) - + 取消歌词关联 Unlink lyrics - + 歌词关联管理器 Open Lyrics Link Manager - + 显示/隐藏桌面歌词 Show/Hide Desktop Lyrics - + 显示主窗口 Show Main Window - + 鼠标穿透 Mouse penetration @@ -153,50 +168,50 @@ DesktopLyricsSelectWidget - - + + 选择歌词 Select Lyrics - + 为桌面歌词选择云端或本地歌词 Choose cloud or local lyrics for desktop lyrics - + 选定歌词 Select lyrics - - + + 打开本地歌词 Open local lyrics - + 打开本地歌词失败 Failed to open local lyrics - + 歌词文件(*.qrc *.krc *.lrc) Lyrics File(*.qrc *.krc *.lrc) - - + + 提示 Info - + 不支持纯文本歌词 No support for plain text lyrics - + 请先选择歌词 Please select lyrics first @@ -204,7 +219,7 @@ DesktopLyricsTrayIcon - + LDDC桌面歌词 LDDC Desktop Lyrics @@ -342,12 +357,12 @@ GetListLyrics - + 提示 Info - + 是否要退出获取专辑/歌单歌词? Do you want to exit getting album/playlist lyrics? @@ -468,7 +483,7 @@ - + 警告 Warning @@ -478,30 +493,18 @@ The song folder does not exist! - - - + 错误 Error - - 保存模式选择错误! - Wrong save mode selection! - - - - 歌词文件名错误! - Wrong save mode selection! - - - + 请选择至少一个源! Please select at least one source! - + 取消匹配 Cancel matching @@ -511,7 +514,7 @@ Please select at least one lyrics language! - + 开始匹配 Start matching @@ -519,32 +522,32 @@ LocalSongLyricsDB - + 成功 success - + 备份成功 Backup Success - + 恢复成功 Restoration successful - + 清理成功, 共清理了 {0} 条数据 Cleaned successfully, a total of {0} data items were cleaned - + 修改成功, 共修改了 {0} 条数据 Modification successful, a total of {0} data items were modified - + 删除成功 Deleted successfully @@ -699,30 +702,40 @@ MainWindow - + 搜索 Search - + 本地匹配 local match - + 打开歌词 Open Lyrics - + 关于 About - + 设置 Settings + + + 提示 + Info + + + + 正在匹配歌词,是否退出? + Matching lyrics, do you want to exit? + OpenLyricsWidget @@ -823,112 +836,112 @@ SearchWidget - - - - + + + + 警告 Warning - + 请先选择一个专辑或歌单 Please select an album or playlist first - + 获取 {0} 歌词成功 Obtaining the lyrics of {0} successfully - + 但歌曲为纯音乐,已跳过 but the song is pure music and has been skipped - + 但保存歌词失败,原因: but saving the lyrics failed, reason: - + ,保存到 ,Save to - + 关闭 close - + 从云端搜索并下载歌词 Search and download lyrics from the cloud - + 保存到: Save to: - + 保存预览歌词 Save Preview Lyrics - + 保存专辑/歌单的歌词 Save album/songlist lyrics - - + + 提示 Info - + 获取歌词完成 Get lyrics completed - + 取消 Cancel - + 请先下载并预览歌词并选择保存路径 Please download, preview lyrics and select the save path first - + 歌词内容为空 Lyrics content is empty - + 歌词保存成功 Lyrics saved successfully - + 歌词保存失败: Failed to save lyrics: - + 正在自动获取 {0} 的歌词... Automatically getting lyrics for {0}... - - + + 选择保存路径 Select save path - + 搜索 Search @@ -936,150 +949,150 @@ SearchWidgetBase - - + + 搜索 Search - - - - - - + + + + + + 错误 Error - + 没有搜索到相关结果 No relevant results found - + 请输入搜索关键词 Please enter the search keyword first - + 正在搜索... searching... - + 获取预览歌词错误 Getting preview lyrics error - + 纯文本 Plain Text - + 逐字 verbatim - + 逐行 line by line - + 原文 original - + 、译文 , translation - + 、罗马音 , romanized - + 处理中... Processing... - - + + 歌曲 Song - - - + + + 艺术家 Artist - - - + + + 专辑 Album - - + + 时长 Duration - + 发行日期 Release date - - + + 歌曲数量 Song count - + 歌单 song list - + 创建者 Creator - + 创建时间 Creation time - + 来源 Source - + 提示 Info - + 没有找到歌词 Lyrics not found - + 没有更多结果 No more results - + 加载中... Loading... @@ -1087,27 +1100,27 @@ SettingWidget - + 选择默认保存路径 Select default save path - + 缓存大小: Cache size: - + 罗马音 romanized - + 原文 original - + 译文 translation @@ -1551,31 +1564,6 @@ 设置 Settings - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> -p, li { white-space: pre-wrap; } -hr { height: 1px; border-width: 0; } -li.unchecked::marker { content: "\2610"; } -li.checked::marker { content: "\2612"; } -</style></head><body style=" font-family:'Microsoft YaHei UI'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">以下占位符可用</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">歌名: %&lt;title&gt; 艺术家: %&lt;artist&gt;</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">专辑名: %&lt;album&gt; 歌曲/歌词id: %&lt;id&gt;</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">语言类型: %&lt;langs&gt;</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> -p, li { white-space: pre-wrap; } -hr { height: 1px; border-width: 0; } -li.unchecked::marker { content: "\2610"; } -li.checked::marker { content: "\2612"; } -</style></head><body style=" font-family:'Microsoft YaHei UI'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The following placeholders are available:</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Song Title: %&lt;title&gt; Artist: %&lt;artist&gt;</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Album Title: %&lt;album&gt; Song/lyrics id: %&lt;id&gt;</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Language Type: %&lt;langs&gt;</span></p></body></html> - 歌词设置 @@ -1626,6 +1614,31 @@ li.checked::marker { content: "\2612"; } 歌曲搜索歌词时自动选择(酷狗音乐) Songs automatically selected when searching for lyrics (Kugou) + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> +p, li { white-space: pre-wrap; } +hr { height: 1px; border-width: 0; } +li.unchecked::marker { content: "\2610"; } +li.checked::marker { content: "\2612"; } +</style></head><body style=" font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">以下占位符可用</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">歌名: %&lt;title&gt; 艺术家: %&lt;artist&gt;</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">专辑名: %&lt;album&gt; 歌曲/歌词id: %&lt;id&gt;</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">语言类型: %&lt;langs&gt;</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> +p, li { white-space: pre-wrap; } +hr { height: 1px; border-width: 0; } +li.unchecked::marker { content: "\2610"; } +li.checked::marker { content: "\2612"; } +</style></head><body style= " font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The following placeholders are available:</p> +<p style="margin-top:12px; margin-bottom:12px; margin-left:0px; margin -right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:6pt;">Song title: %&lt;title&gt; Artist: %&lt;artist&gt; </span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent: 0px;"><span style=" font-size:6pt;">Album name: %&lt;album&gt; Song/lyrics id: %&lt;id&gt;</span></p> +<p style=" margin- top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:6pt;">Language type : %&lt;langs&gt;</span></p></body></html> + LRC歌词毫秒位数 diff --git a/res/resource_rc.py b/res/resource_rc.py index f6a0a3f..586c229 100644 --- a/res/resource_rc.py +++ b/res/resource_rc.py @@ -2374,690 +2374,691 @@ 1\x00 \x22\x22\xba\x80\x18\x00\x10\x11\x11]@\x0c\x00\ \x88\x88\x88.\xa0\xff?\x18D:_\xdfPR\x15\x00\ \x00\x00\x00IEND\xaeB`\x82\ -\x00\x00*\x97\ +\x00\x00*\xa9\ \x00\ -\x00\x8d\xfdx\xda\xed}\x0bxTE\x96p%!!\ -\x09\x9d4D\xc1\x80<\x9a\x161@\x92N\x00\x11C\ -\x00!A\x01\xc3C\x82\xe2\x1b:\xdd\x9d\xa4\xa5\xd3\xdd\ -tw\x08\x11\xd1\x00F\xc2#$\x08\x18\x84\xc8K\x9e\ -*I\x04\x85\x10\x02\xe8\xce\xce\x8c\xeb\xea\xb8\x8e\xb3\xff\ -\xce8\xa3\xe3\xa3ow\xc7\xf1\x97\x99\xd1uvfv\ -\xc6\xff\xdc\xaa\xba\xf7\xd6}u7\xe8~\xff\xff\x7f\xbb\ -_>\xa8~\xdc:\xe7\xd4\xa9S\xe7\x9c:\xe7Tu\ -q\xb7=\xfb\xed+\xed\xbb\xdf\x1d=\xfc\xf2\x8aC\x17\ -?:\x8e\x10Jv\xb8\x97\xdf[>\x1b\xa1t\x13B\ -wnDhi\x03i\x1f~\x9f\xb4+.\x22t\x7f\ -\x00\xa1{g\x92\xf6\x91V\xd2Z_C\x86\xcf\x8e\x22\ -t\xeb\xa7h\xd8\xdf{\xe1\xfbjtc\x17|g\xca\ -G#>y\x07\xa1\xbajtS\xc3k\x08\xddQ\x81\ -r\xca\xe1\xfd\xea\x9dh\xe2W\xff\x82P\xff\xbb\xd1\xd4\ -\xeb\xeb\x10\x9a\xfd\x10\x9a\xba*\x88\xd0\xf8\x7fF\xb7\x7f\ -\xedE\xe8\xee\x07Hk\xff\x02M\xbbq1B\xf3\xff\ -JZ\xeb\x13\xa8\xd4\xf4[\x84\x96G\xd0=\xe8q\x84\ -\x8cS\x90\xffd\x17B\xa3>C\xabS\xf6\x22T\xff\ -3T\xff\xc5_\x10*\x9e\x82\xd6V\xb7 \x94}+\ -\xda\xc2?\x97\x99\x8c\x9a\xb7=\x86P\xd1>tb\x09\ -|\xff\xc4:t\xf1\x8ez\x84\xf2\xe6\xa2O\xbe\x84\xcf\ -\x1f4\xa0\xcf&\xccGhzk\xc2\xa0aV\x18\xc7\ -\x82\x84\xa1\x8fB\xff\xbb\xbeH\x98\x92\xf2 B\x8f\x97\ -$<\xd6\x07\xfd\x0a\xd3\x12\x9e\xffh\x14\xb4\xa1\x84\xf7\ -\x86\xfeo\x84\x1c_%\xbcw\xb0\x0f\xf8t\x92\xb4\x0f\ -\xff\x85\xb4+B\x09\xff\xb2\xe8y\xf8\xfc\xf7\xa4}d\ -\x16i\xady\x09\x9f~u\x0a\xa1I;H\xfb\xc0\x9e\ -\x84\xcf\x16\x00}\x0f\xa5\x90\xd6\x9e\x9c\xc0\xfd\xc7:\xe8\ -7(\xa1\xaf\xf0K\xc0?4\xe1w\xeb~\x8c\xd0\x8d\ -o'\xf6\xdf5\x1d\xfa\x8dJ\xec\xff\xea\x06\x84\xfa\xd5\ -&\x0ez\x13\xe8\x1f\x98M\xda1^\xd2nhM\x1c\ -\xba(\x11\xa1\xa7\xa6%\xe6\x7f\xfa\x1e\x8c\xe7\x8f\x89\x13\ -;\x1fF(aE\xe2\xd4\x9f\xbc\x0dt\xfc\x95\xb4\x15\ -\x19\x893\xc7LF(\xe5\xd6\xc4\xb9\x07n@(\xff\ -\xf5\xc4\xb2\x99\x83\x11\x1a\xb7'\xf1\xe9\x05\xc3\x10Z\xfb\ -M\xe2\x8f\xa7?\x0b\xed\xe4\xc4\x0fF\xde\x8c\xd0\x90w\ -\x12\x7fq\xa6\x10\xa1\xdaO\x12\xff|\x1f\xf0\xb9\xe1@\ -\xd2\xd0\xf1\xcd\x08e,I\xba\xf1Wn\x84\x865%\ -\x0d\xf7\x01?\x8a\xd2\x93\x0a\xbbv\x82 \x85\x93\xee\x1e\ -\x01|\x9d\xb1,\xa9|\x0f\xccW\xd9\xf2\xa4\xfbN?\ -\x89\xd0\xcd\x8f$u\xee\xfd\x02\xa1\xa4\x01I\xa7\xbf\xea\ -\x81q/Jz{8\xf4\x9f\xf0oI\xe1\xba,\x98\ -\xc7\xa1\xfd\x8c\x1f\x02}S?\xe97\xf8N\xe0\xd7\x80\ -S\xa4\x9dzK\xbf\x11\x7f8\x8c\xd0\xd0\xfa~7w\ -}\x8d\xd0\x92\xb7\xfa\x8d\x9b\xf1\x1dB%\xbf\xedWt\ -\xb6\x08\xa1\xc9u\xfd\xa6\xf5}\x0a\xf2\xf6O\xfd\xbao\ -\x7f\x01!KR\xbf\x7fx\x13\xc6a|\xaa\xdf\xaf\xdf\ -x\x1d\xe4\xf9\xdf\xfb}\xb2\x03\xf8\xbbqi\xbfO^\ -\x86y]\xb0,9!\x07\xe8[>,y\xc4\xe7W\ -\x10\x1a\xfbA\xf2M\xbc,\xdf8!y\xec\xe0\xfd\x00\ -wyr\xee\xf6E\xf0ycr\xde\xa150/\x87\ -\x93\xa7\x06@>'4%\x17O~\x0b\xe4\xf6\x1ci\ -'\x0cI.}'\x03\xf0\xf9\x92K?\xbe\x0e\xf8\xf9\ -m\xf2\xc2W\x1f\x805\xf18i\x1fu\x92\xb6\xe2d\ -\xf2\xe2,XK\xf6\x85\xc9\x8b7\xfc\x1d\xa1'\xbfI\ -\xdeX\x00k)eS\xf2\xa1wG\x82|\xadK\xfe\ -\xe9FXS\x89G\x92??\xb6\x00\xa1\xb9\xcb\x93\xff\ -4}\x02B\xd7\xcfJ1$\x02\xfcDc\xca\xb0W\ -\xdfE(mY\xca\xe8o\x96\xc2z\xf9eJ\xa1\x19\ -\xe8y8-\xa5\xb4\xe2\x03X\xb9\xdbH;\x7f\x08i\ -\x1fz\x96\xb4\xcbW\x91\xd61\x93\xb4\x0d\xc5)u\x05\ -\xf9\x08\x0dw\xa7\xbc\xec\x00:nY\x99\xf2\xd3\x9f\x8f\ -G\xe8\xf6\xd9)\xbf\xd8\xf1\x11B\xa3\xb7\x91\xf6\xfe_\ -\xa5\xfc\xeb\xab\x1e\xa0sW\xca\xe7\x9f\xa7\x01\xdf\xbfK\ -\xe9[\x08t\x9a\xdfJ\xf9\xdd\x1b\xa3\x11\xba\xee\x83\x94\ -/W\x0dEh\xd9\x92\xfe)7\xbc\x81P\xb9\xbb\xbf\ -\xf1\xfc \x84nZ\xd0\x7f\xc4\xfd@\xe7\x10k\xff_\ -\x1e\xf9\x04\xa1\x99?\xef\xff\xdb\xf2\xb3\x00\xe7\xd6\xfe_\ -\x0e\x82\xe7\x1f\xffM\xea\xc0e0\xbfO\xbd\x9e:\xca\ -\xf2\x0c\xac\xb3\xf4T\xd3\xba\x11\xf0|*i\xef\x5c\x9c\ -\x9a7\xe9o\x08\x19^J-\xf8\xd6\x00\xef\x8f\xa7\x16\ -\xffd\x15\xe0]\x96\xba\xa8 \x0f\xe8\x9b\x9e\xfa\xf0\x8f\ -\xfe\x88\xd0\x08g\xea\x8aG`\xfd\xdc7-\xb55\xe7\ -\x12B\x95/\xa4\xbetn\x06\x8c\xab/52:\x05\ -\xd6\xf5oR\xfb\x926\xc3\xba\xb8\x94\xfa\xdd\x99\xfba\ -\xfd\xbf\x95\x86n\x0a\x81\xdc}\x9d\xd6\xdf\xf2\x13\xf8>\ -\x85\xb4wu\xa5\x0dz\xfe$B9_\xa6\x8d\x8a\x0c\ -\x87\xf5\xf4B\xda\xa8?-Dh\xd0\xdc\xb4\xbc\xaf\x80\ -\x9e\xe44\xd2\xa6\x9e#\xad\xe1\xefi{\xbe\x01\xb9)\ -\x9c\x9e\xd6we\x1e\xe0\xff\x22\xed[o\x0e\x8c\xffh\ -\xda_\xde\x19\x0b\xf3\xfdV:\xc6\xd3oj:Z\x08\ -|i\xf8\xcf\xf4\x01O\x83|]W\x91~\xc3\x93\xa0\ -\x1fKnJ\x1f\xe1\x87\xf9\x9c\xfc#\xd2>r>}\ -\xd4\xfb o\xa59\xe9\x05_g\x02_\xfe\x94^x\ -\x1d\xb4k\xbeH_\xbc\x0e\xd6\xc5\x8c\xee\xf4\xfb\x97\xff\ -\x06\xe8_\x98\xfe\xd2\x9b\xcba\x9e\x11i\x13\x06\xa5\x9f\ -/\x039\x99X\x9f\x1e\xfc['\xc8\xc9\x85t\xee\xd7\ -\xbc\xde}'\xfdw\xc3@w\xd7\xaeN\xff\xf7\xcf@\ ->2N\x0fHY4\x0e\xe4\xa4b@\xff\xef`}\ -\x8e_?`x\x03\xaf_\x7fC\xda\xca\xaa\x01cW\ -\x9c@h\xf0\x94\x01\xf9\x9d\xa0\xf7Gn\x1eP^\x08\ -\xfa--i\x80\xf7>\x18\xe7\x8a\x82\x01]\xb7\xdd\x8a\ -\xd0\x0d\x87\x07\xbc\xfb\xb9\x03\xda\x05\x03>x\x08\xe4f\ -\xf0QC\xc2\xd8\x8f\x11\xca\xfa\xa9!\xf9q'B\xeb\ -j\x0c)\x1f\x1f\x049z\xcfp\xdd\xa8\xa7`\xddL\ -0\x0c\xfb\xc3v\xf8|\xa0ad\x02\x87\xd0,\x8ba\ -\xdc\xa3w!\xb4\xdei(\x18\x0fzt\xc3\xfd\x86\xda\ -\x11\xb0\xbef\xce7<\xf5\xe7-\xa0\xb7\xff\xd3\xd0\x90\ -\x91\x0ezy\xa2a\xeb$\xc0\xfb\xc0h\xc3\x9e\x81\xa0\ -\x1f\xe6\xbdo\xd8w\x1a\xe4v\xf6\xf5\x86\x83\x16;\xac\ -\xdf$\xc3\xbf\xed\x03\xfd|\x1b2\xfc\xea\x0a\xc8\xf1\x9d\ -\xfd\x0c\x1f\xcf\x01\xf9\xb8\xe9\xfd\x8c\xccO\xc0\x0e$\xfc\ -\x13i\xcb\xf33nI;\x0d\xfa?\x941\x1e\x81\xde\ -\x19t%c\xd6{\xdf \x94{_\xc6l+\xf0\xa1\ -xU\xc6\xdd{\xf9\xf5\xf4Q\xc6\x877\x80\xde\xb8m\ -y\xc6\x87v\xd0\x9b\xf5\x0ff\x04\xbf>\x0fr\xdd\x95\ -\xf1\xfbD\xd0S\x0f:2\xfe\xfc \xe8\xb9i?\xce\ -\xf8\xeeT1\xcc\xeb\x1b\xa4\xbd\xcb\x9d9\xf8\x11\x98\xf7\ -\xa7VgN\xb8\x1b\xf4\xe1\x94\x9c\xcc\x89m\xb0\x9e\x0d\ -S2\xcb\xffy\x22\xc8\xe7W\x99-\x8f\x82E\x9dv\ -s\xe6\xfe\x8fA\x0en\xfbu\xe6\xf1\xf2\x7f\x84\xf5\xb9\ -#\xf3S\xb0\x89h\xe2\xefI[\xde/\xf3\xd3\x9b@\ -\xae\x8a\xdf\xce\xfct\xf1\x9d\x80?/\xf3\xb3\xafA/\ -\xe7\xb83\xc3`+Q\xc5\x9c\xcc\xc8zX\x8fC\xe7\ -gF~\x1b\x86\xe9_D\xda\x92#\xa4\x9d\xf7\x82P\ -\xc1u\xc6\xac\xedsa\xdd\x05\x8cC:@^\xe7,\ -\x22\xadm\x83qH\x17\xf0s\xce6\xe3\x903`_\ -\x1f\xf8\xd08\xe4;\xc0\xb7\xfe\x82q\xe8\xef\xab`\x9d\ -?D\xda\xb9!\xe3\xd0?\xb6\xc3\xfb\x17H;o\xb4\ -q\xe4\xbe7a\xbd\xf67\x8e\xfc\x11\xc8Q\xfe\x04\xe3\ -\xe8\xd9\xe0'\xe4^6\x8e)\x81\xf5\x97\xbe\x93\xb4S\ -\x02\xc6q\xd3\xc0\xbex-\xa7w\ -0w\xeet\xf8BKhOg\xa8\xbd!\xf4\x5cW\ -xsS\xa8\xfd\x15MD\xc3\x90\x0d\x03\xb6\xa1\x95\x00\ -\xae\x16(\xb6\x0b\x08Dp\x86\xd0\x8b\x0d\xa1\x03GC\ -\xfb^\x03X\x9aP\xee\x83q\x0bP\xf8\x91W\x01\xac\ -J<\xa7\x0a\x98\xf8s\x9e;.~\x9a\xe0]\x1d\xbc\ -\xe6\x87f\xc2\x03\xf1\xe1>E\xf0n-*@\xebD\ -\x0aF\xb1\x14pG^\x8d\xbcv\xf4J\xef\xb6\xbe]\ -\xcfG:;\x8b\xd6\x16\xac\xd3$\xaa\x00-\x06\xe4>\ -\xcc\xb5\x1ax\xc5s\xc9\x86r\xf1\xb4\xb80g\xfd\xf8\ -;\x07\x10\xfc\x18 \xae\x97\xf8\x17\xee\xe9\x04\x91\x09\x9e\ -o\x05,\x91\xces\xa1W^\x8c\xbc\x8e'*\xa3\xd4\ -\xe1_\x19\xf0x\xcb\xea}N\x9b\x1f\xa3y\x0e-\xa5\ -Sc\x05$\x01*\x8a\x01\x8c\x0c$\x19\xfe\xea\xf1w\ -6\xb4\x0a\xbewb\x82\xecx\x22\xeb\xb1\xcc\xdb0W\ -\xac\x94PAn\x5c\xa2\x0c\xf1|[\x83'\x9b\x17P\ -\xbe\xa7\x0d\xbfr\x03&\xfe\xd3\x0a\xdc\xcf\x0eO\xfbi\ -\xbfz\xd9\x12(\x88lz\x89\xdbr\x22\xb2\xfd\x1c\xd7\ -\xf2\x5cx\xef\xd3\xa1S0\xa4\xe6`w\x0f\x0c\x12\x84\ -\x1a\x04\x03\x06\x19j;\x18:\xb3;\xb4\xe7R\xf8H\ -\x8f\xce8\x07\x02;y\x1aWca\xabe\xe4#-\ -\xd8\xbd%\xd8\xdd\xd0w\xec9\xbe\xeb\x0d\xb2\xae%\x1e\ -w\xc0\xe7q\xcd\xb6\xfa0\x90T\xb4\x90\x0c\x87\xed\xbc\ -5\xce\xceY@\x01\x19\x9f\x05\x95cN{\xa55\x15\ -z\xf6Th\xd7%\x0b\xb7\xbe=t\xeapLP\xd7\ -SP\x84\xc5K\x954\x89\xac\x89\x09h\x18P\xe2\xc0\ -\xe2d\xc3\xb3Q&\xcd\xaa\xb4~\xfa\x1a6\x87\xb6\x9e\ -$\x8c\x8f\x09q ]\xd4\x15T\x8a$\xbd\x91\xd2\xd7\ -\xd0\xca\x9dj\x8b\x09!\x1b\xe8\xe1\x07\xe6\xc0\xd2Q\x8f\ -e\x0d\xbf\x97A\x8a\x1c\xda\x16\x13\xd2\x104\x17z\xda\ -1\xa4e\x18\x86\x1d\xb8^\xc7\x8clok\xa4\xad%\ -\xfcR\x1b\xd7\xf2b\x1c\xbc\xba\x17 \x10ZV\xcaW\ -\x80\x081\x0b\x844t\xb6\x89\xf0\x8ak<\x13Y\xbf\ -\x8b\x87;H\x06w\x81\xc3]K\x97x9\xac=\xa0\ -\x08$B\xa2\xb4\x14k\x9a\x95DBt\xe6d(\x91\ -u\x0b\x19@\xe8\xd0\xb6\xbe\x17\x0eK\x13\xa4\x83\xce$\ -\xa23\xa1\x05\xa2\xf4h1\xc6H\xc0\xf3J^\xe4\x8d\ -\x0e\xd0\xc9\x18\x94\x0fs\xc4\x8au\x81\x13\x1b\x22\xde$\ -\xd5\x82.\xe1\xd5S\x00k\x12F\x8dI\xfc\x0a\x1d\xdc\ -\x14\xe9\xe8\x22\xcb\x99\xe8\xac\xa8\xfcZ\x04\x1cq`\xb2\ -\xcbd\xfa\xa7L\x9c\x95\x05X\xbbXA\x87\x83\x14J\ -*\x91\x9d\x91p\xc7\xa1p\xeb3\xdc\xf3'\xa2\xa0Z\ -\x02\xd3\xc0\xab$+\x88\xa0KG=\x9a\xb0\x06\xe6\x07\ -o\xc3& \x07\x9b\x02'&\xc8\xcf[4lH<\ -Xh\xea\xd18\x91\x98\x11\xe1c\xeb\xc3\xbbN\x10\xc5\ -\x16jm\x0f\xbfv8'x\xa1\x11\xd6kh\xdf\xe9\ -qQ\x88\xba\xa6\xd5\xaa\x03\xcb\x0c\x9c\xc2z\x10\x0f\x8e\ -p\xd5A\x1d\x09\xa5\x03a\xe8\xeb=\x08\x13\x15>y\ -\xb9\xaf\xa1%\x0a\xcc;@=z\xf0\xe0y\xab\xe9\xc5\ -\xd60\xc0\xd8Rm\xbb\xa0\xb5\x8e\xb2\x83\xdd\xcd\xa1]\ -\x9d\xa1m\xebE]&\x8dh\xa8\x0c{\xb9\xc3\xe5\xb0\ -\xb1\xbe\xcc\xa3\xd8\x9a{\xe0\x8f\x8c\xcd\x06\x18\xf8\x91\xda\ -\xf1d\xf80F\x0f\xb5nJ;&PjW\xacA\ --\x1a\xc7\x83\xd4\xb2+\x8f0>\xd8\xb3#\xfcrg\ -\xa8\xe99 \x99k\xef\x8a\x9bj3#\xdb:\xf41\ -\xabf\xf3N\xae\xb7\xe1*1LAw\xca|\x97\x00\ -\x9e+O\xdcX\xb3\xd5X\x89/\x13\x07\xeeT4\x0f\ -p\x00w%\x1d\x1eji\xa5\xd6:F\xdfb\xc5Z\ -\xbf\x93\x8e!\x07\x8dG\xf9\xe0\x95\xf8\xf0R\xe4_\xaf\ -d^\xbb\xf0\xebq\x8c\x0f\x86)\x06Y\x0a^8\x9b\ -3>\x7f\x95\xcff\x1a\x9f\xbf\x12\xff\xef\xf2\xd9\xc6\xc5\ -A\xc8dlu%\xcf\xcb/[\x8cJArb\xcf\ -V\xb2\xcaY\xe0\x89q\x8dM\xca\xe5\x19\x03\xa7r\xc9\ -\xbbt\x96<\xd7\xb1\xf7\x9aa\xc6\xa5Fb\xc0\x1c\xcb\ -l \xe21`\x03\xf9M\x84\xd2n\x0d\x96\xe1X\xea\ -\xb3\xd6\xcf\xb3y\xdc\x18\xfeH\x0c\xd5\x85U\x94\x03\x8b\ -\xad\x8b\xba\xa1X\xf1K\x96\x8bk:\xd8\xf7\xfc\x91P\ -\xdbq\xd6\xfa:}\x84`\x8f\xaf\xd4iuy\xaa\xa8\ -w\xc0;suX\xa2\x088\xdeo)b|\xb2\xe7\ -\xba\x88\xb8pG\xce\x17E\x014\x9b:\xd0\x02M\x82\ -\xa3\xeb\xa6\xe0\xbdX\xa5V\xcb\x1cea?\xc4[\x98\ -\x1a\xc6\xb5\xc9\xe2\x91\xb6\x1d\x8f\x9c\xeb\xe4.=\x1d\xde\ -\xd7\xcd\xb5n\x8b\x82y\x8a\xe8\xbc+q\x99\xb0E'\ -[\x16\xc1\xd5\xe6\x15\xaeS.\x93\x80\x8d\xa0\x02}\xcb\ -\x9d\xda\xc3\xb5\x9f\x88\xca\xb0Et\x84\x95:\x0ck;\ -\x1e\x17\xc3\xc6\x8bd\x0b,\x93\xc8&\xe6\xd3\x01L\xf1\ -\xc2'\xf5\x8cK\x22p\x85w\x16N\xf6D\x01\x9f\x09\ ->\x0d\xcfg7\xd9\xcc1\xfb\xe0S\xc7\xb8\x1d[\xa2\ -\xf4T\xae\x0cf\x9c\x12!te\x08\x03\x8d\x02.\x1d\ -\xcd\x11v\x85\x8c\xef\x8a\xb7}Qz=,rg>\ -\x90\xb3\x08\xa4T\xa9\x5c\xc8~\xd4\x8am\x9b\x87\xfaW\ -\xc4\xeb\x22\xde\x97\x0d[1\x9f8\x8c\xd5X\xab\xd7\xca\ -v\xc59\xf3\xcb\x17-\xa4:qwW\xa8\xb9#\xd8\ -}\x8a\xdb\xd6\xc8\xb5\xbe\xcc\x9b\xdeS/\x86\x0fu\xc0\ -n\x8bk\xe8\xc5a\x839>\x9f\xc7\xb7\xc0_\x05k\ -\xd2\xedwY\x81`L\xea\xfd?8\xa9+\xe1U\xfd\ -U\x93\xd9\xb7\xab#\x1a\x99\x0d\xdf\x8bL\xabH\x16\xbb\ -\xdf\xb7b\x9b\x19\x9d\xdb\xfc2\xaf\xe7\xed\xab8\x9cB\ -\xdd\xe1\x00\xa7\xc3\xaf^\xe0\xf6o\xbd\x8aq-\x89c\ -C\x99\x8cE \x1a\xe6b\xccXi[B\ -\xbc\xb8*l\x18\xd9m\x87\x93\xf8[\x22\x1f\xe4\x1e}\ -6\x89\x98\x90\xa8\x0a\xb0\x186\x1e\xc1\xcb\x87B\xeb;\ -\xa3\xa1.\x88\x13u\x00/\x12\x173X\x0dt\xb0\x9f\ -\xe8;\xbc'\x1a\xba1t7\xc1\xf2\xd0C}s\x91\ -\xd3\x0c\x8a\xd3\x87B\xed\x9b\xb9\x96\xce\xc8\xf1\x17\x81\x9f\ -\xbc\xe8`\x96FC\xa1\xdcF\x0a\xa2R\x897\x0cn\ -FD\xb2\x08\xf8\xd0\xe6K\x5c\x13\xe3r\xeb\x01\x1eK\ -iWF\xc9VS\x0f\x9c2Q\x04?\x92\x80'\x0c\ -\x02\x0c0\x0c\xd8!\x8a\xe2\x1f\x0d\xd5l\x1d\xb7\xbeB\ -Tb\xc4\x1bu\x89\x01\xba(bAg\x08/\x07\x22\ -\x10\x92\x8b\xafG\x80\x19\xbbfj\xd9g\x22\xa5\x92[\ -I&'6\xd0\xf1\xa0\xdc\x88\x8a\x12b\xc5J\xbd\xa1\ -\x06\xce{\xd8\xa1W7(\xb7&z(\xa2\xeeI2\ -\xeer\x04\xf8\x189\x13A\xb4\xc38y\xd6\xd6c\xe1\ -\xe0m\x81\x15[\x0a\x81\xe5\xc4\xbd\x0a\x88K\x22 j\ -[^KT\xe0h\x88E\x8cq\xba\x88+\xa6\x18\xd8\ -L\x91\x96\x09\xa1=\x9d\x5c\xeb1P\xb6}\x0d\x0d\xdc\ -\xa6\x1e23\xc1\xee\x9d\x91K;,0D\xaey7\ -\x19\xe8\x95\xde\x03:\x04\x17`\xc7\xb6\x96z\xa38$\ -\xcd\xacS\xbf\x18\xae\x0d`\xa9g\xd3\x16\x83\xb9\xc6W\ -\x89\xa8\xaf-X\xd7w\x8c.Y\x1eMz\x99\xc7f\ -u-\xb0\x06l\xd5\xd4\xc5Y\x80\xe5\xc8\x86\xaa\x99\xa4\ -\xc7\xb6\xf3}\x8d\xcdZ\x8f/\x94\x1e\x17\xb9c\xc3A\ -\x15\xaf\xe8\xdc\xdbq\x90\xdb)r\xd6\x83\xe3:\x12\xf9\ -\x0ej\xa3\xedl<\x8c\xa0\xe4:\xb6\x85\x9aZs#\ -\x0dm\xa1\xb6\xb3@z\xf8\xf8\xb3ZT,A\xb3\xf0\ -\xc0\xa5\x98\xbd\x09\xef}m8\xe0\xee\x13c\xfb\xd5\xe2\ -\xb2\xa9\xc1\xcfV\x894\x93>~\xbc\xa2\xe5Nf6\ -\xd7\xda\xc4\x9d?\x1bjz.x\xf9\x05p\xa9\x81\x10\ -\x98=p\xfa\xb4\x08Y\x81\x83\xd2c\xb2\xd5:L@\x9e\xa9o}3\ -\xb7\xfd\x19\xaa\x8a\xf1r\xcf\xcf\xcf\xd7\x22q\x0c\x9f\x87\x1d\x90\xe5npx,|agh\x7f\ -\xbb\x16\x98\x19\xd8c\xd7\x06#$\xb0\x04\xd2\xa5\xd4\x8e\ -Zwd\xb3\x88\x08\xaf$\x0d\xa2\x16\xfa2!V\xc2\ -\xe8\x0e\x1c\x17\x89M\xa3\xa4\x14\xc8\xac*\x95\x05\xab\x0e\ -\xe4s8\x12\xfcB\xd8\xa1\x11Ja\x12Es\x0aZ\ -@g\xf6\xee\xc0j\xd4\x8a\xf3+\x04\xbcZTt\x8c\ -\x82\x02\xf9h\x82<\xd2yA2\xe1\x8c\x99\xd0\xc5/\ -D\x04=\x8c\xe7\xe9\xc7\xdb*Ar\x88\x12R\x0a\xbe\ -2\x876\x96\xda\xf7\xf6\x13 \xc2\x04\xb3\x09V\xb8\x09\ -\x0cJh\x7f+1\xca::\xea~\x05\x11~\xd6\xc2\ -3A?\xb2\xe0\x89\x1c\xc9\xad\x8d\x8d\xba\xfb\x15\xa2{\ -)\xf7\x0e\xc6\xb0\xde\x01\xd8n>\xd9\xb7\xf9\x12\x99\x19\ -b\xc4uH[\xacAZ\x8dJC\x0a\xc1\x02\x9f\xc2\ -$\xd6a\xad\xa4M\xd2h\x96\xa4\xf0+\xc7@-\x81\ --\x01\xf3\x18\x95\x9e\xa5t\xa7$d\xf3\xe4\xe1\x037\ -\xa3r\xb4eFb&\xb3\xba\x954E\xce\x9e\xe1\x8e\ -\x1c\x00:\x88\xe8\x80\xa3\x13\xec\xd9N\xa4Z\x8b\xa6\x81\ -@\x93\x13k`\x13\xc6\x1f`6\xf4g\x1bA\xbfj\ -u\x1a\x89#PN\xac-\xbd\xea<\xaa\xa4\xaa\x22\xe7\ -\xceD.o\x92e\x1f\xd4\xe6\x22\x17\xa3\xaeT\xc8\x88\ -`\x92\xeah\xe2\x83\x95)\x22\xd4kQ!~B\xa5\ -\xd5\xa5 \xf2\x95\xdem\x5c\xe3Y\xd8\x06\x12K\x0b\xf2\ -\xbd\xb6p]\xb0\xfb%Q\xca\xe1+2\x89Z\xa4M\ -F\xcb\xb0\x8a\x13\xd66\xd1\xc7\xfc@=4\x9b\xc4\x06\ -)\xc9\x1c\x8d\x96\xf6 \xd4D\x9d8\x04\xfb\x1e\x12\xd6\ - \xd1\x89+\xbdx\xf73P\xc2\xc6\x84\xfaF\xa2\x12\ -\xbc\x1cl\x18\xb2J^%\xddMra\x923\xa0\x0d\ -n8N\x86Zi\xaa@\x1fXo\x03w|k,\ -`3\xc4\x9d>\xab\xe7\xa4`U\xd4\xb0\x18\xcb\x19V\ -\xc3qG\xce\x8b\xe1\xb1\xa8\x9c\xf9~\xb3\x91\xcdF\xa8\ -\xb9\xd6\xe68\xa6\x22[6\x15D]\xc8\xd5\xb6\x91\xa8\ -m2\x15TIk\xc3\x8a#j\xa6\xdd\xb14jX\ -\xdcJ}\x5c\xf2}\x80&\xc4\x84\x19\xaa\xc5\x1a\xc2\xc1\ -r\x1e\x9cx\x22\x8a\x91Mg\xb8\xae\x1d\xc1\xee\x06X\ -\x0d\xa1\x9e\xd6\xa8\x8c\xb8\xef\x1ai\x90\xaby\x17ff\ -\x15PE\xf2\x87\x12Uc\xd4T\x85\x8f7\x93\x09\x8b\ -t\x9e\x8a\x9ch\x88J\xde\xb5\xc6\x15\xb5\xa1\x15(\xa0\ -\x09\xe6\xa1\x06\x87s\x04\x87V\x0b\xc7`\xba\xc8w\xb7\ -s\x1b[\xe2\xc0\x14#\x80\xa9\xdd\xa9\x01g\x16y\x8f\ -\xa3\x92V\x91H\xd6\xc1\xaf\xe1K\xe6\xd2\x18\x9b\xb8\x0d\ -\x11\x95\xad\xa0`\xd9hV\x00\xc7\x86\xfd2\xebW\xc3\ -\xe0c\x9d\xba\x9c\xe0\xe5\x8e\xd0\xae\xf3\xc4\x1b\xcd5\xc1\ -\xa6\x86|\x10\xecy\x06\xfb\x0e\xa1\x17\x0eI1\x82A\ -x0\xe5\x1ew\x15\xd9B\x95\xce\xc6\xa3\xc9\x91%\x1e\ -\xec:cp1\x91j\x03MA`\xacQ \x0f\x87\ -\xad\xbb\x15\xd7\x1c\xd5b3%\xf3\xb3\x19hG`\x9a\ -.\xc6\x84\x96\x8b==?\xe6\xa2/\x16\xcf\x19\xffz\ -\xfda\xeeHsL\xe8\x99r(\x8c\xd7\x1b\xab\xe7\x1a\ -PQd\xe5\xb9\xa3\xf3\xef\x07\x90\x03\x1b\x8bI\x92\x82\ -Pw#\xb8\xf3\x8c\x14\x90\x0f\xaeF\x0a\xd2qA\x1b\ -\xde\xc3Kc'\x9bq\xbeW\xb6\xaa\xd7\x02\xab\xdbZ\ -\xe5 \x81\x06\x03+B\xcc.\x19\x8bI<\xdd\x199\ -a\xbac\xb9\x88\xdd}\xbcB\xcc\xd8\x9d%I\xa1>\ -F\x8d\xa5\x94,\xbd\x9e\x0a\x9d\x90,}\xcc\xefq\x8f\ -\x8b\x8d*S&\x83\xccP\x89\x94\xc5\x06\x90\x8bi\x0d\ -P\xff\x8a\xff\x9f(d\xa1DL\x08\xb2\xf3\xe0\xeb\xd9\ -t\xf8\xf9\xbeM-\x5c\xcb\x9e\xd0\xbe\xd7\xc2\xfb:\xb8\ -\x8b\xbbc\xa3\x8a\x1a\xfd\x89\xdeu \xccg\xadf\xa1\ -D\x0ax\xa3}\xbb/\xc7\x06q=\xacv\xfdpi\ -\x1a\xf1>\xc0\x07\x88\x0d)\x8b\x81\xb4\x98d\xd1\x98\xd5\ -\x8d\xe1\x90\xb4YlP\xff\x15\xe55\xd11\x0eQ\xe0\ -\xd1\x1a\x00oa\xe3\x1c\xc0dE8\x858D\xea\x92\ -\xc9:\x1c\x00\x08ho\xaf\xc9\xe6\x1f\xbcnR<\x19\ ->\xd1\xcc5\ -a\xa1\xf6\x97\xc2\x07\x8eJ\xebYM\xd2\xd4\xab$I\ -\xbd\xeeF\xea\xa1\x8f\xb6\xfax\xf7\xb5B\x19<\x03k\ -\x13\xec\xd9\x8e\xa3J\x0b\xacN\xf72\xa7\xdb\xee\xa9\x13\ -\x0b}4\x1c\x01\xc6,\x93J@17\xa9\x04``\ -#\xde\x8ca\x92b|*\x8cl\xf5a\x8d*\xa5d\ - \xd1u&\xb1\xa4\x000\x10c\x94\xe2\xe8\xcc\x16%\ -\xd2q\x89*ge\xa7\x02\xdd\x22\x85J\xcdZ\x026\ -r\xc6\x96(0\xd5\x03B\x18`\x91\xd7\xe1&jW\ -\x16\xae)\x17cB\xec9\x12i\xe5\x19i\xceD\x98\ -Q}XC\x98\xf8\x92fQ \x8d\xed\x89s\xa4\x0d\ -&\x8b8\x0f\x9a\xb1I\x03\xd7x\xa2o\xe3\x09\xc9R\ -j\x83X(\xc6\xdc\xa4\xfc\x99[#e[-\xd2\xcb\ -[-\x07\x8d\xfa{\xb0\x83\xe4\x10y/\xf1x4w\ -q'\xb7\x99\x06Y\xc8\x91\xa0\xc8\xc5WB\xcd\x87#\ -\x977\xc1\xde-*\xa7Y\x09\xbeS+\x0fM$X\ -J\xf1\xe8M\x98\x04Fg\xc2X@Q'\xec\x07(\ -\xf0\xd5\x06\x1cu'\xa3\xdd%\x87)\x96\x89/ \x9a\ -%\x04\xa3\x15qPM\xf0l\xc5\xaeTQ%H\x85\ -\xd6\xf9\x1fQ\xff+b\xb0\xfc\xcc?\xd3\xc8u\xf0\x01\ -\xd8\xc8\xc6\x8bD\xdbG\xc5=L\xb1\xa5`\xb6\xbbL\ -\xa1\x22\x13i5\xc57\x8e\xff\xda\xcacm\xfc\xf1\x84\ -du\xe7Wmf\x84\xf3x\x12S$9\x1eL\x13\ -{4&\x18S\x9a\xc7P\xb7Xv\x82O\x7f\x95\x90\ -\x85\x1b\x07\xdcX\x11G\xcdN\x03\xc1tK\x9a\x95\xcd\ -D\xa7\xe7\xca\x92\xd0\x86r\x87\xd5'\x0bV.Q\xe4\ -\x8f\x85\xac\xac\x9dz\x00\xfcJ\xb5j\xd6\xad\x91S\x9b\ -\xd2.G*\xcd\xab\xc5\x8as\xad\x88\xb4\x1c\xd1\x8d\x14\ -\x11\xd9y4u\xf1g\x09\x1b\xcfH\x85\xb4\xda\x18G\ -R\x8c5$\x83\xab[\x97+\x0cc\xdfk\xdc\x91\xbd\ -\xb1\x80^\xcdm\x15zr\x19;S\xa5\xdds*\xf5\ -\x0b$\xaf(\xba\xd5U\xc7{\xe9\x99\x08\xd6t2\xfb\ -QM\xa4\xf3\x15\xf6\xd2A\xbd@\x9f\xcc\x80\xb3\xdbN\ -r\xfa\xaa\x0es\xdd\xaei)\xb3\xc1RF.\xed\xe4\ -\x1a\x8f\xd2)\x05\x1f\x7fWGT\xf1\x89\xeb*\x0e\xbd\ -\xc5q\x15wp\xe8\x19\xb0\xe8;\xf9kW\xfa\x19\xe5\ -\x8e@\xc0\xe9\xae\x92\x19\xef\x12\x9c\x19\x13xK\x8c7\ -s45\xdc\xbb\x13o\x12\x8es]-E:@\xe2\ -\xbf\x89I\x93\x82\xb8/a\xd2\xe8\xad,\xe9\xe2S\xa4\ -\x95\xf8\xca\x09W\x0c\xff&\x9b\x96t]\xd8\x13\xe98\ -\xa2\xf4r40\x0dD\xf7\xc0\x1f\x9f\xc8W\x96\xc2\xa6\ -\xdes\x8fT\x03\x9bR\xee\xa9\xf5\xd9\x1c\xf1\x94\xbb\xb3\ -\x8f\x8e\xc5g\xaa\x030\xf5\x82\xec\x970\xb7/\xa8q\ -\x1a\xc3\x17w\x84\xf6\xec\x0c\xf6\xec\xd0F\xcd\x87\xa7\xab\ -\xf0\x0dT\x0e\xf8_n\xc3S\x22\xeb\xf7r\xadMj\ -j\xef\x064w\xf1H\x99\x9co\xe3\xb9\xf0\xd66m\ -\x1cf\xf1\x1c\xb8:\x98\xa5\xd4\xaaF\xf0\x1b\xc2\xdb\xbb\ -d\x97R\x19\xc8\xa5M\xcc\x09\xde#8'/\x1d\xca\ -V\xc2U\xc7E\xa4\xd2\xe1||\x80>\xda9\xa3*\ -\xfc?y}\x17\xce\xde\xcd\x85\xe7*\x98\xf4\xba2t\ -%\xe8\x1c\xf6.+\xe9\xa4\xd1$\xc5\x98\xf8\xfb\x94\xf0\ -\xd9#ns3w\xa9\xe1.g`nm\x05\x0d\x5c\ -\xe1\x1b\xac\xe8\x89#\xf5\xb8_\x137w\xc2\x08\xedt\ -\xdbe\x133+\xd6(|!\x99\xcf||)\x8d\x07\ -\xcb\xbe\x9d\xe1\x03\xaf\x81V\xca\x0e\x1e\xfe\xb0|\xb8\x0d\ -v\x9a\x0aV\xac-\x5c\x17\x93\x1b3\xf1B#\xbc\xb8\ -GbF\xe2'\xa8\x18\xd0\x05@\x9f\xb8\xd0\x0c\xfc\x9a\ -D_-\xf8]\x05.Y\xac\xc7\xaf\xbdt\xfb\xc4\xab\ -=7\x9a\x0e\x02icL\x86\x19?\xc3\xfb\xf0\xa4\xa8\ -\x987\x19\xfc3E\x00I\xb8s\xc1B\xbf\xb7`;\ -\xc63\xd3\x82\x87\xcd\xb3(\x1f\xe0\xf3\x96\xcfL\x8f/\ -\xdb1\x1bx\x08\xb7\xc2d\x99\xf1d90n\x9eZ\ -\xf6s?\x0e\xc8\xf3J\x89\xffTH\xd2\x081\x87<\ -\x86\xe6\x22Z}j\x87?\xfe\xf9i\xf0<\x19\xa7\x1f\ -+,\xb7\x0a\x1a\x11y~\x8cy\x92\xbaF\x93\xc0^\ -\xf3\xa1\x91ix\xd4\xc2\xe8\x8aqP\x8f\xc0\x99\x81\xdf\ -yu\xf8\xe6\x13\xc7a\xbe\x06\xecS\x18\xdc'\xb18\ -\xd6S#\x0d\x22\xed\xda\xbc\xff\x97\x05\xee\x1d\xef_\x05\ -5\xeaY\xbc:z\x0a\xc1\x01\x94(\x9aEC\x87V\ -Z<.\xe4\x8f\x96\xe2Sa.&\xc2S\xaa\x10|\ -\xc1\xf3Qx\x03\x0a\xafUo\x5c\x16\x99\xacZ$\x99\ -\x16\x16N\xc2c\xc5\xd5\x81\x1a\xd7\x8c\xe2j\x87\xd5n\ -\x99Q\x5c\xe1\xb1\xd7\xcf(\xf6\x9a\xac.g\x95{\xba\ -\xd9\xe6p\x07\x1c>\xf3\x8cbgM\x95\xc9\xef\xb3M\ -7\x17Y\xf8\xcb?,\xf0\xde\xe2\xb4y\xdc\x16X1\ -\x9e|\xaf\xbb\xcal\xaas\xda\x03\xd5\xd3\xcd\xb7\x16\x98\ -M\xd5\x0egUu\x80\xbc\xf6\x07\xea]\x8e\xe9\xe6\xd5\ -\x0e_\xc0i\xb3\xba\xf20\xe4\x22S\x8d\xd3nw9\ -\xa6\x99\x01\xa7\xdfku\x0b\x8f\x99*=\xee@\x9e\xdf\ -\xf9\xb8\xa3h\xd2To`\x9ay\x06\x8f\xae\xd8\xc2?\ -3\xa3\xd8\xe2eh\xf3\xf18\xcc\xba\xdd\xa7\xe0\xde\xff\ -p\xd2\xb4\xb6\x1e\x9c\x95u\xa6\xd0\xe9\xcd`g?_\ -\xdf\xd1\xb7i\x87&\xc5\x87\xbbq\x15G\x14tw0'\x98\ -\x95\x9b\xc7\x00=\xf3/%\xf75\x0e\xe6J\xb1O\xfe\ -\xfe0!\xc3/\x0d\x16\x97\xb8H\xf2\xa3\x22 U\xd8\ -\x0c0aA\xec\xf6k>]\xa0\xc8Y\x99\xb0\x8e\xe7\ -\xd7\xb2x\x5cB\xe7\xacH\xb6\x98\xd3\x0am\xdb\xc4_\ -/\xc5\x1e\x17Q\xe1\x19\xab\x81G\xe3\x00\x96\x16t\xf9\ -I+M\xe8\xf3U\xd0u\xcf\xba\x88y\x06\xe5iO\ -%\x05\xe3\xf5(\xe0\x0bM\xd5\x07dT4\xc5\xb5U\ -U\xf5\x1a\x0f[#7\xadk\xb7Q\xee\x93\xb5\x99\x03\ -\xdf\x943\x16y\x1cs\x1a\xff\xf0~\xae\xb7\x87\xdb\xbf\ -\x95_\x98s\xcaq\xfe{\x9c\x8e\xbe\xb8\xa6\xc3t\x9a\ -RN\x12:54\xa6\xaf>\x95j\xc7{2\x1f\xd6\ -\xd1\xbcb)@O\x82\xcbR\x00\x7f\xe3\x98\x9d\xf7\x0d\ -\xa1\xf6\x86\xe0\xc5\xed\xf4|~\xcf\xb1\x9c\x82'\x0b\x0b\ -\x0a\xc6\x15i\xa2\xbd\xea20\x15\x04\x83\xa6\x8aN\x89\ -\xb2\x9e\xc6jf\x06\x85A\x0aG\xf7\xa4\x01]'\xcb\ -\x06\xe2\xa3\x93\xda\x831\xebT\xa2\x88\x0a\x8f\x01:P\ -q\xf6O\x0f\x22\x13\x9e\x15\x05];9*\xd4\xb8\xe0\ -\xd0m\x91\xaeq\xd1\xb9\x8bG\x03\x10V\x8cz\xd3\xc6\ -\xd6\xfb\xd0\x9bn\xd4 \xc8\xe56\xda \xe2\x8f\xbdh\ -`\x8f;\xf2\xa2a\x18u\x0f\xe6i\xc4Z\x06+\x8e\ -\xe8I\x81\x16M\xab\xa4\x07X2\x15\x84pa\x1b\xcc\ -\x5c\x0e$\xd6\xbe\x93{\x1a\xc4\x22C=e\xa4\xebP\ -x\xbc\x0e7\xeby\x5c\xa3C\xa1\x04\xf3=\x1d\x0a%\ -8]\x87B\xf9\xe0\x10\xc6\x10\xc4U\xf5\xa8\x82\x90\x89\ -\x16\xc1dTR\x87\x80)\xe1\xe0\xd6\xb7\x84\x8f_\xe8\ -\xdbDe\x5c\xd9/>u\xaf\xec\xf5\x03\xab{%x\ -\x93\xac\xa6PC\xd9\xa8*\x0a\x195\xa3CpY\xd4\ -#\xb1Q\x9679\x07\xab\xcd\xbd\xabX\xde\xea)\x8f\ -\x7fy\xab\xa5\xbdDV`\xaa}\xdf\x98\x81T\xa7I\ -w\x8c\xa9\xe0\x98\x15p\x1c\xcc\xbd\x11J}\x99E\xa0\ -\x89\xbe$-I\xd1\x10(\xe9z}\xc5\xa1\xca\xc8\xe5\ -}`)\xf9^\x99^\x9f\xa7\xca\xe7\xf0\xfb\x99(\x9b\ -\xeeZ\xf7\xe3\x88\xfa\xf2\x0a!\x98~\x8dk]\x09\xe6\ -{\xaeu%\xb88\x03\xd0\xaa~\xba:B\xf9`\x8c\ -\xcc\xb9\xea\xf9x5\x82\xb2_\xaa\xe0b2\x9a\x00v\ -~$q\xa91\xecx\xf4\x87\xb2\xd7\x0f\xac?\x94\xe0\ -cf\x985d\xea\x9a\xae\xedQ\xc1\x893e\xac\x89\ -\x9f\xf4\xb3(4\x9e\x93\x0d\xd3\x13\x97\xdeB\x14\x93\xd3\ -\xae\xbb<\xae\xc9\xfdQ\x02\xca\x89Wg\xaa\xce5b\ -\x8d\x19\x17q\xb2\xac\xa5\x12\x0e\xc9Mj\xc2\xf9!\x12\ -2\x1a\x93\x17\xb76Ww\x8d+\xa1\xa3\xeaw5V\ -@\xcd\xcb9L\x02V\x9ej\xf535\xca8\xbb*\ -\xcb\xabj\xac\xf9\xd9\x8aX\x14(\xeb]\xdc\xbe\xfd:\ -\x9ah%\xbe/'j*J\xd9+\xed3T\x8cF\ -\xa3R\xd0J%h)z\x00-\x06\xf2Mh.\xbc\ -^\x80\xab\x83\x17\xa3{\x81\x8a24\x0f\xebd3\xca\ -\x83\xa5`A\xcb\xd0$\x9c\x08\xb0@\xcf\xa5\xf0\x8f\xed\ -1\x19\xe5\xc3\xae\x8c\xffn\x0e\x88\x83\x19\xf7\xaa\xc6\xe1\ -\x10/N#X\x80\x1d\xfc_>\xfc\x9b\x04\xff{\xf0\ -\x19@\x0b\xf4_\x02\xff/\x81^%\x80E\x08\xfcN\ -\xc6\xb0\xc8\x0d\xffN\xec\xde\xe6\xe3\xa4\x82\x1d\x87\xa9\xd3\ -u\xd2\x1e\xfc\xeb\x1a,\x8aVf\x07\xc4\x07\xbfWQ\ -8\xd5\xe2\x9d\xf0fU\xed$\xff\x5c!\xfe\xdc\xa2\x80\ -D\x0e\x89\xfb\xa8\xf6\xe6\x9f\xe3\x0b$+\x81\xde\xa9\xcc\ -\xf3R\xb8]r`\xf8g\x05\x8c\x16\xbc\xcc\xfct\x04\ -^\xf1\xb4\x1a\x9f\x04\x10\x0e#\xf2\xcf\xe6\xd1\x10\xb9p\ -\x9a\x8f,\xcb\ -\x17\x128d\xb6\xe5OT\xe0\x02!R\xc8\x1b\xed9\ -\x17\xae\x03\xe0i-\xd0y\xc2\xc7\x8cGz&\x0fV\ -U\x00\xe3!\x01\x95\x95\xf0\x9a\xfc\xcc\x870\x8fd\xdc\ -\x82\xfc\xab\xbf%\x90\xcc\xcc\x8dJd\x0b\xeb\xc2\xc9%\ -\xa9\x98\xc6EWC\xb5b\x83k\x15O\xd2*\xee<\ -\x05\xe8Br\xe9\xbf#7\xaf6\x09\xc8&D\xa5\xaa\ -\xb7\xa5\xb2\x13\xc37\x83\x99w\xe1\xa7\xa4\xd3\xbcc\xe1\ -9\x22\xfdL\xe9\x94\xe2i+\xf3\x8d\xf0\xbcv\x1a\xf0\ -\x7ff\xea\xeafj\x16S\x0d\xad7WR\xa6I\x9a\ -\xabr}\xe7V\xd1\x9b\xff\xe4\x7f\xe6\xec\x87\x9c\xb32\ -\x95\xdb\xbeT\x08u\xc88/\xb8\xf7\xfe\x18\xfc\x8f#\ -u\xde\xef\xc3\xe2\xd1\xa5\x8bJ\x96>\xb0x\x8ei\xee\ -\xd2\x05e\xa6\xc5\xf7\xce.\x9bWb2\xe7Y,\xcb\ -&\x95X,\xa5KK\xc9\x17\x93\xf3\x0b,\x969\x0b\ -\xcd&su \xe0-\xb2X\xea\xea\xea\xf2\xeb&\xe5\ -{|U\x96\xa5K,K\xe6\x94\xe4\xf1\x09\xe2\xc9\x05\ -\x16\x7f\x00\xb6{\x81|{\xc0n\x9e\x91\xced\xe6g\ -\x14\xd78\x02V\x93\xdbZ\xe3\x98n^\x05\xcfT\x07\ -\x1ck\x02f\x93\xcd\xe3\x0e8\xdc\x81\xe9\xe6B\xb3\xc9\ -B\x1f\xb2U[}~\x07|V\x1b\xa8\xcc\x9b\x8a?\ -\xc7\xc9lS\xa0\xde\x0b\xbd\xf9\x8e\x16\x9b\xdf\x0f\x08\xbc\ -\xb9&\x97\xd3\xb4\xd6TW\xed\x0c8\xf2\xfc^\xab\xcd\ -Qd\xf2\xfa\x1cyu>\xabw\x9ai]z\xb5\x0f\ -\xbe%\xc9\xfc\x22S\xa1w\xcd4S\x85\xc7gw\xf8\ -\xf2p\xae\xbf\xc8T\xc0?\xe4r\xe6\xd7\xbam\xfc\x0f\ -\xe09\xecEE5V\xdfJ\x07\xdf\x8d\x92Vd2\ -?\x1a\xeb\xc1\x89\xf8\xc1b\x0b\ -&\x99\xcf\x9c\xe3\xd1\xf3ityJ\xbe\xd2Z\xe3t\ -\xd5\x17\xdd\xb2\xc0i\xf3y\xfc\x9e\xca\x80\xe9\x01\xeb\x5c\ -\x87\xd3t\xef\xbc[\xa61I\xfb\xdb\xbd\x01\xfa\xb6\x8e\ -\x0ccrA\x81\xf0=\x0f\xae\xc8\xed\xf1\xd5X]\xd3\ -xv{E\x0c@[\x95\xd3\x9d\x17\xf0x\xf9\xac?\ -\x0c\x9b~P\xe1\x09\x04<5\xf2\xcf\x5c\x8e\xca@Q\ -\x01\xf3\x01\xaeK \x9f\xe4\xad\x0a\xe4U\xb8<\xb6\x95\ -yN\xb7\x9d\x1f#\xe0\xe6\xf9/\xbe\x85\x87\xcc3\x82\ -\x17\x8e\x06\xbb\xb7r\xcd\x07\x83\x17\x9b\xf9\xcb>\xf1\xf5\ -\xe1|\xf9\xc0\xff=\x92\xf4j HI\x06\xbda\xc2\ -t\xf3XW`Z\xc0\x19p9\xc6V\x01\x9b\xc5\x8a\ -^\xfa\x8d\xd5\x17p\xfa\x03\xfcWLI\xc4\xff\xabc\ -\x221&iXVWEm\x0d\x1e\x96\x22\x1cA\xbf\ -w\xda\xff?\x19\x18\x89S\xd0\xf4\x0d\xa1\xddeuW\ -\xf9\x15\xe4\xabKUR\xfd\xa4\xfeT\xb8\xfc\xa2\x86\x16\ -\x05\x920X\x80-\xdf\x173\xf5B\xb0Q+\xbc?\ -\xb8lI\x09\x8d\xc7t\xbe\x1c>\xfe,H{hw\ -\x97\x1a\x93\x8fF9\xfcL\xc6X*\xbc\x96\x12\xa4\x01\ -\xe6b\xd9J\xe6\x197\xf3\xe3dB\xc0\x93<\xa1\x08\ -\xe2IfI\xba*2\xd8\xbd\x9d\xfc(d\xe4\xd06\ -\x12\xda\x8b\xbc~\x8e;\xbe\x95\x9c\xb8\x09m?\x1a\xb9\ -\xbcCMr\xa5\xaaT\xe2j\x08V\xde\xbc\x10\x9b\xcc\ -\xd1@&\xb9`AM\xa3T\xfe \xa71\x13\xff\xb2\ -\x1aa\x88\xec\xc0)\x7fG-\x09\xbe\xc8;\xd8\xc1\x11\ -\xb2\x8b?\x8fag\x06@\x06UC\x7f\x5cM\xe0\xb1\ -t\xab\x0f\xf9$\x0f\x87\x99\xf3\x98'\x5c\xf4\xa0\xbd\xde\ -\x0f\xb2\x91\xc8\xb3\xcbg\xa3br\xee\x02\xb7\xe5 \x1f\ -\x87|\xe1\x00\xe5\x7f\xd3\x19Z\xb6.'u\x03\xf0?\ -\xca\x85\xc14\x0a \x9cbfS\xc4\x92\x1f\xe7\x8fy\ -\xbf\xbfE;\xc1\xad\xc8\xcfO\xd58\x9bL\xc6ca\ -\x13\xe00 \x8d\xdb\x8b\xe5\xc3\x1a\xc6$\xcc\xf4nH\ -\xa1I3\xe9\x9e\x149\x88t\xb4\x88\xca\x99\x8f-\x14\ -;\x1b\xbc\xf0\x9c\xfaar\xf4\x8a\xcf\x9a\xaf\xa6\xd1F\ -\x0f\x1e\xb2Oy=f\xdf\xe1\xf6\xc8\xe6\xd3j\x00q\ -\xc5\xc9\x95\x04\xdeI];\xe6\x0a#\xeeT[\xf0\xe2\ -\xce\x22-\x8e,\x96~f\x96\xa5\x90M\xc7s\xe7N\ -\x93_d%dj\x80\x19\x02H}\xe2\x8a\xf5\xd1\x82\ -8\x96\x80\xee\xe3\xe1\xed\x9b4z\x16\xc8oI\xa4?\ -\x98\xc6V\xfb\xebMT\x16\xb9G\x91\xd4\xfa\xebM\xd7\ -X&'(\x9d\xc3\xf5\xe8V\xe2\x0c\xa6\x99\xc1\xb6\xa3\ -\xdc\xe56Y\x96Y\x0ew5\xde\xc8x\xb13\xcc\x86\ -\x04\xdc\xf4\x80\x02q\xa9\x95w\xa5\xf3\xe1\x9f\xc7\xf0\x22\ -\x12\xb2f\x15b\x10\xc8\x87\x87\xa9u\xd1y\x9d\xc6o\ -\x92\xe6\x91\xe3rWz\xf7\x92\xa9%g9\xfa\x1a\xf6\ -\xc2\x02\x08m\xdd\x1c\xec\xed&\xbfU\x1a\xe9\xda\x18\xd9\ -\xb2AM?9\x94XE/\x02^\x8d\xb3\xf3\xb2\xdf\ -.\xe3\xc7\x1f>\xb59\xdcs\x5cc\xd2F\xe2\xab\x82\ -\xbc\xb1%'\xd4\xfeR\x0c\xc9Y\x82\xc3\xd1v\x9a<\ -\xa9\xa4w\x8e\xb9iM\x87\xa4\xea$]\xcfF/s\ -D\x03)\xfe\xb0(\x83~L\xa8\xfde\xae\xeb\x12\xd7\ -\xb2!\xdcx\x1c\x1fp\xc4:\xef\xe09\xae\xb7\x85\xff\ -\x8dQ\xb0\x9f\xe34H\xcaW\xfdf\x9fKQ\x13\xa3\ -'\x90\xcc/\xf8\xe9\x09\xe4\x13\xe2\xae\xd6\x1f\xe3W\xa6\ -\xd9s\xc5\xd2op8\xc4\x02\xbe8.\x81\xc3\x1c\xba\ -[\x88\xba3Y1\x0b\xad\xf2\x92_\x0dGN\xbc\x93\ -j\x8b\x1c62?Nk\xdd^\xeb\xf5\x98Y\xea\xeb\ -1\xb5\xe6 V\xb9\x90v\xf6z\xb0\xa2tHJc\ -+%\xb8,\xae95D\x9f\xcd,\xd1\xa4k\xa8v\ -bn\xf5T\xfb\x10\xf1~L\x1f\xd5\x138\x00\xcdt\ -\xefn\xe4/z\xc4G\xb5\xb4\xbbG;\xe3ed\xcf\ -x\xd1\xac\xbb\x1c\xc2p\x05\x04\x1d\x06\x108\xfa\x0c\x88\ -7\xf9%\xef\x97*\xdc\xdd\xc0\xa4\x8e\xb0\xf4\xa9\x1f\xcd\ -\x11\xafyP\x9e\x13do\x13d\xab\xbd\xb2\xd9\x1f\x13\ -\xa6\x02\xc1_\x99\xae\xc1\x84\x19Q\xef\x90`+\xbd\x85\ -\xe5\xa5]\xf1\x9dEQ*\xea\xbe\x95\xc6h\x16\xeeJ\ -~\x17J\xb8\xf9Le7\x99$\xdc\xa6\x97\xfa\x1a6\ -p=\xbb\x88\xf9\xd4\xf2A\xe705\xd9L\xd2:\xb2\ -\xf5UM\xef \xae\x9b\xe1\x94\x92\x16w\xb2Q)\x1b\ -\xca\xdco\x11sZ\x95\xde\x13\xbfW\xdbK\xbb\x96\xfb\ -\xe1\x95~\x90\x98PbN\x5c\x1e\xea\xe1z4\xd4\x81\ -Y\xe5uD\xbbCA\xf3t\xa1\x1c`\xae\x0a`<\ -\xb5C\x83\xa93\xc3\xec0U\x1cj\x82w\x89\x09\x09\ -\xff\x07`\xab\x0a\xf7\ +\x00\x8f\xc7x\xda\xe5=\x09x\x93\xc7\x95c\x83\x8dm\ +d\x0b\x9c\x00\xe6\x16\x82PCl\xcb\x06B\x881&\ +`s\x9b#\x98\x1cMs K\xb2\xad KB\x92\ +1\x0e!5\x87\xc1\x5c\xb6\x09\x10s8\x80\x09g\x12\ +l\x03\x09\x18c\xa0\xed\xf6\xd8\xed\xb6\xcdv\xdb\xfd6\ +=r\xfb\x97\xe46\x09\x9b\xa6I\xb7\xddm\xb3\xef\x9f\ +\x99\xff\xff\xe7\xbf$A\xb2\xdf\xee~\xfb\xf9\x83\xd1\xf1\ +\xcf{o\xde\xbcy\xef\xcd{oF\x05]\xf6\x8c\x1f\ +\xdfj=\xf0\xd3q\xa3n\xaen\xbb\xfe\xf6\x19\x84P\ +\x82\xc3\xfd\xf4\xc3\xa5s\x11J\x99\x81\xd0\xfc-\x08=\ +\xb2\x8a\xb4O\x9d\x22m\xd9^\x84\x1e\x0b \xf4\xe8]\ +\xa4}\xdaNZ[\x032|\x00\xcf\xdc\xff\x06\x1a\xf1\ +\xb7\x1e\xf8\xbe\x00\x8d\xeclF\xc8\x9c\x8dF\xbf\xf7\x13\ +\x84jG\xa0\xf1u\xaf#4w:\xca,\x85\xf7\xeb\ +\xe7\xa1)\x9f\xfc\x13B\xc9\x8b\xd1\x8c\xbbk\x10*\xb6\ +\xa0\x19k{\x11\xba\x97C\x0f|\xe6Ehi6i\ +\xcbo\xa0\x99#W T\xf2&im+P\xb1\xe9\ +]\x84\xac=\xe8!\xf4,B\x83\xefC\xfes\x9d\x08\ +\x8d{\x1f\xadK<\x8c\xd0\x86:T\xfb\xbb\xbf T\ +\x98\x826T6!4b\x1a\xda\xc9?7\xa8?\xda\ +\xb3\xfb\x19\x84\x0a<\xe8\xecJ\xf8\xfey\x0b\xba\xfe`\ +-B9e\xe8\xbd\x8f\xe0\xf3o\xbd\x87>\xb8w1\ +B\xb3\xedq\x83GXa\x1c\xe3\xe2\x86?\x05\xfd\x17\ +\xdd\x88\x9b\x9e\xf88\xc0\xfd<\xee\x99>\xe87e|\ +\xdc\xa1\xb7\xc7\x224\x15\xc5\xbd9\xfcc\x84*\xbe\x17\ +\xf7\xe6\xf1>\xe0\xd3F\xd2>\xf53\xd2\x96u\xc7\xfd\ +\xd3\xf2C\xf0\xf9\xf7I\xfb\xf4\x10\xd2\xda\xe2\xe2\xde\xff\ +\xe4\x84\x06\x04\xfb-\x19\x0d|\ +}\xf0\xde~\xa5\x07a\xbe\x96M\xed\xf7\xc8\xc5\xe7\x11\ +\x9aX\xd3\xaf\xe3\xf0\xef@\xd0R\xfa]\xfc\xa4\x1b\xa1\ +'\xc7\xf7\xfb\xf1(\xe8\x9f\xf5i\xbfPM:\xccc\ +F\x7f\xe3\xaf\x81\xbe\xfc\xd7\xfb\x0f\x99\x0f\xfcJ}\x8d\ +\xb4\x0f\xfcG\xff\xd1\x9f\x9e@h\xe4\xfa\xfe\xf7t~\ +\x86\xd0\xaa\xd6\xfe\x93\x0a\xbfDh\xde\xa5\xfe\xf9\x97\xf3\ +\x11\x9a\xbe\xa4\xff\xd2\xdf\xfc\x15\xa1\xfe\xeb\xfbw=\xf0\ +\x12B\xb9\xa3\xfb\x7f\xef\xbb0\x8e\xc1\xcf\xf7\x7f\xab\xa1\ +\x1d>G\xfd\x7f\xf3\x9d7\x102\xfc\xb1\xff{{\x81\ +\xcf\xf5\x89\xfd\xdf{\x15\xe6w\xf9\xbd\x09q\x99@\xe7\ +\xeaO\x12F\x7fx\x0b\xa1o|\x9c0\xfeI\x90\xdb\ +\xd1\x93\x13&\x0e9\x0a\xf0\xa7&d5.\x87\xcf\x8f\ +%d\xb7\xad\x07y\x5c\x970#\x00r\x9au\x22\xa1\ +`\xda\xdf\x83\xfc^&mVnB\xf1OR\x01\xef\ +\xee\x84\xe2w`m$}\x9e\xb0\xec\xc27\x11z|\ +\x19iW\xcf\x22\xad}c\xc2\x8a\xf4:\x80cNX\ +\xb1\xf9o0O\xa7\x13\xb6\xe4^\x87\xe7\xb7%\xb4\xfd\ +t\x0c\xf0\xdb\x92\xf0\xa3-\xb3\x11\xeaw(\xe1\xc3\xd3\ +K\x11Z<5\xe1O\xb3\xeeEh\xe8\x83\x89\x86x\ +\x80\x1foL\x1cq\xe1\xa7\x08\x0d|$q\xdc\x1fa\ +m\x16\xbf\x96\x98g\x06z\x9e|;\xb1\xb8\xec\x17\xb0\ +\x82w\x93v\xc9\xefH\xfbd9i\xad\xf3H[q\ +\x17i7}\x92X\x93\x9b\x83\xd0\x98\xaa\xc4W\x1d@\ +Gf}\xe2\x8f\xfey2\xcc\xdf\xd0\xc4_\xee}\x1b\ +\xa1\xf1\xc3H\xfb\xf8\xf9\xc4\x7f\xb9\xe0\x01:\xe7'~\ +\xf8a2\xf0\xffo\x89}\xcb\x80\xce\xf1\x1f$\xfe\xfe\ +;\xe3\x10\x1a\xf2\xcf\x89\x1f\xad\x1d\x8e\xd07'\x0eH\ +\x1c\xf6\x1d\x90\xdb\xb9\x03\x8cW\x07#4\xa1b\xc0\xe8\ +\xc7\x80\xce\x8c\xd5\x03\xde:\xf9\x1eBsN\x0fx\xb7\ +\xf42B\xdf\x0e\x0d\xf8h0<\xff\x5cc\xd2\xa0G\ +a\x9e7U$\x8d\xb5l\x83\xf9{'\xc9\xb4q4\ +\x07\ +\xf9\x8d?\x99<\xf8\xd09\x90\xfb\xc4\xe4\xb1\xe1Q\xb0\ +\x9eZ\x93\xc7\xfei\x19Bw-H\xce\xfe\x04\xe8\x18\ +\x90D\xda\x94\xcb\xa4M\xfbk\xf2\xc1?\x82\xbcLy\ +4\xb9\xef\xd6\x22\xc0\x7f#\xf9\x0bo&\x8c\xbb6\xf9\ +/?\x99\x08\xf3\xdc\x9a\x82\xf1%\xde\x9f\x82\x96\x01?\ +6\xb7\xa7\x0c\xdc\x0ar5\xc4\x9a2\xecy\xd0\x8f\xc5\ +\x7fJ\x19\xed\x87y\x9c\xdeB\xda\xa7\xf7\xa4\x8c\xfd9\ +/g\xff\x99\x92\xfbY\x1a\xf0\xe3\x5cJ\xde]\xd0>\ +\xdb\x9a\xb2b#\xac\x8b\x07w\xa7<\xf6\xf4oa\x1c\ +KS\x9c\x9d\xb0\x8e\xee\x1b\x9e\xf2\xcaw\x9f\x86yF\ +\xa4\x8d\x1b\x9cr\xb5\x04\xe4d\xea\xbe\x94\xde\xbfv\x80\ +\x9ct\xa7p\xbf\xe1\xf5\xef\xf1\x94\xdf\x8f0\xc1z\xcd\ +L\xf9\xfc\x03\x90\x0f\xe3\x85\x81\x89\xcb'\x81~\x9a>\ +p\xc0\x97\xb0N\xef=2pT\x1d\xafg;I[\ +9s\xe0\xc4\xd5g\x11\x1av\xdf\xc0\x9c\x0e\xd0\xff\xa6\ +\x86\x81\xa5y\xa0\xe7\x06\xc6\x0f\xf4>\x02\xe3-\xeb?\ +\xb0\xf3\xfe\xfb\x10\x1a\xde6\xf0\xa7\x1f:\xa0-\x19\xf8\ +\x8bo\x81\xdc\x0c;i\x88\x9b\xf8\x0eBw\xff\xd0\x90\ +\xf0\xac\x13\xe6w\xac!\xf1\x9d\xe3 G/\x1b\xee\x1a\ +\xfbmX7\xf3\x0c#>m\x04}\xfb=\xc3\x988\ +\x0e\xa1\xa2~\x86IO-@h\xcbHC\xeed\xd0\ +\xa7[S\x0c\xd5\xa3a}\xcd\x19c\xf8\xf6\x9fw\x82\ +\x9em7\xd4\xa5\xa6\x80\xfe\xe95\xec\x9a\x0ax\x1f\xff\ +\xdcpp\x10\xe8\x89%\xa7\x0cG.\x82\xdc\x16\x85\x0d\ +\xc7-`s\xb6\xbea\xf8\xd7#\xa0\xa7g\xfc\xd2\xf0\ +\xab[ \xc7\x0b\xde2\xbc3o-\xc8\xdd\xefS\xd3\ +\xde\x03{\x10\xf7\x0f\xa4}8>\xf5\x1b\xc9\x17\xc1\x0e\ +p\xa9\x93\x11\xe8\x9f\xbb>I\x9d\xf3\xe6\x1f\x11\xca\xf6\ +\xa6\xce\xb5\x02\x1f\x0a\xe7\xa5.9\xcc\xaf\xa7\xdf\xa6\xfe\ +z\xd8\xfb\xa0g\xa6\xa6\xfe\xda\x0e\xfas\x83!\xb5\xf7\ +\xb3\xab \xd7[S\xff-\x1e\xf4\xd5\x13\x0f\xa4\xfe\xf9\ +q\x90\x97Y\x87R\xbf<_\x00\xf3{\x93\xb4\x8b\xe6\ +\xa6\x0dy\x12\xe6\x7fSf\xda\xbdK@/\xde\xff\x9f\ +iSZ`=\xa7\xdd\x97V\xfa\x8fS@N?N\ +kz\x0a,k\xc1\x9f\xd3\x8e\xbe\x03\xf2\xf0@G\xda\ +\x99\xd2\xef\x83=s\xa4\xbdo\x02\xba\xa7\xa5\x90v\xd5\ +[i\xef\x8f\x07\xf9*<\x9a\xf6\xfe\x8a\xf90\xff\xef\ +\xa6}\xf0\x19\xe8\xe7I\x0di\xa1\x07\xcb@o\x0cO\ +\x0bo\x82\xf58rQZ\xf8\xdd\x10L\xffr\xd2\xce\ +[O\xda%U\xa4}<\x83\xb4\xab\x07\x90\xd6\xbe\x80\ +\xb4\x1b\x96\xa6\xf5=\x14\x06\xf8o\x92\xf6\xe1\x8e\xb4\x8f\ +\x8c0\xee\xc9k\xd3>\xfd\x04\xf4M\xe6\x92\xb4\x7f\xff\ +\x14\xe6#\xd5K\xda\x11\xfdI{\xefh\xd2N{\x96\ +\xb43\xdaH;;\x9e\xb4\xf3\x17\xa4\xfd\xe5\xe2\x07\xf0\ +\xfd\x19\xd2\xce\xce#\xed\xfcgIk\xffm\xda_W\ +W\xc18~d\x8c\xab\x01~\xa6\x8c3\xc6O\x80y\ +\x9a\xb4\x84\xb4\xc55\xa4\xb5]0\xf6+5\x02\xbe\xc7\ +H;\x1f\xde\xdf\x04\xba\xd7\xf7\x19\x07t\x80|\x8e)\ +$\xed\xd2xc\xd2\xdc\x5c\xf8\xfe#c\xb2\x1b\xec\xe3\ +\xd2\xf7\x8d\xc9\xdf\xa9\x84u\xd8l\x1c\xd8\xd8\x00\xef7\ +\x92\xb6b\x99q\xf0\xf7\x9fD(/\xdb\x98\xde\xb8\x10\ +\xd6\xe1D\xe3\xd0v\x90\xdb\x05\xe3I\xebx\xc48\xb4\ +\x13\xf8\xba`\xb5q\xe8%\xb0\xb7\xdfj7\x0e\xfd\x12\ +\xf0m\xa92\x0e\xff\xb7\x0aX\xf7\x8f\x93vq\xb7q\ +\xf8\x1fZ\xe1}+i\x17\x7fn\x1cs\xe4\xbb\xb0\x0e\ +\x7fc\x1c\xf3w O\x96y\xc6qs\xc1o\xc8\xfe\ +\x95qB\x11\xacC\xc3\x0b\xa4\x9d\xb1\xd08i&o\ +\xd7w\x19s\xb6\x1c\x03\xbdRN\xda\x89sIky\ +\x81\xb43\xdd\xa4]2\xc38c\x19\xc8\xcf\xe0\xb7I\ +;!\x87\xb4\xf9\x1bH;7\xce8\x83\xa7\x0f\xfd\x8c\ +\xb4%\xdf \xed\x93m\xa4\xb5\xd6\x91\xb6b\x1bi\xb7\ +\xfc\xdc\x98\xff\x0b\xa0\xef\xbe\xd3\xa4\xddr\xd48k\xaf\ +\x05\xd6M\x83q\xf6\x83\xa0\xa7\xc6\xbdf\x5cd\x01=\ +W\x99j\x5c\xd4\x06\xf2V\xf6\x98\xf1\x8d\x9fm\x05\xbe\ +4\x1a{\x1e\x86uS\xfb\x03\xe3/\xcd\xe0\xd7\x15\x1d\ +0\xbe\xfb9\xac\xf3\xca\xb5\xc6[\xed\xbb\xc1.n&\ +\xad#\xcd\xf8\x85\x15\xec\xe7\x82}\xc6/\xd6\xc1:\x1c\ +o7\xfe\xe9\x10\xc8\xf1\x8a\x19\xa0\x04\xea\xed\xfd\x80R\ +3*A\xc5\xf0W\x84L\xa8\x0aY\x91\x13\xb9\xe1\x95\ +\x17\xf9\x90\x07U\xc0\xffVT\x95\xc4+2\xd0\xda(\ +\xb5\xa4\xb8\xb8\xa8\xb7\xebZ\xe8\xec.\xae\xbbi\x00|\ +2pN\x99\xa7:\xf0\xa8\xd3^\xe1\x08\xc4\xf1\xe0\x06\ +A7\x1f\x00\xa9\xc0\x80\xac\xc8%vN\xe4\x1a\x8f\x05\ +_\xdc\xce\xf7J/\xaat\xd8\xd6\xcc\xf5\xac/q\xfa\ +\xd9\xce\xe9\x18+O\x85\x1b\xba?\x8b\x1c\xc8.vO\ +\x0e]o\xe9;\xf7Z\xdf\xb1K\x91 \x0cE\x01L\ +\xb3\x1b\xf9\x91\x0b\xda\x00\xc0\xf1 \xb7DD\xb8co\ +\x14\x22\xbe\x22C0\xd4\x87\xbdvk\xc0\x81\xc1MC\ +s\x80\x12\x1f\x8c\xc5\x8a\xec\xa8\x16@\x05P%\xbc3\ +Q\xfa\x1c@i\x00\xde\xad\x83W>x-\xa7w\x08\ +w\xe5b\xe8ZS\xf0`G\xb0\xb5.\xf8bgh\ +GC\xb0\xf55MD#\x90\x0d\x03\xb6\xa15\x00\xae\ +\x1a(\xb6\x0b\x08Dp\x86\xe0\xcbu\xc1c\xa7\x82G\ +^\x07X\x9aP\x1e\x81q\x0bP\xf8\x91W\x00\xacr\ +<\xa7\x0a\x98\xf8s\x9e;.~\x9a\xe0]\x0d\xbc\xe6\ +\x87f\xc2\x03\xf1\xe1>\xf9\xf0n\x03\xcaE\x1bE\x0a\ +\xc6\xb2\x14p'/\x84_?u\xabgw\xdf\xfeC\ +\xe1\x8e\x8e\xfc\x0d\xb9\x1b5\x89:\x8aVQ\x9e\xf9y\ +\xee`\xa2\x02\x18\x9d\x0b\x7fZ\x03\x84\xf0\x04\xb9\xe1[\ +\x9e\x97\x1eT\x06-\x99:\x9e\xb8,\xda\x93\xe5}-\ +\x16S\x1b\xeeg\xc3\x12#\xf4.\xc3OXa\xb8\x01\ +*\x8d\x01\xfc$\x083\xee\xa7\x82/\x0e\xee\xbe\xe0\xc5\ +\xb6`\xeb\x8ep\xe3\x15\xae\xe9E\xae\xa13x|{\ +\xdf\x89\x83\xbd7\xdb\x82\x9b:\xb2L\xc1\x96\xe3\xc1K\ +\x07\xc2\xdb_\xe1v\x9e%\x8f\x04\xcf\xef\x0ew\xec\xe1\ +\x87\x9cZ\xec\xf0\xaf\x09x\xbc%\xb5>\xa7\xcd\x8f\x07\ +\x9d\x8bV\x00\x09>LL\x15\xbc\xf2c\x22\xb2\xb0,\ +\xba\xb08\xf9\xf1w\x0e \xe2\x19 \xa9V\x12\x9aP\ +w\x07\xac\x93\xde\xab\xcd\xc0\xdap\xc7\x95\xe0k/\x87\ +\xdfh\xd2AS\x08\xf2\xa9?\xd2\x0a\x00\x1f\xc0\x9f\x12\ +\xae\xb3|\xcb\xe1\xffD\xa4\x19\xea\x81\xf5v\x9d\xcf\xc9\ +\xc9\xd1\xc1\xbb\x0a\xcd\x97\x89O\x00\xcfO\xac\x5c\x97S\ +\xa2\x16\xb3Qjj\x88\xb0\x092\xa6A\xd1\x8b\xa2\x94\ +E\xa2\xc2\x0a\xef\xd6\xc2\xf7N<5v\x95,Y\xe9\ +\x94\x09j\xc3%R\xcc/\x9b\xf5X\xc2xi\xb3\xeb\ +\xc8\x9d\x1d\x9e\xf6\xd3~\xb52\xe9\xcae\xc7\x14:\xbc\ +U`r7L7\xe84\xd0\x0b0\xddD\xc8\x82\x07\ +o\x84Nv\xeb\x8cs\x10\x08\x16O\xe3:\xack\xaa\ +\x19\xf5\x90\xdc\xdb\xb5\xb3\xb7\xab\xae\xef\xf4\x8b|\xd7a\ +\xb2\xaeE\x1ew\xc0\xe7q\xcd\xb5\xfa0\x90$\xb4\x8c\ +\x0c\x87\xed\xbc+\xc6\xce\xe9@\x01\x19\x9f\x05\x95bN\ +{%\x95\x1a|\xe1|p\xff\x0d\x0b\xb7\xa95x\xfe\ +DTPwSP\x84\xc5\xab\x944\x89\xac\x89\x0ah\ +\x04P\xe2\xc0\xd2h\xc3\xb3Q\x22\xcd\xaa\xa4>\xfb\xea\ +v\x04w\x9d\x93\x96mD\x88\x83\xa8N/\xa3R$\ +\x99\x8d\xc4\xbe\xbaf\xee|KT\x08\x19@\x0f\xd11\ +\xbct\xd4bY\xc3\xefe\x90\xc2m\xbb\xa3B\x1a\x8a\ +\x16BO;\x86\xf4(\x86a\x07\xae\xd70#;\xdc\ +\x1cni\x0a\xbd\xd2\xc25\xbd\x1c\x03\xaf\x1e\x06\x08\x84\ +\x965\xf2\x15 BL\xe7\x17\xde\xe5\x06\xba\xf6\xea/\ +\x857\xed\xe7\xe1\x0e\x96\xc1]\xeapWSeW\x0a\ +k\x0f(\x02\x89\x90(-\xc6\x86f\x0d\x91\x10\x9d9\ +\x19Nd\xddB\x06\x10l\xdb\xdd\xf7\xd2\x09i\x82t\ +\xd0\x99Dt&\xb4T\x94\x1e-\xc6\x18\x09x\xde\xc6\ +\x8b\xbc\xd1\x01:\x0d\x83\xf2a\x8e\x10{\xe4\xc4~\x08\ +\xef\x91T\x83.\xe1\x15u\x00k\x12F\xa1K\xfc\x02\ +c\x11n\xef$\xcb\x99h\xef\x88\xfcZ\x0e\x1cq`\ +\xb2Kd\xfa\xa7D\x9c\x95\xa5X\xbbX\xb1\x0e\xf7I\ +\xc6\x81\x9d\x91P{[\xa8y\x1bw\xe8l\x04T+\ +a\x1ax\x95d\x05\x11t\xe9\xa8G\x13\xb6E\xfc\xe0\ +m\xd8\x03\xc8\xc4\x86\xd6\x89\x09\x92L\xb6\x07\x0bM-\ +\x9a$\x123:tzSh\xffY\xa2\xd8\x82\xcd\xad\ +\xa1\xd7Od\xf6^\xab\x87\xf5\x1af\xd5\xec\ +\xd8\xc7\xf5\xd4\xdd&\x86\xe9\x9a\xbe\x87'f\xac\x19j\ +\xac\xc4\xbb\x88\x01w\x12Z\x048\x80\xbb\x92\x0e\x0f6\ +5Sk\x1d\xa5o\x81b\xad\xcf\xa7c\xc8D\x93\xc1\ +\x17[\x8b\x97\x9f\x09\xbf^\xc3\xbcv\xe1\xd7\x93\x18\x17\ +\x1cS\x0c\xb2\xd4{\xedr\xe6\xe4\x9c\xb5>\x9bir\ +\xce\x1a\xfc\xbf\xcbg\x9b\x14\x03!\xd3\xb0\xd5\x95|P\ +\xbfl1*\x05\xc9\x8976\x92UN\x07\x9f\x94\xab\ +oP.\xcf(8\x95K\xde\xa5\xb3\xe4\xb9\xf6\xc3w\ +\x0c3&5\x12\x05\xe6Df\xff\x18\x8b\x01\x1b\xc4\xef\ +!\x95vk\x88\x0c\xc7*\x9f\xb5v\x91\xcd\xe3\xc6\xf0\ +\xc7`\xa8.\xac\xa2\x1cXl]\xd4\x0d\xc5\x8a_\xb2\ +\x5c\x5c\xc3\xf1\xbeC'\x83-gX\xeb\xeb\xf4\x11\x82\ +=\xbeb\xa7\xd5\xe5\xa9\xa0\xde\x01\xef\xcc\xd5`\x89\x22\ +\xe0x\xbf%\x9f\xf1\xc9^\xec$\xe2\xc2\x9d\xbc\x9a\x1f\ +\x01\xd0\x5c\xea@\x0b4\x09\x8e\xae\x9b\x82\xf7b\x95Z\ +)s\x94\x03\xe2\xd6\xce\xca\x1bIfi\x03\xd2\x963\ +\xe1+\x1d\xdc\x8d\xad\xa1#]\x5c\xf3\xee\x08\x98\xa7\x8b\ +\xce\xbb\x12\x97\x09[t\x87l\x83\xc8+\x5c\xa7\x5c&\ +\x01\x1bA\x05\xfa\x96;\x7f\x90k=\x1b\x91a\xcb\xe9\ +\x08\xcbu\x18\xd6r&&\x86M\x16\xc9\x16X&\x91\ +M\xcc\xa7\x03\x98\xe2\x85Oj\x19\x97D\xe0\x0a\xef,\ +\x9c\xeb\x8e\x00>\x0d|\x1a\x9e\xcfn\xb2\x81c\xc2 \ +\xe7Os{wF\xe8\xa9\x5c\x19\xcc8%B\xe8\xca\ +\x10\x06\x1a\x01\x5c\x0a\x9a'\x04\x05\x18\xdf\x15\xef\xfa#\ +\xf4zB\xe4\xceb g9H\xa9R\xb9\x90p\x84\ +\x15\xdb6\x0f\xf5\xaf\x88\xd7E\xbc/\x1b\xb6b>q\ +\x18\xeb\xb0V\xaf\x96\x05E2\x17\x97._Fu\xe2\ +\x81\xce\xe0\x9ev\xd8\xb7r\xbb\xeb\xb9\xe6Wy\xd3{\ +\xfe\xe5P[;\xec\xb6\xb8\xba\x1e\x1c5\x9a\xe7\xf3y\ +|K\xfd\x15\xb0&\xdd~\x97\x15\x08\xc6\xa4>\xf6\xb5\ +\x93\xba\x06^\xd5\xde6\x99}\xfb\xdb#\x91Y\xf7\x95\ +\xc8\xb4\x8ad\xb1\xe1\x1e+\xb6\x99\x91\xb9\xcd/\xf3Z\ +\xde\xbe\x8a\xc3\xc9\xd3\x1d\x0ep:t\xe1\x1awt\xd7\ +m\x8cke\x0c\xe3!\xeb\xa9\x0a\xb7~1\xa2\xc1S\ ++\xdf\xcf+\x19oVR\x1a\xea\xe9\xe6:\xf7r7\ +\xeb\xc3\xe0\xbdF%m\x0a\xde,\xc9\x1dA\xad\xc0\x01\ +q\xb8\xb0O\xad\xe5\xfc\x09\xfb\xfd\xe0\xf1\x1e\xae\xa7)\ +2B\xc1/\x90\x01e\xd4\x9f\x8a\x18fw\x80\x173\ +A\x22\x22\x8f\x84m\x06\xf6g\xc8L;\xf1\xb0\x1cX\ +\x12|x\xbe\xa9\xff,\x9b\x07\xa5$\x8c\xe4c\x16\x07\ +\x1a\xf85\xb6\xf38\xd7\xb1\x8d2\x19\x8b@$\xcc\x05\ +\x98\xb1\xd2\xb6\x84xq$b\xc5n;\x9c\xc4\xdf\x12\ +\xf9 \xf7\xe83hXN\x88\x14\xc1\xc6\x83\x04\xec\x22\ +\xa1\xce\x8d\x11\xb5\x18\xa4\x8c\x84\x8eD\x09#\xa1\x9b@\ +w\x13,\x0f=\xd47\x179\xcd\xa0\xc0QH\xae\xa9\ +#|\xe6e\xe0'/:\x98\xa5\x91P(\xb7\x91\x82\ +\xa8\x94\xe3\x0d\x83\x9b\x11\x91t\x02>\xb8\xe3\x06\x1f\xe1\ +\x14=\x16=\xc0\x13)\xed\xca(\xd9:\xea\x81S&\ +\x8a\xe0\xc7(b\xa80\x0c\xd8!\x8a\xe2\x1f\x09\xd5\x5c\ +\x1d\xb7\x9e\x0d\x1b\x96\x8bK#\x8aX\xd0\x19\xc2\xcb\x81\ +\x08\x84\xe4\xe2\xeb\x11`\xc6\xae\x99Z\xf6\x99@\xb9\xe4\ +V\x92\xc9\x89\x0et2(7\xa2\xa2\x84T\x81Ro\ +\xa8\x81\xf3\x1ev\xf0\xc2f\xe5\xd6D\x0fE\xc4=I\ +\xea\x02G\x80O\x910\x11D;\x8c\x93gm-\x16\ +\x0e\x12\x7fwc\xd2\x08\xcb\x89{\x15\xd0\x88\x1f\xf3Z\ +\xa2\x0cGC,b\x8c\xd3E\x5c1\xc5\xc0f\x8b\xb4\ +\xdc\x1b<\xd8\xc15\x9f\x06e\xdbWW\xc7m\xef&\ +3\xd3\xdb\xb5/|c\xaf\x05\x86\xc8\xed9@\x06z\ +\xab\xe7\x98\x0e\xc1\xb9\xd8\xb1\xad\xa6\xde(\x0e\x153\xeb\ +\xd4/\x86k\x03X\xea\xd9\xac\xd5\x10\xae\xfe\x02\x11\xf5\ +\x0d\xb9\x1b\xfbN\xd3%\xcb\xa3I)\xf1\xd8\xac\xae\xa5\ +\xd6\x80\xad\x92\xba8K\xb1\x1c\xd9P%\x93\xf3\xda}\ +\xb5\xaf~\x8f\xd6\xe3\xcb\xa4\xc7E\xee\xd8pP\xc5+\ +:\xf7$s\xe1\x149\xeb\xc1q\x1d\x89|\x07\xb5\xd1\ +v6\x1eFPr\xed\xbb\x83\x0d\xcdY\xe1\xba\x96`\ +\xcbe =t\xe6\x05-*V\xa29x\xe0R\xca\ +\xc6\x84\xf7\xbe6\x9cz\xf0\x89\xa9\x9dJq\xd9T\xe1\ +g+D\x9aI\x1f?^\xd1r'3\x83kn\xe0\ +\xae^\x0e6\xbc\xd8{\xf3%p\xa9\x81\x10\x98=p\ +\xfa\xb4\x08Y\x8d\x83\xd2\x0c\x9f\x87\x1d\x90\ +\xa5\xeepx,tm_\xf0h\xab\x16\x98B\xec\xb1\ +k\x83\x11\xf2\x97\x02\xe9RfO\xad;2XD\x84\ +W\x92\x06Q\x0b}\x89\x10+at\x07\x8e\x8bD\xa7\ +QR\x0adVcO6\x8d\x01\xbf\x10vh\x84R\ +\x98D6\xcf\xa33{\x0fb5j\xc5\xf9\x15\x02^\ +-*:FA\x81|\x1cA\x1e\xee\xb8&\x99p\xc6\ +L\xe8\xe2\x17\x22\x82\x1e\xc6\xf3\xf4\xe3m\x95 9D\ +\x09)\x05_\x99\xdb\x9aH\xed{\xebY\x10a\x82\xd9\ +\x04+\xdc\x04\x06%x\xb4\x99\x18e\x1d\x1d\xf5\x98\x82\ +\x08?k\xe1\x99\xa0\x1fY\xf0D\x8e\xb4R\xa5B\xb2\ +J\xed\x1dL`\xbd\x03\xb0\xdd|\xaew\xc7\x0d23\ +\xc4\x88\xeb\x90\xb6B\x83\xb4*\x95\x86\x14\x82\x05>\x85\ +I\xac\xc1ZI\x9b\xa4q,I\xa1\xd7N\x83Z\x02\ +[\x02\xe61\x22=\xab\xe8NI\xc8\xe6\xc9\xc3\x07n\ +F\xe5h\xcb\x8c\xc4Lfu+i\x0a_\xbe\xc4\x9d\ +<\x06t\x10\xd1\x01G\xa7\xb7\xbb\x91H\xb5\x16M\x83\ +\x80&'\xd6\xc0&\x8c?\xc0l\xe8/\xd7\x83~\xd5\ +\xea4\x06G\xa0\x9cX[z\xd5\x19eIU\x85\xaf\ +\x5c\x0a\xdf\xdc.\xcb>\xa8\xcdE\x16F]\xae\x90\x11\ +\xc1$\x09\x89xV\xa6\x88Po@y\xf8\x09\x95V\ +\x97\x82\xc8\xb7zvs\xf5\x97a\x1bH,-\xc8\xf7\ +\x86\xbc\x8d\xbd]\xaf\x88R\x0e_\x91I\xd4\x1ef\x11\ +\x16M\x1b\x0e[\xa8dG\xd2\xa3$/%\x19\xe6A\ +\x12 &\x8a7\x0a'&\xad4l\xaf\x0f\xac\xa7\x8e\ +;\xb3+\x1a\xb0B\x8d\xfa\x056p\x141D\x85\xc6\ +1;%F\xdbp'\xaf\x8a\xa1\xaa[=\x9b\xf4\xb1\ +g\xc88CV\x92\x5c\xa3\x19\x89F#\x9c\xa1\xfaK\ +\x1bV\x0c\x01%\xed\x8e\xc5\x11#\xc6V\xea\xfe\x91\xef\ +\x034W$0\xac\x1a/\x1e\x07\xcb\x08\xf0oI\xf0\ +)\xbc\xfd\x12\xec\xc5{\xbb\xea@P\x82\xdd\xcd\x11\x19\ +\xf1\xc8\x1d\xd2 \xd7\x80.\xcc\xcc\x0a\xa0\x8a\xa4\xd6$\ +\xaa&\xa8\xa9\x0a\x9d\xd9Ct_\xb8\xe3|\xf8l]\ +D\xf2\xee4\xe4\xa6\x0d-W\x01M\xd0\x9cU8\xd2\ +!\xf8zZ8\x86\x10\x1c}\x07Z\xb9-M1`\ +\x8a\x12\xdb\xd3\xeeT\x87\x93n\xbc1.\xa7\x05\x16\x92\ +\xe2\xf4k\xb8YY4\xfc$z\xe8\xa2\x1e\x12t\x0f\ +\x1b\xe8\x09\xe0\xb0\xa9_f\x18\xaa\x18|\xac\xbf\x93\xd9\ +{\xb3=\xb8\xff*q\xd4\xb2L\xe0\xef\x93\x0fz\xbb\ +\xb7a\xb3\x1a|\xa9M\xda>\x0f\xc6\x83)\xf5\xb8+\ +\xc8\xee\xa2x.\x1eM\xa6,&o\xd7\x19\x83\x8b\x09\ +\xe2\x1aht\x1ec\x8d\x00y\x14\xecj\xad\xb8\x1a\xab\ +\x1akp\x99\x0b\xca@;\x09\xd3t=*\xb4,\xec\ +\x04\xf91\x17}\xd1x\xce\xb8\x9e\x9bNp'\xf7D\ +\x85\x9e&\x87\xc28\x84\xd1z\xae\x07\x15EV\x9e;\ +2\xff\xbe\x069\xb0\xb1\x98$)\x08v\xd5\x83\xa7\xcb\ +H\x01\xf9\xe0v\xa4 \x05\x97\xfa\xe1\xed\xad4v\xb2\ +O\xe5{e\xa8z-\xb5\xba\xad\x15\x0e\xb2\x077\xb0\ +\x22\xc4l \xb1\x98\xc4\xd2\x9d\x91\x13\xa6;\x96\x8b\xe8\ +\xdd'+\xc4\x8c\xddt\x91\xec\xe23\xd4vIy\xc4\ +\xbb\xa9\xd0\x09y\xc4g\xfc\x1e\xf7\xa4\xe8\xa8\xd2d2\ +\xc8\x0c\x95HYt\x00Y\x98\xd6\x00u=\xf8\xff\x89\ +B\x16\xaa\xa7\x84\xf83\x0f\xbe\x96\xcd\x14_\xed\xdb\xde\ +\xc45\x1d\x0c\x1ey=t\xa4\x9d\xbb~ :\xaa\x88\ +\x81\x91\xc8]\x07\xc1|Vk\xd6\x10$\x82\xa3\xd6w\ +\xe0ft\x10w\xc3j\xd7\x8f$&\x13g\x80k\x8e\ +\x81e\xe9\x0c\xa4\x15$\xc1\xc4\xacn\x0c\x87d\x94\xa2\ +\x83\xfa\xef\xa8<\x89\x8cq\xa8\x02\x8f\xd6\x00x\x0b\x1b\ +\xe3\x00\xa6)\x22\x0d\xc4!R\xd7U\xd6\xe0\xbdq@\ +{\xe7I\xf6\xc5\xe0\x90\x92\x0a\xcb\xd0\xd9=\x5c\xdd!\ +\xea9E[i\x92\xaa3\xd1\xd5\xe6TD\xd0\x95\xa9\ +\xdct\xaa\x9cp\x9c\x5c\xca\xe6F\x93\x1ey\x9e\x98\xd7\ +M\xec&\x1d@\x9e\xeb\x8eE\xb1\xcc\xc1N\xb0<]\ +\x9a\x1c\xde\xd1\x1dl\xed\xe0\xda/G\x87P\x12\xd1\xed\ +\x12\xfc\x12AY\xb3\xb1m\x89xu\x11\xc1H\xb6\x88\ +\x00\xf6sDQ\xf2\xe9\x98\xb6\xab\xb1\xc8\x94\xdc;\xa2\ +\x19\x13U\xd2\x1f\xeb\xb6\xe8\xe0F\xc0\xa4z\xb0\xa6p\ +b\xcfPs\xd9\xc3\x16!t\xbd=:\xb0(\xfeT\ +\xe4\xce\x19\xb0\xd5\xf0QU\xceo&\xf8Pk\x99l\ +\x19&\xc3\xf6.|\x1d\x9c\xfd+\xd1\xa1i\x17\xca\x08\ +!@G\xd42\x5c!\x04\xc8\xc4\xa1o\xf5\x1c\xa6%\ +\xb8\x06\x8cq\x85\xcfcs\xf8I\x98\xb6L\xb6\xe7\x16\ +6\x93VU\x84R\xdanJqI\xbfF`\xa9\x5c\ +\xacrR\x97]\x8c!\xdbl\x9aT\xc3\xa1\xc9Pc\ +'a\xb3.\x81\xad2\x02\x85\x0a\x067\xe6\x8a,l\ +\xa7\xbd\xfd\xd7\x8cle)v9\x22\x17\xc5o\x94\x05\ +\xef\xf9\xe2~\x9a\x7f\x82\xbc\x9b\xc2p\xfd\x01\xd9\xd0p\ +\xa0\xa1\xf7\xda\xb5\xde\xeb\x07HL/\x0b\xf6I|\x19\ +t\x96\x89\x0f\x9e7\xef\xc9\x87mv\x96)\xdf\xb4a\ +\x8a\xce\xa8\x0d,n\x11\xcb\x00\xda[\xbb\xcf\x131\xe4\ +b\xa4\xb1\x09)\x80,\xe6\xb5:u\xab.\x85\xc8b\ +\xf3h\x5cCK\xb8\xed,\x912\xf0\xe0\xf0;!\xb5\ +,\xd4I\xa8\x09-\xd1$T\x9d\xcacI\xac\xc6v\ +\xce\x8d\x0bHe\xb2)\xc5\xe8\xf4\x09\x0b\xb6\xbe\x12:\ +vJZ\xcfj\x92f\xdc&I\xeau7F\x0f}\ +\xa4\xd5\xc7\xbb\xafe\xca\xb8\x12X\x9b\xde\xeeF\x1cp\ +Yju\xba\x1fu\xba\xed\x9e\x1a\xb1\x06F\xc3\x11`\ +\xcc2)\x92\x13\xd3vJ\x00\x11\x9d+\xe5\xc3\x066\ +r\xcct\x90be*\xf2\xd8*\xbe*Uj\xc6@\ +\xa2\xd4L\x82F\x01`\xb1F\x82F\xb9n\xed1\xa5\ +\xc1f3\xd1Q\xe2=`\xa4B\xd2j7\xc9o\x91\ +\xe4\x16\xcda)\x89\x19\x84\x87/\x05\xc7\x99\xcdU\xb8\ +\xfd\x065+\xcaN\xb9\xba\x95\x07\xe5\x9a\x05\x02L|\ +EVw\xc0\x94\x04\x08\x01\x8c\xe5^\x87\x9b\x18\x0cY\ +\xa0\xa9\x94\xa6A\xe4g\x83$\x9da\xa4\x89\x10A\x16\ +\xf5a\x0d\x15S*:\x95~4\xa7$J\x976\x98\ +t\xe2\xf6h\x06\x1c\x0d\x5c\xfd\xd9\xbe-g%\x1b\xaf\ +\x0db\x99\x18\xbc\x93\x92bn\x8d\x13-\xa0\ +\xdb?\x97j\x0ad\x15cL\x09 {\xf6\x19W\x83\ +i\x83\x8e\xc1\x0a\xab;=\xa7\x88\xe9)\xedI\x16-\ +9b\x15\x81\xb2\x02\xd4M\xa5S+\x1a(\xad\x0d\xe9\ +<\x84:\x06\x98Kb\x80\xc4\xea\x80\xf9a\xf5\x09\xff\ +\x96\x1cC\xc3r-E\x87\xb5&9r^\xd8-\xd3\ +\xc0&\xd9\x11>\xb6|QM\xe086HI\x92\xd4\ +D#\xf3\xa7\xe2\xb0N\xd6\xa6\xc8\xa4\x08O\xaa\xd8!\ +Ms\xac\x83\x1cJ\xcd\xa7\xde\xfd\x1a\x86\x0f\xeb6\xc9\ +\xae\xd8\x18\xc4\x82\x98k\xf5\x0b\xb7Ld\xe1i\x8at\ +\xc9F2\x80\x92\xee\xd9\xd0\x86\x13%W\xa6\x87\xbc\x88\ +:\x00R\xe22@*b\xd8\xc4\xea\x11\xeeZ7\x9f\ +ciy]\x1fT\x1a\x03\x8au\xc6\x92I\xf7p]\ +\xbd~\xdf\xbbq\xd1\x9bTJ&W6\x06n\xe7q\ +\x10G\xe9\xf2\x03m 1]\x94\xa2\xdd5\x03g\xd0\ +\xe4\xcb\xcf*K\x1b\x1a\xb8\xa6\xbd\xe1\xb6\xdd\xc1\x96S\ +\xc1\xd6c\x91\xf8\xa9\x95\x0aQ\x0eGHuD\x1cN\ +\xe4]\x9af\x97\xa8\x86T\x8fq1$\xd6\xf41\x8a\ +\x81V\xa6\xdbK\xa7\x82\xdd\xcd\xfa\xdd\xd2\xd517\xa6\ +\xb3\xb8\x8c\xf5\x18CSo\xb2.\xb4\xe8LO\xbe\xa4\ +l\x9d\x8dVR\x05\x94\xf9\xba\xe0\x81\xce\xbe\xedM\x91\ +\x84D\x1dx\xd6\xac\xfa!\xfc\x8e8\xb9_\xf9p\x80\ +6\xd8)\xb4\xe4\xcfG\xa5y\x9d\x18?\xd3\x8a\xa5\xcb\ +\x11\x8d\xa4\x88\xc8\xce\xa3\xa1\x93? X\x7fI\xaa\x8e\ +\xd5\xc68\x86b\xac\x22\xb9g\xddb[a\x18G^\ +\xe7N\x1e\x8e\x06\xf4v\xae\xa0\xd0\x93\xcb\xe896\xed\ +\x9e3\xa8_ yE\x91\xad\xae:RM\x0f:\xb0\ +\xa6\x93\xd9\x8fj\x22]\xac\xb0\x97\x0e\xea\x05\xfad\x06\ +\x9c\xddv\x92#U5\x98\xebvMK\x99\x01\x962\ +|c\x1fW\x7f\x8aN)\xf8\xf8\xfb\xdb#\x8aOL\ +\xf7k\xe8-\x8e\xdb\xb8XC\xcf\x80E\xde\xc9\xdf\xb9\ +\xd2O-u\x04\x02Nw\x85\xccx\x17\xe1\x9c\x9e\xc0\ +[b\xbc\x99\xf3\xa6\xa1\x9e}x\x93p\x86\xebl\xca\ +\xd7\x01\x12\xfb\xedZ\x9a\x14\xc4|\xb1\x96Foe1\ +\x1a\x9f\xdc-\xc7\xf7H\xb8\xa2\xf87\x19\xb4\x18\xed\xda\ +\xc1p\xfbI\xa5\x97\xa3\x81i\x10z\x08\xfe\xf8\x12\x04\ +e}k\xd2C\x0fI\x85\xad\x89\xa5\x9ej\x9f\xcd\x11\ +K\x0d;\xfb\xe8D|P:\x00S/\xc8~\x11s\ +\xa5\x82\x1a\xa71t}o\xf0\xe0\xbe\xde\xee\xbd\xda\xa8\ +\xf9\xf0t\x05\xbeU\xcc\x01\xff\xcbmxbx\xd3a\ +\xae\xb9AM\xed\x12@\xb3\x80G\xcad\xab\xeb\xaf\x84\ +v\xb5h\xe30\x8b\x87\xbb\xd5\xc1,\xa5V5\x82\xdf\ +\x10j\xec\x94]4f \x17q1\xc7rO\xe2j\ +\x02\xe9\xa4\xb5\x12\xae:.\x22\xd5\x03\xe7\xe0S\xf1\x91\ +\xb2&\x15\xf4\xa2.\xfe\xff\x058\x7f\xb2\x10\x9e+c\ +\x0a\x03\x94\xa1+A\xe7\xb0\xf7\x93I9\x97\xa9\x8a1\ +\x89\x09\x17n\xc7\x1e\xeeF\xdd\x02g`au\x19\x0d\ +\x5c\xe1[\xc9h\x0aF=\xee\xd7\xc5\xcd\x9d0B;\ +\xddv\xd9\xc4\xcc\x8a5\x02_H\xce6\x07\xdf4\xe3\ +\xc1\xb2og\xf8\xc0k\xa05\xb2\xd3\x84_/\x1f\xee\ +\x87\x9d\xa6\x82\x15\x1b\xf26F\xe5\xc6l\xbc\xd0\x08/\ +\x1e\x92\x98\x11\xff\x1e*\x00t\x01\xd0'.T\x88_\ +\x93\xe8\xab\x05\xbf+\xc3\xc5\x96\xb5\xf8\xb5\x97n\x9fx\ +\xb5\xe7F\xb3@ m\x8c\xc90\xe3gx\x1f\x9e\x9c\ +\xc9\xe0M\x06\xffL>@\x12.R\xb0\xd0\xef-\xd8\ +\x8e\xf1\xcc\xb4\xe0a\xf3,\xca\x01\xf8\xbc\xe53\xd33\ +\xc9v\xcc\x06\x1e\xc2}0Yf\x1d\xf7\xfe\ +\xde9\xd3\x86ZpV6\x9a\x82\x17w\x80\x9d\xfdp\ +S{\xdf\xf6\xbd\x9a\xf0\xc4\xb1\xea\x00\xcc\x9b\x82!r\ +\xedG\xc9\x91\xdd\xd0\xc5\x1b\xdc\xf6m\xc2=u8H\ +s\xe5\x14W\x7f\x85\x05n!\xfc\xb4`\x1e\xf3\xba&\ +\xc1\xca_)J\x9d\x0e\xb9\xe2\xe3\xcd\xa4\x97\xd9b\x1a\ +\xa8\xba\xba\xb6\x8f\xeb\xde\xa7\xe8\x1b\xa5\x9a\x81y\xf2\x8e\ +.\xced\xfa\xdf\x8d\x1eQ(\xfa\xe7\x98d\x5c2Q\ +\xb5\xa6\xe7L\x8an\x83@\xa0\x1d\xd8j\x94\x91z-\ +\xc6\x9c\xb7\xb4\xf7\x9d8\xc85\xed\xe9;\xdd\xa0\xe8\x15\ +5\xbem\x04O\xe8i\x97\xd3\x0f\xffI\xe7_\xa7\x88\ +\xe1\xc3\xdb\xcf$\x0c\xd7=o\xab\x8b.\x11x_\x8a\ +JE\x10\xfd\xe6\x94\x96\xe2\x8b=]|\xb1\xd9\xd3U\ +\xcc\xd1\xc7\x12\xb4\x12\xdf\x94\x93\xa9Z\xbeR\x86q`\ +\xc9\xca\xa2L\xe2\xd2O\xd2\x043\x81\x01\xa3\xe3\xd8k\ +\x80\x03\xf7^\x1b\x5c\x22\x10\xbf\x12\xad\x92\xc8/]\xb9\ +J\xf3A\x92\x96\xab\xa2)\x1a\xbf\xce\x99B\xb7\xe2b\ +\x9b\x8c\xde\xaeF\xd9A\x9e\xe6=\xd2\xf56*\x1c\xd3\ +\x18\x1c\xf2\xdb\x1c\xac\xcc\xc1e\xbfNu\xc8H\xc0E\ +\x0aDz\xbb\xce\xf3\xe1n\x5c\xc5\x11\x01\xdd\x83L\xd5\ +\x8br\xf3\x18\xa0\x07\xf9\xa5\xe4\xbe\xc6i[)\xf6\xc9\ +_\x0a&d\xf8\xa5\xc12\xa5/\x9a\x04$\x09\x9b\x01\ +&,\x88\xdd~\xcd\xa7s\x159+\x13\xd6\xf1\xfcZ\ +\x16\x0fz\xe8\x9cr\xc9\x10sZ\xc1\xdd\xdb\xf9;\xa3\ +\xd8\x83.*<\x135\xf0h\x9c\xe4\xd2\x82.?\xb2\ +\xa5\x09}\xb1\x0a\xba\xee)\x1d1\xcf\xa0<\xc2\xa9\xa4\ +`\xb2\x1e\x05|\x89\xac\xfah\x8f\x8a\xa6\x98\xb6\xaa\xaa\ +^\x93ak\xe4\xa6\x15\xf96\xca}\xb263\xe1\x9b\ +R\xc6\x22Ob\x8e\xd8\x9f8\xca\xf5tsGw\xf1\ +\x0bs^)\xce\x7fO\xd2\xd1\x17wt*OS\xca\ +IB\xa7\x8a\xc6\xf4\xd5GM\xedxO\xe6\xc3:\x9a\ +W,\xb9\xe8ypYr\xe1o\x12\xb3\xf3\x1e\x16l\ +\xad\xeb\xbd\xdeH\x0f\xddw\x9f\xce\xcc}>/7w\ +R\xbe&\xda\xdb\xaeISA0h\xaa\xe8\xc4\x08\xeb\ +i\xa2ffP\x18$9\x14\xc5\x86\x12\xee\x92e\x03\ +\xcf\xb6\x81\xba\xd0\x1e\x8cY\xa7\x12ETx\x0c\xd0A\ +l\x8d\x88P4\xaa\x01\x91\x09\xcf\x8a\x82\xae\x9d\x1c\x15\ +j\x5cp\xe86_\xd7\xb8\xe8\x5c\xb0\xa3\x01\x08+F\ +\xbdic\xeb}\xe8\xf55j\x10\xe4\xc6\x1am\x10\xb1\ +\xc7^4\xb0\xc7\x1cy\xd10\x8c\xbaG\x0a5b-\ +C\x14\x87\x0b\xa5@\x8b\xa6U\xd2\x03,\x99\x0aB\xb8\ +\xb0\x0dfn\xfc\x11\xab\xf6\xc9\xe5\x0bb\x91\xa1\x9e2\ +\xd2u(<^\x87\x9b\xf5<\xee\xd0\xa1P\x82\xf9\x8a\ +\x0e\x85\x12\x9c\xaeC\xa1|p(c\x08b\xaazT\ +AHC\xcba2\xca\xa9C\xc0\x94pp\x9b\x9aB\ +g\xae\xf5m\xa72\xae\xec\x17\x9b\xbaW\xf6\xfa\x9a\xd5\ +\xbd\x12\xbcIVS\xa8\xa1lT\x15\x85\x8c\x9a\xd1!\ +\xb8$\xe2a\xde\x08\xcb\x9b\x9c\xe0\xd5\xe6\xdem,o\ +\xf5\x94\xc7\xbe\xbc\xd5\xd2^$+0\xd5\xbeD\xcc@\ +\xaa\xd3\xa4\x8b\xc3Tp\xcc\x0a8\x0e\xe62\x08\xa5\xbe\ +L'\xd0D_\x92\x96\xa4h\x08\x94\xf4\x93\x09\x8a\xe3\ +\xa0\xe1\x9bG\xc0R\xf2\xbd\xd2\xbc>O\x85\xcf\xe1\xf7\ +3Q6\xdd\xb5\xee\xc7\x11\xf5\xa7\xcb\x84`\xfa\x1d\xae\ +u%\x98\xaf\xb8\xd6\x95\xe0b\x0c@\xab\xfa\xe9\xea\x08\ +\xe5\x83Q2\xe7\xaa\xe7c\xd5\x08\xca~I\x82\x8b\xc9\ +h\x02\xd8\xf9\x91\xc4\xa5\xc6\xb0c\xd1\x1f\xca^_\xb3\ +\xfeP\x82\x8f\x9aa\xd6\x90\xa9;\xba\x8bG\x05'\xc6\ +\x94\xb1&~\xd2\xcf\xa2\xd0xN6LO\x5cz\x0b\ +QLN\xbb\xee\xf2\xb8#\xf7G\x09(3V\x9d\xa9\ +:\x91\x895fL\xc4\xc9\xb2\x96J8$7\xa9\x09\ +\xe7\xebH\xc8hL^\xcc\xda\x5c\xdd5\xa6\x84\x8e\xaa\ +\xdf\xedX\x015/\xe71\x09Xy\xaa\xd5\xcf\xd4(\ +\xe3\xec\xaa,\xaf\xaa\xb1\xe6\xe7*bQ\xa0\xac\xf7s\ +G\x8e\xeah\xa25\xf8\x12\x9c\x88\xa9(e\xaf\xe4\x0f\ +P\x01\x1a\x87\x8aA+\x15\xa1U\xe8\x9bh\x05\x90o\ +B\x0b\xe1\xf5R\x5c\x1d\xbc\x02=\x0cT\x94\xa0EX\ +'\x9bQ6,\x05\x0bz\x14M\xc5\x89\x00\x0b\xf4\x5c\ +\x05\xff\xd8\x1e\xd3P\x0e\xec\xca\xf8\xef\xe6\x818\x98q\ +\xafJ\x1c\x0e\xf1\xe24\x82\x05\xd8\xc1\xff\xe5\xc0\xbf\xa9\ +\xf0\xbf\x07\x9f^\xb4@\xff\x95\xf0\xffJ\xe8U\x04X\ +\x84\xc0\xef4\x0c\x8b\x5c\xdb\xef\xc4\xeem\x0eN*\xd8\ +q\x98:E'\xed\xc1\xbf\xae\xc2\xa2hev@|\ +\xf0{-\x85S)^\xf4nV\xd5N\xf2\xcf\xe5\xe1\ +\xcf-\x0aH\xe4x\xbb\x8fjo\xfe9\xbe@\xb2\x1c\ +\xe8\x9d\xc1\x9e\xe9'\xd0\x144\x1d\xef\xcc\xcd\ +\x0a\xa8_\x15\xe2\x14\x06b\x01\x95\x09\xc2e!U \ +\xcd\xbd\x90D\x90\xa7>L\xba\xc9\x8f\x07h\xea\x83\xfd\ +\xb6F\xc6\xcbi8\xd60S\xd1_\x80\x9d\x8f\x8bp\ +\x88>w\xd1\x14J\x0aM\xd3(\x93/d\xd4\xc42\ +g\xd3\xbb\xc8\x85\xf4\x0b\x99+\xf9\x13e\xb8\xbc\x87\x94\ +\xe1Fz\xce\x85\xb3\xf8<\xad\xb9:O\xf8\x98\xf1H\ +\xcfd\xc3\x9a\x08`<$\x1c\xb2\x06^\x93_\xde\x10\ +f\x81\x8c[\x90^\xf5\xb7\x04\xd2\x9d$\x9d\xd8\x04\x9c\ +p\xae\x8bl^]8\xad$\x95\xd1\xb8\xe8:\xa8T\ +lm\xad\xe2\xe9_\xc5\x15\xa6\x00]H+i\xcf\xc4\ +\xff\xd4<\x98\xfeW\xce\x04\x9b\x8e,\xd59\xe5|\x0f\ +\x18x\x17~J\xfa|\x22\xbf\x03>\xab\x0e\x94g\xcf\xc0\x9f\ +\xe3d\xb6)P\xeb\x85\xde|G\x8b\xcd\xef\x07\x04\xde\ +,\x93\xcbi\xda`\xaa\xa9t\x06\x1c\xd9~\xaf\xd5\xe6\ +\xc87y}\x8e\xec\x1a\x9f\xd5;\xd3\xb41\xa5\xd2\x07\ +\xdf\x92d~\xbe)\xcf\xbb~\xa6\xa9\xcc\xe3\xb3;|\ +\xd98\xd7\x9fo\xca\xe5\x1fr9s\xaa\xdd6\xfeG\ +\x0d\x1d\xf6\xfc\xfc*\xabo\x8d\x83\xefFI\xcb7\x99\ +\x9f\x982=/\xd7L\x1f\x8d\xf6\xe0\x14\xfc`\x81\x05\ +\x93\xccg\xce\xf1\xe8\xf94\xbaFJ\xfe\x01o`&\ +y[C\x88\x9c\x96\x9bK?\xc0\x0f\xe7\xbb=\xbe*\ +\xabk&\xcfL\xaf\xd8\x1f0W8\xdd\xd9\x01\x8f\x97\ +\xcf\xe9\xc3\xa0\xe8\x07e\x9e@\xc0S%\xff\xcc\xe5(\ +\x0f\xe4\xe72\x1f\xe0\xaa\x03\xf2I\xf6\xda@v\x99\xcb\ +c[\x93\xedt\xdb\xf9\x11\x00n\x9e\xbb\xe2[x\xc8\ +\x5c\xd8{\xedTo\xd7.n\xcf\xf1\xde\xeb{\xf8\xfb\ +9\xf1\x8d\xdf|q\xc0\xff\x1cIz\x15\x0e\xa4\xe0\x82\ +\xde|a\xbag\xa2+03\xe0\x0c\xb8\x1c\x13+\x80\ +\xcdb\xbd.\xfd\xc6\xea\x0b8\xfd\x01\xfe+\xa6\xe0\xe1\ +\x7f\xeb\x98H\x04I\x1a\x96\xd5UV]\x85\x87\xa5\x08\ +6\xd0\xef\x9d\xf6\xff#\x03#Q\x08\x9a\x9c!\xb4\xbb\ +\xac\xee\x0a\xbf\x82|u!J\x92\x9fT\x97\x0a\x97r\ +T\xd1\x92?\x12\xe4\x0a\xb0\xc5\xf9b\x1e^\x08%j\ +\x05\xef\x87\x94\xac,\xa2\xd1\x96\x8eWCg^\x00i\ +\x0f\x1e\xe8Tc\xf2\xd1\x18\x86\x9f\xc9\x07Ke\xd5R\ +\xfa3\xc0\xdc\x05[\xce<\xe3f~OL\x08g\x92\ +'\x14!:\xc9\xecH\x17T\xf4v5\x92\xdfq\x0c\ +\xb7\xed&\x81\xbb\xf0\x1bW\xb83\xbb\xc8y\x9a`\xe3\ +\xa9\xf0\xcd\xbdj\x92\xcbU\x85\x10\xb7C\xb0\xf2^\x85\ +\xe8d\x8e\x032\xc9\xf5\x09j\x1a\xa5\xe2\x069\x8di\ +\xf8\xc7\xd0\x08Cd\xc7I\xf9keIhE\xde\xc1\ +\x0e\x8e\x8e]\xfcE\x0b;3\x002\xa8*\xfa{h\ +\x02\x8f\xa5\xdb\x86\xc8'\xd98\x88\x9c\xcd<\xe1\xa2\xc7\ +\xe8\xf5~C\x8d\xc4\x95]>\x1b\x15\x93+\xd7\xb8\x9d\ +\xc7\xf9(\xe3K\xc7(\xff\x1b.\xd1\xa2t9\xa9\x9b\ +\x81\xff\x11\xee\xf8\xa5{|\xe1\x8c2\x9b\x00\x96<5\ +\x7f\xd4+\xf9-\xda\xe9kE\xf6}\x86\xc6\xc9c2\ +\x1e\x0b\x9b\xde\x86\x01i\x5c8,\x1f\xd6\x08&\x1d\xa6\ +w\xff\x09M\x89I\xb7\xa0\xc8A\xa4\xa0\xe5T\xce|\ +l\x19\xd8\xe5\xdek/\xaa\x1f&\x07\xab\xf8\x9c\xf8:\ +\x1aK\xf4\xe0!\xfb\x94\xd7v\xf6\x9dh\x0d\xef\xb8\xa8\ +\x06\x10S\x14\x5cI\xe0|\xea\xba1W+q\xe7[\ +z\xaf\xef\xcb\xd7\xe2\xc8\x0a\xe9\x97aY\x0a\xd9d;\ +w\xe5\x22\xf9\x11UB\xa6\x06\x98\xa1\x80\xd4'\xaeX\ +\x1f-wc\x09\xe8:\x13j\xdc\xae\xd13W~{\ +#\xfd\x8d3\xb6\x96_o\xa2\xd2\xc9\xfd\x8e\xa4\x92_\ +o\xba&2\x19?\xe9\x94\xadG\xb7\xcef\x08\xcd\xfb\ +\xb5\x9c\xe2n\xb6\xc8r\xc8r\xb8\xeb\xf0\xcd\xd9^\xec\ +\xea\xb2\xdb~7=~@\x5cf\xe5\xf5\xe6|p\xe7\ +\x19\xbc\x88\x84\x9cX\x99\x18\xe2\xf1\xe1aj\xddM^\ +\xa3\xf13\xa2\xd9\xe40\xdc\xad\x9e\xc3dj\xc9I\x8d\ +\xbe\xba\xc3\xb0\x00\x82\xbbv\xf4\xf6t\x91\x9f\x17\x0dw\ +n\x09\xef\xdc\xac\xa6\x9f\x1c9\xac\xa0\x17\x14\xaf\xc3\xb9\ +w\xd9\xcf\x8d\xf1\xe3\x0f\x9d\xdf\x11\xea>\xa31ic\ +\xf0E@\xde\xe8\x92\x13l}%\x8a\xe4\xac\xc4\xc1f\ +;M\x8d\x94\xd3\xbb\xd0\xdc\xb4bCRu\x92\xaeg\ +c\x93\x99\xa2\x81\x14\x7f\x0b\x94A?!\xd8\xfa*\xd7\ +y\x83k\xda\x1c\xaa?\x83\x8f/b\x9dw\xfc\x0a\xd7\ +\xd3\xc4\xff,(\xd8\xcfI\x1a$\xe5\xa8~f\xcf\xa5\ +\xa8x\xd1\x13H\xe6G\xf7\xf4\x04\xf29q\xdf\xea\x8f\ +\xf2\xc3\xd0\xec\xa9a\xe9g3\x1cby^\x0c\x97\xd3\ +a\x0e-\x11b\xeaL\xce\xcbBk\xb8\xe4W\xd6\x91\ +\xf3\xec\xa4\x96\x22\x93\x8d\xbbO\xd2Z\xb7wzmg\ +\xba\xfa\xdaN\xad9\x88V\x0c\xa4\x9d\x9b\x1e\xa2(\x0c\ +\x92\x92\xd4J\x09.\x89iN\x0d\x91g3]4\xe9\ +\x1a\xaa\x9d\x98[=\xd5>T\xbc\xb7\xd3G\xf5\x04\x0e\ +/3\xdd\xbb\xea\xf9\x0b(\xf1A,\xed\xee\x91Np\ +\x19\xd9\x13\x5c4\xa7.\x870J\x01A\x87\x01\x04\x8e\ +>\x03bMm\xc9\xfb%\x09730\x89!,}\ +\xeaG3\xc5K\x1c\x94\xa7\x00\xd9[\x0e\xd9Z\xae\x0c\ +\xf6\xf7\x7f\xa9@\xf0W\xb9k0!\xf2\xaf\xd5\xb3u\ +\xdc\xc2\xf2\xd2\xae\xe7N\xa7(\x15U\xddJc4\x07\ +w%?\xe5$\xdck\xa6\xb2\x9bL\x8am\xfb+}\ +u\x9b\xb9\xee\xfd\xc4|j\xf9\xa0\xf3\x98\x8ak&%\ +\x1d\xdeuA\xd3;\x88\xe9\xde7\xa5\xa4\xc5\x9cJT\ +\xca\x862\xb3\x9b\xcf\x9cE\xa5\xf7\xd7\x1f\xd6\xf6\xd2\xee\ +\xe4\xdez\xa5\x1f$\xa6\x8b\x98\xf3\x94m\xdd\x5c\xb7\x86\ +:0\xab\xbc\x8eH7$h\x9e\x1d\x94\x03\xccR\x01\ +\x8c\xa52h\x08uf\x98\x1d\xa6\x8aC\x0d\xf0.>\ +.\xee\xbf\x00l\xf3\x8b\x81\ " qt_resource_name = b"\ @@ -3095,7 +3096,7 @@ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00L\x00\x01\x00\x00\x00\x01\x00\x00\x93\xe0\ -\x00\x00\x01\x91\x97t\xbe)\ +\x00\x00\x01\x92\x0a\xba\x87\xef\ \x00\x00\x00\x1c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x05\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00(\x00\x02\x00\x00\x00\x01\x00\x00\x00\x06\ diff --git a/ui/settings.ui b/ui/settings.ui index ab51efb..f5cd3c6 100644 --- a/ui/settings.ui +++ b/ui/settings.ui @@ -110,7 +110,7 @@ p, li { white-space: pre-wrap; } hr { height: 1px; border-width: 0; } li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } -</style></head><body style=" font-family:'Microsoft YaHei UI'; font-size:9pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">以下占位符可用</p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">歌名: %&lt;title&gt; 艺术家: %&lt;artist&gt;</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">专辑名: %&lt;album&gt; 歌曲/歌词id: %&lt;id&gt;</span></p> diff --git a/ui/settings_ui.py b/ui/settings_ui.py index 5a21688..c0275c6 100644 --- a/ui/settings_ui.py +++ b/ui/settings_ui.py @@ -493,11 +493,11 @@ def retranslateUi(self, settings): "hr { height: 1px; border-width: 0; }\n" 'li.unchecked::marker { content: "\\2610"; }\n' 'li.checked::marker { content: "\\2612"; }\n' - "\n" + '\n' '

\u4ee5\u4e0b\u5360\u4f4d\u7b26\u53ef\u7528

\n' '

\u6b4c\u540d: %<title> \u827a\u672f\u5bb6: %<artist>

\n' - '

\u4e13\u8f91\u540d: %<album> \u6b4c\u66f2/\u6b4c\u8bcdid: %<id>

\n' + '

\u4e13\u8f91\u540d: %<album> \u6b4c\u66f2/\u6b4c\u8bcdid: %<id>

\n' '

\u8bed\u8a00\u7c7b\u578b: %<langs>

', None, ) diff --git a/ui/sidebar_window.py b/ui/sidebar_window.py index c8d3368..9c398fc 100644 --- a/ui/sidebar_window.py +++ b/ui/sidebar_window.py @@ -1,7 +1,5 @@ # SPDX-FileCopyrightText: Copyright (c) 2024 沉默の金 # SPDX-License-Identifier: GPL-3.0-only -from enum import Enum - from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QIcon from PySide6.QtWidgets import ( @@ -15,10 +13,7 @@ QWidget, ) - -class SidebarButtonPosition(Enum): - TOP = 0 - BOTTOM = 1 +from utils.enum import Direction class SidebarWindow(QMainWindow): @@ -30,6 +25,7 @@ def __init__(self) -> None: self.Total_Widgets = 0 self.Top_Widgets = 0 self.Bottom_Widgets = 0 + self.current_widget = 0 def __setup_ui(self) -> None: self.setObjectName("SidebarWindow") @@ -53,6 +49,7 @@ def __setup_ui(self) -> None: self.sidebar.addItem(self.__sidebar_spacer) def set_current_widget(self, index: int) -> None: + self.current_widget = index current_button = self.sidebar.itemAt(index + 1).widget() if index >= self.Top_Widgets else self.sidebar.itemAt(index).widget() if not isinstance(current_button, QPushButton): return @@ -72,7 +69,7 @@ def SidebarButtonClicked(self) -> None: def set_sidebar_width(self, width: int) -> None: self.sidebar_widget.setFixedWidth(width) - def add_widget(self, name: str, widget: QWidget, position: SidebarButtonPosition = SidebarButtonPosition.TOP, icon: None | QIcon = None) -> None: + def add_widget(self, name: str, widget: QWidget, position: Direction = Direction.TOP, icon: None | QIcon = None) -> None: """添加一个新页面到侧边栏中 :param name: 页面的名称 @@ -102,10 +99,10 @@ def add_widget(self, name: str, widget: QWidget, position: SidebarButtonPosition } """, ) - if position == SidebarButtonPosition.TOP: + if position == Direction.TOP: self.sidebar.insertWidget(self.Top_Widgets, button) self.Top_Widgets += 1 - elif position == SidebarButtonPosition.BOTTOM: + elif position == Direction.BOTTOM: self.sidebar.addWidget(button) self.Bottom_Widgets += 1 widget.setParent(self.Widgets) @@ -123,3 +120,4 @@ def clear_widgets(self) -> None: self.Total_Widgets = 0 self.Top_Widgets = 0 self.Bottom_Widgets = 0 + self.current_widget = 0 diff --git a/utils/data.py b/utils/data.py index 34e1b90..26dba66 100644 --- a/utils/data.py +++ b/utils/data.py @@ -1,5 +1,13 @@ # SPDX-FileCopyrightText: Copyright (c) 2024 沉默の金 # SPDX-License-Identifier: GPL-3.0-only + +"""数据管理模块 + +这个模块提供了: +- 配置文件管理 +- 歌词关联数据库管理 +""" + import json import os import sqlite3 @@ -16,12 +24,20 @@ class ConfigSigal(QObject): class Config(dict): + """LDDC的配置管理类 + + 1. 使用QMutex保证线程安全 + 2. 使用方法类似字典 + 3. 使用json格式存储配置文件 + 注意: 用于QMutex导致这个类并不高效,不应该在需要高性能的地方使用 + """ + def __init__(self) -> None: self.mutex = None self.config_path = os.path.join(config_dir, "config.json") self.__singal = ConfigSigal() - self.lyrics_changed = self.__singal.lyrics_changed - self.desktop_lyrics_changed = self.__singal.desktop_lyrics_changed + self.lyrics_changed = self.__singal.lyrics_changed # 在歌词相关配置改变时发出信号 + self.desktop_lyrics_changed = self.__singal.desktop_lyrics_changed # 在桌面歌词相关配置改变时发出信号 self.default_cfg = { "log_level": "INFO", @@ -61,7 +77,7 @@ def write_config(self) -> None: json.dump(self, f, ensure_ascii=False, indent=4) def read_config(self) -> None: - if os.path.exists(self.config_path): + if os.path.isfile(self.config_path): try: with open(self.config_path, encoding="utf-8") as f: cfg = json.load(f) @@ -71,6 +87,8 @@ def read_config(self) -> None: self[key] = value elif isinstance(value, list) and isinstance(self[key], tuple): self[key] = tuple(value) + elif isinstance(value, float) and isinstance(self[key], int): + self[key] = value except Exception: self.write_config() @@ -111,7 +129,13 @@ def __delitem__(self, key: Any) -> None: class LocalSongLyricsDB(QObject): - changed = Signal() + """歌词关联数据库管理类 + + 1. 使用sqlite3数据库管理本地歌曲歌词 + 2. 使用QMutex进行保证线程安全 + """ + + changed = Signal() # 用于更新歌词关联管理器UI def __init__(self) -> None: super().__init__() @@ -122,6 +146,7 @@ def __init__(self) -> None: def init_db(self) -> None: with QMutexLocker(self.mutex): + # 创建表 cur = self.conn.execute(""" CREATE TABLE IF NOT EXISTS songs ( id INTEGER PRIMARY KEY, @@ -136,6 +161,7 @@ def init_db(self) -> None: UNIQUE (title, artist, album, duration, song_path, track_number) ) """) + # 创建索引 cur.execute(""" CREATE INDEX IF NOT EXISTS idx_title_artist_album_duration_song_path_track_number ON songs (title, artist, album, duration, song_path, track_number) @@ -154,6 +180,7 @@ def set_song(self, track_number: str | None, lyrics_path: str | None, config: dict) -> None: + """设置歌曲歌词关联,如果存在则更新,否则插入""" with QMutexLocker(self.mutex): # sqlite 不认为两个 NULL 是相等的 self.conn.execute(""" @@ -171,6 +198,7 @@ def query(self, duration: int | None, song_path: str | None, track_number: str | None) -> tuple[str, dict] | None: + """查询歌曲歌词关联""" parameters = self.handle_null(title=title, artist=artist, album=album, duration=duration, song_path=song_path, track_number=track_number) with QMutexLocker(self.mutex): cur = self.conn.execute("""SELECT lyrics_path, config FROM songs @@ -182,10 +210,12 @@ def query(self, return None def del_item(self, id_: int) -> None: + """删除歌曲歌词关联""" with QMutexLocker(self.mutex): self.conn.execute("""DELETE FROM songs WHERE id = ?""", (id_,)) def get_item(self, id_: int) -> dict[str, int | str | dict | None] | None: + """根据id获取歌曲歌词关联信息""" with QMutexLocker(self.mutex): cur = self.conn.execute("""SELECT title, artist, album, duration, song_path, track_number, lyrics_path, config FROM songs WHERE id = ?""", (id_,)) query_result = cur.fetchone() @@ -203,12 +233,14 @@ def get_item(self, id_: int) -> dict[str, int | str | dict | None] | None: return None def del_all(self) -> None: + """清空歌曲歌词关联数据库""" with QMutexLocker(self.mutex): self.conn.execute("""DELETE FROM songs""") self.conn.commit() self.changed.emit() def get_all(self) -> list: + """获取所有歌曲歌词关联信息""" with QMutexLocker(self.mutex): cur = self.conn.execute("""SELECT id, title, artist, album, duration, song_path, track_number, lyrics_path, config FROM songs""") return cur.fetchall() diff --git a/utils/enum.py b/utils/enum.py index 6e6d75c..03ac388 100644 --- a/utils/enum.py +++ b/utils/enum.py @@ -38,9 +38,9 @@ class LyricsFormat(Enum): class SearchType(Enum): SONG = 0 - ARTIST = 1 - ALBUM = 2 - SONGLIST = 3 + ALBUM = 1 + SONGLIST = 2 + ARTIST = 3 LYRICS = 7 @@ -83,8 +83,8 @@ class LocalMatchSaveMode(Enum): class LocalMatchFileNameMode(Enum): # 歌曲/格式 - SONG = 0 - FORMAT = 1 + FORMAT = 0 + SONG = 1 class Direction(Enum): diff --git a/utils/exit_manager.py b/utils/exit_manager.py index 23401bd..fe12d28 100644 --- a/utils/exit_manager.py +++ b/utils/exit_manager.py @@ -9,6 +9,8 @@ class ExitManager(QObject): + """程序退出管理器""" + close_signal = Signal() def __init__(self) -> None: @@ -41,6 +43,9 @@ def exit(self) -> None: for thread in self.threads: thread.quit() thread.wait() + for window in self.windows: + window.destroy() + window.deleteLater() cache["version"] = cache_version cache.expire() @@ -50,6 +55,12 @@ def exit(self) -> None: app.quit() def window_close_event(self, window: QWidget) -> bool: + """窗口关闭事件 + + 独立窗口关闭时调用 + + :param window: 关闭的窗口 + """ if not check_any_instance_alive() and not self.check_any_window_show(window): self.exit() return True diff --git a/utils/logger.py b/utils/logger.py index 3daf173..c6ca4b9 100644 --- a/utils/logger.py +++ b/utils/logger.py @@ -1,5 +1,8 @@ # SPDX-FileCopyrightText: Copyright (c) 2024 沉默の金 # SPDX-License-Identifier: GPL-3.0-only + +"""日志记录器""" + import io import logging import os diff --git a/utils/translator.py b/utils/translator.py index e8cae03..e7a8e4c 100644 --- a/utils/translator.py +++ b/utils/translator.py @@ -1,5 +1,7 @@ # SPDX-FileCopyrightText: Copyright (c) 2024 沉默の金 # SPDX-License-Identifier: GPL-3.0-only +import platform + from PySide6.QtCore import QLibraryInfo, QLocale, QObject, QTranslator, Signal from PySide6.QtWidgets import QApplication @@ -19,6 +21,19 @@ class _SignalHelper(QObject): language_changed = _signal_helper.language_changed +def get_system_language() -> QLocale.Language: + if platform.system() == 'Darwin': + try: + from Foundation import NSUserDefaults # type: ignore[reportMissingImports] + if (languages := NSUserDefaults.standardUserDefaults().objectForKey_("AppleLanguages")): + language = languages[0] + if language.startswith("zh"): + return QLocale.Language.Chinese + except ImportError: + logger.warning("Failed to get system language on macOS") + return QLocale.system().language() + + def load_translation(emit: bool = True) -> None: global translator, qt_translator # noqa: PLW0603 app = QApplication.instance() @@ -31,9 +46,8 @@ def load_translation(emit: bool = True) -> None: lang = cfg.get("language") match lang: case "auto": - locale = QLocale.system() - language = locale.language() + language = get_system_language() logger.info("System language detected: %s", language) if language != QLocale.Language.Chinese: translator.load(":/i18n/LDDC_en.qm") diff --git a/utils/utils.py b/utils/utils.py index 011d338..1dc446c 100644 --- a/utils/utils.py +++ b/utils/utils.py @@ -6,16 +6,16 @@ import sys from collections import OrderedDict from collections.abc import Iterable -from pathlib import Path from typing import Any -from chardet import detect +from charset_normalizer import from_bytes, from_path from .enum import LyricsFormat from .error import DecodingError def get_lyrics_format_ext(lyrics_format: LyricsFormat) -> str: + """返回歌词格式对应的文件扩展名""" match lyrics_format: case LyricsFormat.VERBATIMLRC | LyricsFormat.LINEBYLINELRC | LyricsFormat.ENHANCEDLRC: return ".lrc" @@ -37,77 +37,49 @@ def time2ms(m: int | str, s: int | str, ms: int | str) -> int: def read_unknown_encoding_file(file_path: str | None = None, file_data: bytes | None = None, sign_word: Iterable[str] | None = None) -> str: """读取未知编码的文件""" - from utils.logger import logger - file_content = None - if not sign_word: - sign_word = [] - if not file_data and file_path: - with Path(file_path).open('rb') as f: - raw_data = f.read() - elif file_data: - raw_data = file_data + if file_data is not None: + results = from_bytes(file_data) + elif file_path is not None: + results = from_path(file_path) else: - msg = "file_path and file_data cannot be both None" + msg = "文件路径和文件数据不能同时为空" raise ValueError(msg) - - with contextlib.suppress(Exception): - if sys.version_info >= (3, 11): - encoding = locale.getencoding() - else: - encoding = locale.getpreferredencoding(False) - if encoding in ("chinese", "csiso58gb231280", "euc-cn", "euccn", "eucgb2312-cn", "gb2312-1980", "gb2312-80", "iso-ir-58", "936", "cp936", "ms936"): - encoding = "gb18030" - file_content = raw_data.decode(encoding) - for sign in sign_word: - if sign not in file_content: - file_content = None + file_content = None + if sign_word is not None: + for result in results: + for sign in sign_word: + if sign not in str(result): + break + else: + file_content = str(result) break - if encoding == "gb18030" and file_content and "锘縍EM" in file_content: # 修复编码检测错误 - file_content = None - - if file_content is None: - detect_result = detect(raw_data) - if detect_result['confidence'] > 0.7 and detect_result['encoding']: - encoding = detect_result['encoding'].replace('gb2312', 'gb18030').replace('gbk', 'gb18030') # gbk is a subset of gb18030 - with contextlib.suppress(Exception): - file_content = raw_data.decode(encoding) - for sign in sign_word: - if sign not in file_content: - file_content = None - break + else: + best = results.best() + if best is not None: + file_content = str(best) if file_content is None: - encodings = ("utf_8", "gb18030", "shift_jis", "cp949", "big5", "big5hkscs", - "euc_kr", "euc_jp", "iso2022_jp", "shift_jisx0213", "shift_jis_2004", - "utf_16", "utf_16_le", "utf_16_be", "utf_32", "utf_32_le", "utf_32_be", - "ascii", "cp950", "cp932", "iso2022_kr", "euc_jis_2004", "euc_jisx0213", - "iso2022_jp_1", "iso2022_jp_2", "iso2022_jp_2004", "iso2022_jp_3", - "iso2022_jp_ext", "latin_1", "cp874", "hz", "johab", "koi8_r", "koi8_u", - "koi8_t", "kz1048", "mac_cyrillic", "mac_greek", "mac_iceland", - "mac_latin2", "mac_roman", "mac_turkish", "ptcp154", "utf_7", "utf_8_sig", - "iso8859_2", "iso8859_3", "iso8859_4", "iso8859_5", "iso8859_6", "iso8859_7", - "iso8859_8", "iso8859_9", "iso8859_10", "iso8859_11", "iso8859_13", - "iso8859_14", "iso8859_15", "iso8859_16", "cp037", "cp273", "cp424", - "cp437", "cp500", "cp720", "cp737", "cp775", "cp850", "cp852", "cp855", - "cp856", "cp857", "cp858", "cp860", "cp861", "cp862", "cp863", "cp864", - "cp865", "cp866", "cp869", "cp875", "cp1006", "cp1026", "cp1125", "cp1140", - "cp1250", "cp1251", "cp1252", "cp1253", "cp1254", "cp1255", "cp1256", "cp1257", - "cp1258") - for encoding in encodings: - with contextlib.suppress(Exception): - file_content = raw_data.decode(encoding) + with contextlib.suppress(Exception): + if sys.version_info >= (3, 11): + encoding = locale.getencoding() + else: + encoding = locale.getpreferredencoding(False) + if encoding in ("chinese", "csiso58gb231280", "euc-cn", "euccn", "eucgb2312-cn", "gb2312-1980", "gb2312-80", "iso-ir-58", "936", "cp936", "ms936"): + encoding = "gb18030" + if file_data is not None: + file_content = file_data.decode(encoding) + elif file_path is not None: + with open(file_path, encoding=encoding) as f: + file_content = f.read() + if sign_word is not None and file_content is not None: for sign in sign_word: if sign not in file_content: file_content = None break - if file_content is not None: - break - if file_content is None: msg = "无法解码文件" raise DecodingError(msg) - logger.debug("文件 %s 解码成功,编码为 %s", file_path, encoding) return file_content diff --git a/utils/version.py b/utils/version.py index 38b1f4a..8433a0f 100644 --- a/utils/version.py +++ b/utils/version.py @@ -1,6 +1,9 @@ # SPDX-FileCopyrightText: Copyright (c) 2024 沉默の金 # SPDX-License-Identifier: GPL-3.0-only -__version__ = "v0.7.1" + +"""版本信息与处理模块""" + +__version__ = "v0.7.2" import re from typing import Literal diff --git a/view/about.py b/view/about.py index 11fec65..8444425 100644 --- a/view/about.py +++ b/view/about.py @@ -13,9 +13,8 @@ class AboutWidget(QWidget, Ui_about): - def __init__(self, version: str) -> None: + def __init__(self) -> None: super().__init__() - self.version = version self.setupUi(self) self.connect_signals() @@ -25,7 +24,7 @@ def init_ui(self) -> None: if year != "2024": year = "2024-" + year self.label.setText(html.replace("{year}", year)) - self.version_label.setText(self.version_label.text() + self.version) + self.version_label.setText(self.version_label.text() + __version__) def connect_signals(self) -> None: self.github_pushButton.clicked.connect(lambda: QDesktopServices.openUrl(QUrl("https://github.com/chenmozhijin/LDDC"))) diff --git a/view/desktop_lyrics.py b/view/desktop_lyrics.py index 5ad98a3..0518d5b 100644 --- a/view/desktop_lyrics.py +++ b/view/desktop_lyrics.py @@ -1,5 +1,8 @@ # SPDX-FileCopyrightText: Copyright (c) 2024 沉默の金 # SPDX-License-Identifier: GPL-3.0-only + +"""桌面歌词界面实现""" + import math from typing import NewType @@ -47,6 +50,8 @@ class DesktopLyricsSelectWidget(SearchWidgetBase): + """选择歌词界面""" + lyrics_selected = Signal(Lyrics, str, list, int) def __init__(self, parent: QWidget | None = None) -> None: @@ -72,15 +77,15 @@ def setup_ui(self) -> None: self.verticalLayout.insertWidget(0, self.label_title) self.verticalLayout.insertWidget(1, self.label_sub_title) - # 设置保存按钮 + # 设置选定歌词按钮 self.select_lyrics_button = QPushButton(self) self.open_local_lyrics_button = QPushButton(self) - save_button_size_policy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) - save_button_size_policy.setHorizontalStretch(0) - save_button_size_policy.setVerticalStretch(0) - save_button_size_policy.setHeightForWidth(self.select_lyrics_button.sizePolicy().hasHeightForWidth()) - self.select_lyrics_button.setSizePolicy(save_button_size_policy) - self.open_local_lyrics_button.setSizePolicy(save_button_size_policy) + select_lyrics_button_size_policy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + select_lyrics_button_size_policy.setHorizontalStretch(0) + select_lyrics_button_size_policy.setVerticalStretch(0) + select_lyrics_button_size_policy.setHeightForWidth(self.select_lyrics_button.sizePolicy().hasHeightForWidth()) + self.select_lyrics_button.setSizePolicy(select_lyrics_button_size_policy) + self.open_local_lyrics_button.setSizePolicy(select_lyrics_button_size_policy) but_h = self.control_verticalLayout.sizeHint().height() - self.control_verticalSpacer.sizeHint().height() * 0.8 @@ -189,6 +194,7 @@ def closeEvent(self, event: QCloseEvent) -> None: class DesktopLyricsMenu(QMenu): + """桌面歌词菜单(桌面歌词与托盘图标)""" def __init__(self, parent: "DesktopLyricsWidget") -> None: super().__init__(parent) @@ -245,6 +251,8 @@ def show_main_window(self) -> None: class DesktopLyricsTrayIcon(QSystemTrayIcon): + """桌面歌词托盘图标""" + def __init__(self, parent: "DesktopLyricsWidget") -> None: super().__init__(QIcon(":/LDDC/img/icon/logo.png"), parent) self._parent = parent @@ -268,6 +276,8 @@ def self_activated(self, reason: QSystemTrayIcon.ActivationReason) -> None: class DesktopLyricsControlBar(Ui_DesktopLyricsControlBar, QWidget): + """桌面歌词控制栏""" + update_lyrics_info = Signal(dict) def __init__(self, parent: QWidget | None = None) -> None: @@ -312,6 +322,8 @@ def update_lyrics_info_slot(self, info: dict) -> None: class DesktopLyricsWidgetBase(QWidget): + """桌面歌词窗口基类(处理窗口移动调整等)""" + moved = Signal(QPoint) resized = Signal(QSize) @@ -474,6 +486,8 @@ def update_cursor(self, event: QMouseEvent) -> None: class LyricsText(QWidget): + """逐字歌词文本显示实现""" + update_lyrics_signal = Signal(tuple) def __init__(self, parent: DesktopLyricsWidgetBase) -> None: @@ -715,6 +729,8 @@ def clear_cache(self) -> None: class DesktopLyricsWidget(DesktopLyricsWidgetBase): + """桌面歌词窗口""" + send_task = Signal(str) # snder new_lyrics = Signal(dict) # slot to_select = Signal() # sender diff --git a/view/get_list_lyrics.py b/view/get_list_lyrics.py index e61b795..f49c506 100644 --- a/view/get_list_lyrics.py +++ b/view/get_list_lyrics.py @@ -1,6 +1,9 @@ # SPDX-FileCopyrightText: Copyright (c) 2024 沉默の金 # SPDX-License-Identifier: GPL-3.0-only -from PySide6.QtCore import Signal + +"""获取专辑/歌单歌词界面""" + +from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QCloseEvent from PySide6.QtWidgets import QDialog, QMessageBox, QWidget @@ -15,6 +18,7 @@ def __init__(self, parent: QWidget | None = None) -> None: super().__init__(parent) self.setupUi(self) self.ask_to_close = False + self.setWindowModality(Qt.WindowModality.WindowModal) def question_slot(self, but: QMessageBox.StandardButton) -> None: if but == QMessageBox.StandardButton.Yes: diff --git a/view/local_match.py b/view/local_match.py index f3c97a5..6a82d14 100644 --- a/view/local_match.py +++ b/view/local_match.py @@ -99,28 +99,11 @@ def start_cancel_button_clicked(self) -> None: if len(lyric_langs) == 0: MsgBox.warning(self, self.tr("警告"), self.tr("请选择至少一种歌词语言!")) - match self.save_mode_comboBox.currentIndex(): - case 0: - save_mode = LocalMatchSaveMode.MIRROR - case 1: - save_mode = LocalMatchSaveMode.SONG - case 2: - save_mode = LocalMatchSaveMode.SPECIFY - case _: - MsgBox.critical(self, self.tr("错误"), self.tr("保存模式选择错误!")) - return + save_mode = LocalMatchSaveMode(self.save_mode_comboBox.currentIndex()) - match self.lyrics_filename_mode_comboBox.currentIndex(): - case 0: - flienmae_mode = LocalMatchFileNameMode.FORMAT - case 1: - flienmae_mode = LocalMatchFileNameMode.SONG - case _: - MsgBox.critical(self, self.tr("错误"), self.tr("歌词文件名错误!")) - return + flienmae_mode = LocalMatchFileNameMode(self.lyrics_filename_mode_comboBox.currentIndex()) - source = [Source[k] for k in self.source_listWidget.get_data()] - if len(source) == 0: + if len(source := [Source[k] for k in self.source_listWidget.get_data()]) == 0: MsgBox.warning(self, self.tr("警告"), self.tr("请选择至少一个源!")) return @@ -147,7 +130,7 @@ def start_cancel_button_clicked(self) -> None: self.worker.signals.finished.connect(self.worker_finished) self.worker.signals.massage.connect(self.worker_massage) self.worker.signals.progress.connect(self.change_progress) - threadpool.start(self.worker) + threadpool.startOnReservedThread(self.worker) def worker_massage(self, massage: str) -> None: self.plainTextEdit.appendPlainText(massage) diff --git a/view/main_window.py b/view/main_window.py index 893f8ae..e7a3d17 100644 --- a/view/main_window.py +++ b/view/main_window.py @@ -1,17 +1,23 @@ # SPDX-FileCopyrightText: Copyright (c) 2024 沉默の金 # SPDX-License-Identifier: GPL-3.0-only + +"""LDDC的主窗口""" + from PySide6.QtCore import Qt, Slot from PySide6.QtGui import QCloseEvent, QIcon, QShowEvent +from PySide6.QtWidgets import QMessageBox -from ui.sidebar_window import SidebarButtonPosition, SidebarWindow +from ui.sidebar_window import SidebarWindow +from utils.enum import Direction from utils.exit_manager import exit_manager from utils.translator import language_changed -from utils.version import __version__ -from view.about import AboutWidget -from view.local_match import LocalMatchWidget -from view.open_lyrics import OpenLyricsWidget -from view.search import SearchWidget -from view.setting import SettingWidget + +from .about import AboutWidget +from .local_match import LocalMatchWidget +from .msg_box import MsgBox +from .open_lyrics import OpenLyricsWidget +from .search import SearchWidget +from .setting import SettingWidget class MainWindow(SidebarWindow): @@ -22,34 +28,50 @@ def __init__(self) -> None: self.is_inited = False def showEvent(self, event: QShowEvent) -> None: + # 直到要显示时才初始化 if not self.is_inited: self.init() return super().showEvent(event) def init(self) -> None: + """初始化主窗口""" self.setWindowTitle("LDDC") self.resize(1060, 600) self.setWindowIcon(QIcon(":/LDDC/img/icon/logo.png")) self.set_sidebar_width(100) - self.search_widget = SearchWidget(self) + self.search_widget = SearchWidget() self.local_match_widget = LocalMatchWidget() - self.settings_widget = SettingWidget(self.widget_changed) - self.about_widget = AboutWidget(__version__) + self.settings_widget = SettingWidget() + self.about_widget = AboutWidget() self.open_lyrics_widget = OpenLyricsWidget() self.init_widgets() self.connect_signals() self.is_inited = True def init_widgets(self) -> None: - self.clear_widgets() self.add_widget(self.tr("搜索"), self.search_widget) self.add_widget(self.tr("本地匹配"), self.local_match_widget) self.add_widget(self.tr("打开歌词"), self.open_lyrics_widget) - self.add_widget(self.tr("关于"), self.about_widget, SidebarButtonPosition.BOTTOM) - self.add_widget(self.tr("设置"), self.settings_widget, SidebarButtonPosition.BOTTOM) + self.add_widget(self.tr("关于"), self.about_widget, Direction.BOTTOM) + self.add_widget(self.tr("设置"), self.settings_widget, Direction.BOTTOM) def closeEvent(self, event: QCloseEvent) -> None: + if self.local_match_widget.running: + def question_slot(but: QMessageBox.StandardButton) -> None: + if but == QMessageBox.StandardButton.Yes and self.local_match_widget.worker: + self.local_match_widget.worker.stop() + if exit_manager.window_close_event(self): + self.destroy() + self.deleteLater() + else: + self.hide() + + MsgBox.question(self, self.tr("提示"), self.tr("正在匹配歌词,是否退出?"), + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No, question_slot) + event.ignore() + return + if exit_manager.window_close_event(self): super().closeEvent(event) else: @@ -57,7 +79,7 @@ def closeEvent(self, event: QCloseEvent) -> None: self.hide() def connect_signals(self) -> None: - + self.widget_changed.connect(self.settings_widget.update_cache_size) # 更新缓存大小 language_changed.connect(self.retranslateUi) def retranslateUi(self) -> None: @@ -66,7 +88,12 @@ def retranslateUi(self) -> None: self.settings_widget.retranslateUi(self.settings_widget) self.about_widget.retranslateUi(self.about_widget) self.open_lyrics_widget.retranslateUi(self.open_lyrics_widget) + + # 重新设置侧边栏按钮 + current_widget = self.current_widget + self.clear_widgets() self.init_widgets() + self.set_current_widget(current_widget) @Slot() def show_window(self) -> None: diff --git a/view/search.py b/view/search.py index 3128ba7..652d5ef 100644 --- a/view/search.py +++ b/view/search.py @@ -39,7 +39,6 @@ def __init__(self, parent: QWidget | None = None) -> None: super().__init__(parent) self.setupUi(self) self.connect_signals() - self.search_type = SearchType.SONG self.songlist_result = None self.reset_page_status() @@ -58,7 +57,6 @@ def __init__(self, parent: QWidget | None = None) -> None: def connect_signals(self) -> None: self.search_keyword_lineEdit.returnPressed.connect(self.search) self.search_pushButton.clicked.connect(self.search) - self.search_type_comboBox.currentIndexChanged.connect(self.search_type_changed) self.results_tableWidget.doubleClicked.connect(self.select_results) self.translate_checkBox.stateChanged.connect(self.update_preview_lyric) @@ -90,26 +88,8 @@ def get_source(self) -> Source | list[Source]: match self.source_comboBox.currentIndex(): case 0: return [Source.QM, Source.KG, Source.NE] - case 1: - return Source.QM - case 2: - return Source.KG - case 3: - return Source.NE case _: - msg = "Invalid source" - raise ValueError(msg) - - @Slot(str) - def search_type_changed(self, index: int) -> None: - """搜索类型改变""" - match index: - case 0: - self.search_type = SearchType.SONG - case 1: - self.search_type = SearchType.ALBUM - case 2: - self.search_type = SearchType.SONGLIST + return Source(self.source_comboBox.currentIndex()) def result_return(self) -> None: """返回""" @@ -164,8 +144,8 @@ def search(self) -> None: self.search_pushButton.setText(self.tr('正在搜索...')) self.search_pushButton.setEnabled(False) self.taskid["results_table"] += 1 - self.search_info = {'keyword': keyword, 'search_type': self.search_type, 'source': self.get_source(), 'page': 1} - worker = SearchWorker(self.taskid["results_table"], keyword, self.search_type, self.get_source(), 1) + self.search_info = {'keyword': keyword, 'search_type': SearchType(self.search_type_comboBox.currentIndex()), 'source': self.get_source(), 'page': 1} + worker = SearchWorker(self.taskid["results_table"], keyword, SearchType(self.search_type_comboBox.currentIndex()), self.get_source(), 1) worker.signals.result.connect(self.search_result_slot) worker.signals.error.connect(self.search_error_slot) threadpool.start(worker) @@ -485,11 +465,12 @@ def results_table_scroll_changed(self) -> None: not self.all_results_obtained and self.search_info and table.rowCount() > 0): + self.get_next_page = True # 创建加载中的 QTableWidgetItem loading_item = QTableWidgetItem(self.tr("加载中...")) - loading_item.setTextAlignment(0x0004 | 0x0080) # 设置水平和垂直居中对齐 + loading_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) # 设置水平和垂直居中对齐 # 设置合并单元格 table.insertRow(table.rowCount()) @@ -509,14 +490,13 @@ def results_table_scroll_changed(self) -> None: class SearchWidget(SearchWidgetBase): - def __init__(self, main_window: QWidget) -> None: + def __init__(self) -> None: super().__init__() self.setup_ui() self.resize(1050, 600) self.select_path_pushButton.clicked.connect(self.select_savepath) self.save_preview_lyric_pushButton.clicked.connect(self.save_preview_lyric) self.save_list_lyrics_pushButton.clicked.connect(self.save_list_lyrics) - self.main_window = main_window self.setAcceptDrops(True) # 启用拖放功能 def setup_ui(self) -> None: @@ -636,15 +616,13 @@ def get_list_lyrics_update(count: int | str, result: dict | None = None) -> None if count == self.get_list_lyrics_box.progressBar.maximum(): set_no_running() - MsgBox.information(self.main_window, self.tr("提示"), self.tr("获取歌词完成")) + MsgBox.information(self, self.tr("提示"), self.tr("获取歌词完成")) self.get_list_lyrics_box = GetListLyrics(self) - self.main_window.setEnabled(False) self.get_list_lyrics_box.ask_to_close = True self.get_list_lyrics_box.progressBar.setValue(0) self.get_list_lyrics_box.pushButton.setText(self.tr("取消")) self.get_list_lyrics_box.pushButton.clicked.connect(pushButton_clicked_slot) - self.get_list_lyrics_box.closed.connect(lambda: self.main_window.setEnabled(True)) self.get_list_lyrics_box.progressBar.setMaximum(len(self.songlist_result["result"])) self.get_list_lyrics_box.plainTextEdit.setPlainText("") self.get_list_lyrics_box.show() @@ -799,3 +777,4 @@ def auto_fetch_slot(self, result: dict) -> None: else: MsgBox.critical(self, "错误", result["status"]) + self.preview_plainTextEdit.setPlainText("") diff --git a/view/setting.py b/view/setting.py index a4adc92..cbc72da 100644 --- a/view/setting.py +++ b/view/setting.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-only import os -from PySide6.QtCore import Qt, QUrl, SignalInstance +from PySide6.QtCore import Qt, QUrl from PySide6.QtGui import QDesktopServices, QFont, QFontDatabase from PySide6.QtWidgets import QFileDialog, QListWidgetItem, QWidget @@ -16,12 +16,10 @@ class SettingWidget(QWidget, Ui_settings): - def __init__(self, widget_changed_signal: SignalInstance) -> None: + def __init__(self) -> None: super().__init__() self.setupUi(self) self.init_ui() - self.widget_changed_signal = widget_changed_signal - self.widget_changed_signal.connect(self.update_cache_size) self.connect_signals() def init_ui(self) -> None: diff --git a/view/update.py b/view/update.py index a87b6b6..1fbd674 100644 --- a/view/update.py +++ b/view/update.py @@ -31,6 +31,8 @@ def button_clicked(self, button: QAbstractButton) -> None: QDesktopServices.openUrl(QUrl(f"https://github.com/{self.repo}/releases/latest")) self.close() self.deleteLater() + if self in dialogs: + dialogs.remove(self) def show_new_version_dialog(name: str, repo: str, new_version: str, body: str) -> None: