Showing preview only (421K chars total). Download the full file or copy to clipboard to get everything.
Repository: PrajwalVandana/maestro-cli
Branch: master
Commit: b0f790aabe2b
Files: 27
Total size: 406.9 KB
Directory structure:
gitextract_c46jkt4m/
├── .github/
│ └── workflows/
│ └── cross-build.yml
├── .gitignore
├── LICENSE
├── data/
│ └── icons/
│ └── maestro_icon.icns
├── dist/
│ └── maestro_music-2.0.6-py3-none-any.whl
├── install-scripts/
│ ├── mac
│ ├── ubuntu
│ └── windows.nsi
├── maestro/
│ ├── __init__.py
│ ├── __main__.py
│ ├── __version__.py
│ ├── config.py
│ ├── helpers.py
│ ├── icon.py
│ ├── jit_funcs.py
│ ├── mac_presence.py
│ └── main.py
├── readme.md
├── requirements.txt
├── scripts/
│ ├── add_album_art.py
│ ├── custom_album_art.py
│ └── rename_tracktitles.py
├── setup.py
├── specs/
│ ├── maestro-mac.spec
│ ├── maestro-ubuntu.spec
│ └── maestro-windows.spec
└── uninstall-scripts/
└── unix
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/cross-build.yml
================================================
name: Cross-Platform Build
on:
push:
branches:
- dev
jobs:
pyinstaller-build-windows:
runs-on: windows-latest
steps:
- name: Build executable
uses: PrajwalVandana/pyinstaller@c7c491de8409921df7045f681695d4d5ab71a4e0
id: pyinstaller
with:
python_ver: '3.12'
spec: specs/maestro-windows.spec
requirements: requirements.txt
- name: Install NSIS
uses: repolevedavaj/install-nsis@v1.0.2
with:
nsis-version: '3.10'
- name: Run NSIS and zip dist
run: |
makensis install-scripts/windows.nsi
Compress-Archive -Path "${{ steps.pyinstaller.outputs.executable_path }}/maestro/*" -Destination "${{ steps.pyinstaller.outputs.executable_path }}/maestro-windows.zip"
- name: Upload to release
uses: softprops/action-gh-release@v2
with:
files: |
install-scripts/maestro-installer.exe
${{ steps.pyinstaller.outputs.executable_path }}/maestro-windows.zip
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
prerelease: true
pyinstaller-build-mac:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-12, macos-latest]
steps:
- name: Build executable
uses: PrajwalVandana/pyinstaller@c7c491de8409921df7045f681695d4d5ab71a4e0
id: pyinstaller
with:
python_ver: '3.12'
spec: specs/maestro-mac.spec
requirements: requirements.txt
- name: Read version from __version__.py
id: version
# read VERSION = "a.b.c" from __version__.py
run: |
VERSION=$(sed -n 's/VERSION = "\([^"]*\)"/\1/p' maestro/__version__.py)
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Add install script
run: |
mkdir -p "${{ steps.pyinstaller.outputs.executable_path }}/Scripts"
mv install-scripts/mac "${{ steps.pyinstaller.outputs.executable_path }}/Scripts/postinstall"
- name: Create .pkg
id: pkgbuild
# main branch
# uses: PrajwalVandana/generate-mac-installer-github-action@fd5c2a03cfc2be65e32095573392ed03423a4208
# dev branch
uses: PrajwalVandana/generate-mac-installer-github-action@9d0c29930827283cce48688cb2154b47c92a4042
with:
root-directory: "${{ steps.pyinstaller.outputs.executable_path }}/maestro"
scripts-directory: "${{ steps.pyinstaller.outputs.executable_path }}/Scripts"
identifier: com.maestro.maestro-cli
version: ${{ steps.version.outputs.version }}
install-location: /usr/local/bin/maestro-bundle/
- name: Rename .pkg, compress dist
run: |
mv "${{ steps.pkgbuild.outputs.output-path }}" "${{ steps.pyinstaller.outputs.executable_path }}/${{ matrix.os == 'macos-latest' && 'maestro-apple-silicon' || 'maestro-mac-intel' }}.pkg"
tar -czf "${{ steps.pyinstaller.outputs.executable_path }}/${{ matrix.os == 'macos-latest' && 'maestro-apple-silicon' || 'maestro-mac-intel' }}.tar.gz" "${{ steps.pyinstaller.outputs.executable_path }}/maestro"
- name: Upload to release
uses: softprops/action-gh-release@v2
with:
files: |
${{ steps.pyinstaller.outputs.executable_path }}/${{ matrix.os == 'macos-latest' && 'maestro-apple-silicon' || 'maestro-mac-intel' }}.pkg
${{ steps.pyinstaller.outputs.executable_path }}/${{ matrix.os == 'macos-latest' && 'maestro-apple-silicon' || 'maestro-mac-intel' }}.tar.gz
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
prerelease: true
pyinstaller-build-linux:
runs-on: ubuntu-20.04
steps:
- name: Build executable
uses: PrajwalVandana/pyinstaller@c7c491de8409921df7045f681695d4d5ab71a4e0
id: pyinstaller
with:
python_ver: '3.12'
spec: specs/maestro-ubuntu.spec
requirements: requirements.txt
- name: Add install script and compress
run: |
mv install-scripts/ubuntu "${{ steps.pyinstaller.outputs.executable_path }}/maestro/install-maestro"
tar -czf "${{ steps.pyinstaller.outputs.executable_path }}/maestro-ubuntu.tar.gz" "${{ steps.pyinstaller.outputs.executable_path }}/maestro"
- name: Upload to release
uses: softprops/action-gh-release@v2
with:
files: |
${{ steps.pyinstaller.outputs.executable_path }}/maestro-ubuntu.tar.gz
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
prerelease: true
================================================
FILE: .gitignore
================================================
.vscode
build
*.egg-info
**/*.log
*.egg
__pycache__
*.code-workspace
**/*.env
.pylintrc
todo.md
test_songs/
song_info.txt
*_test.py
*_ref.*
sound_vis_cache/
gui_helper.py
**/*.spotdl
profile.*
================================================
FILE: LICENSE
================================================
Copyright © 2022 Prajwal Vandana
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: install-scripts/mac
================================================
#!/bin/sh
MAESTRO_BUNDLE_LOC=/usr/local/bin/maestro-bundle
MAESTRO_SYMLINK_LOC=/usr/local/bin/maestro
# Make maestro executable
chmod +x $MAESTRO_BUNDLE_LOC/maestro
# Recursively find and disable quarantine
xattr -d -r com.apple.quarantine $MAESTRO_BUNDLE_LOC
# Remove existing symlink
rm -rf $MAESTRO_SYMLINK_LOC
# Create a symlink to the maestro executable
ln -s $MAESTRO_BUNDLE_LOC/maestro $MAESTRO_SYMLINK_LOC
================================================
FILE: install-scripts/ubuntu
================================================
#!/bin/sh
PREINSTALL_LOC=.
MAESTRO_BUNDLE_LOC=/usr/local/bin/maestro-bundle
MAESTRO_SYMLINK_LOC=/usr/local/bin/maestro
# Make maestro executable
echo Changing permissions for maestro executable
chmod +x $PREINSTALL_LOC/maestro || echo Failed to change permissions for maestro executable, try running with sudo
# Remove existing bundle files
echo Removing any existing maestro bundle files at $MAESTRO_BUNDLE_LOC
rm -rf $MAESTRO_BUNDLE_LOC || echo Failed to remove any existing maestro bundle files at $MAESTRO_BUNDLE_LOC, try running with sudo
# Move files to $MAESTRO_BUNDLE_LOC
echo Creating maestro bundle at $MAESTRO_BUNDLE_LOC
mkdir -p $MAESTRO_BUNDLE_LOC || echo Failed to create maestro bundle directory at $MAESTRO_BUNDLE_LOC, try running with sudo
echo Moving files to $MAESTRO_BUNDLE_LOC
mv $PREINSTALL_LOC/maestro $MAESTRO_BUNDLE_LOC/maestro || echo Failed to move maestro executable to $MAESTRO_BUNDLE_LOC, try running with sudo
mv $PREINSTALL_LOC/_internal $MAESTRO_BUNDLE_LOC/_internal || echo Failed to move dependency files to $MAESTRO_BUNDLE_LOC, try running with sudo
# Remove existing symlink
echo Removing any existing symlink at $MAESTRO_SYMLINK_LOC
rm -rf $MAESTRO_SYMLINK_LOC || echo Failed to remove any existing maestro symlink at $MAESTRO_SYMLINK_LOC, try running with sudo
# Create a symlink to the maestro executable
echo Creating symlink to maestro executable
ln -s $MAESTRO_BUNDLE_LOC/maestro $MAESTRO_SYMLINK_LOC || echo Failed to create symlink to maestro executable, try running with sudo
================================================
FILE: install-scripts/windows.nsi
================================================
!define PRODUCT_NAME "maestro-cli"
OutFile "maestro-installer.exe"
Section
; Set installation directory
SetOutPath $PROGRAMFILES64\maestro-bundle
; Add files to installation directory
File ..\dist\maestro\maestro.exe
File /r ..\dist\maestro\_internal
; Add $PROGRAMFILES64\maestro-bundle to PATH
EnVar::SetHKCU
EnVar::AddValue "Path" "$PROGRAMFILES64\maestro-bundle"
; Add uninstaller registry key
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayName" "${PRODUCT_NAME}"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "UninstallString" "$PROGRAMFILES64\maestro-uninstall.exe"
SectionEnd
Section -Post
WriteUninstaller "$PROGRAMFILES64\maestro-uninstall.exe"
SectionEnd
Section "Uninstall"
; Remove $PROGRAMFILES64\maestro-bundle
RMDir /r "$PROGRAMFILES64\maestro-bundle"
; Remove $PROGRAMFILES64\maestro-bundle from PATH
EnVar::SetHKCU
EnVar::DeleteValue "Path" "$PROGRAMFILES64\maestro-bundle"
; Remove uninstaller registry key
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
; Remove uninstaller
Delete "$PROGRAMFILES64\maestro-uninstall.exe"
SectionEnd
================================================
FILE: maestro/__init__.py
================================================
================================================
FILE: maestro/__main__.py
================================================
import sys
from multiprocessing import freeze_support
from maestro.main import cli
if __name__ == "__main__":
# check if frozen
if getattr(sys, "frozen", False):
freeze_support()
# click passes ctx, no param needed
cli() # pylint: disable=no-value-for-parameter
================================================
FILE: maestro/__version__.py
================================================
VERSION = "2.0.6"
================================================
FILE: maestro/config.py
================================================
import os
from datetime import date, datetime
from urllib.parse import urljoin
# region constants
DISCORD_ID = 1039038199881810040
PROMPT_MODES_LIST = ["insert", "append", "tag", "find"]
PROMPT_MODES = {mode: i for i, mode in enumerate(PROMPT_MODES_LIST)}
LOOP_MODES = {
"none": 0,
"one": 1,
"inf": 2,
}
EXTS = (".mp3", ".wav", ".flac", ".ogg")
METADATA_KEYS = (
"album",
"albumartist",
"artist",
"artwork",
"comment",
"compilation",
"composer",
"discnumber",
"genre",
"lyrics",
"totaldiscs",
"totaltracks",
"tracknumber",
"tracktitle",
"year",
"isrc",
"#bitrate",
"#codec",
"#length",
"#channels",
"#bitspersample",
"#samplerate",
)
INDIC_SCRIPTS = (
"bengali",
"assamese",
"modi",
"malayalam",
"devanagari",
"sinhala",
"tibetan",
"gurmukhi",
"tamil",
"balinese",
"thai",
"burmese",
"telugu",
"kannada",
"gujarati",
"urdu",
"lao",
"javanese",
"manipuri",
"oriya",
"khmer",
)
CUR_YEAR = date.today().year
# region paths
MAESTRO_DIR = os.path.join(os.path.expanduser("~"), ".maestro-files/")
SETTINGS_FILE = os.path.join(MAESTRO_DIR, "settings.json")
LOGFILE = os.path.join(MAESTRO_DIR, "maestro.log")
OLD_LOG_DIR = os.path.join(MAESTRO_DIR, "old-logs/")
DEFAULT_SETTINGS = {
"song_directory": os.path.join(MAESTRO_DIR, "songs/"),
"last_version_sync": 0,
}
OLD_SONGS_INFO_PATH = os.path.join(MAESTRO_DIR, "songs.txt")
SONGS_INFO_PATH = os.path.join(MAESTRO_DIR, "songs.json")
OLD_STATS_DIR = os.path.join(MAESTRO_DIR, "stats/")
OVERRIDE_LYRICS_DIR = os.path.join(MAESTRO_DIR, "override-lyrics/")
TRANSLATED_LYRICS_DIR = os.path.join(MAESTRO_DIR, "translated-lyrics/")
# endregion
# region player
HORIZONTAL_BLOCKS = {
1: "▏",
2: "▎",
3: "▍",
4: "▌",
5: "▋",
6: "▊",
7: "▉",
8: "█",
}
SCRUB_TIME = 5 # in seconds
VOLUME_STEP = 1
MIN_PROGRESS_BAR_WIDTH = 20
MIN_VOLUME_BAR_WIDTH, MAX_VOLUME_BAR_WIDTH = 10, 40
LYRIC_PADDING = 3
_FARTHEST_RIGHT_CONTROL_DESC = 5
INDENT_CONTROL_DESC = 0
PLAY_CONTROLS = [
("SPACE", "pause/play"),
("b", "go [b]ack to previous song"),
("r", "[r]eplay song"),
("n", "skip to [n]ext song"),
(
"l",
"[l]oop the current song once ('l' in status bar). press again to loop infinitely ('L' in status bar). press once again to turn off looping",
),
("c", "toggle [c]lip mode"),
("v", "toggle [v]isualization"),
("LEFT", "rewind 5s"),
("RIGHT", "fast forward 5s"),
("[/-", "decrease volume"),
("]/=", "increase volume (] has issues on Windows)"),
("m", "[m]ute/unmute"),
(
"e",
"[e]nd the song player after the current song finishes (indicator in status bar, 'e' to cancel)",
),
("q", "[q]uit the song player immediately"),
(
"UP/DOWN",
"to scroll through the queue/lyrics (mouse scrolling should also work)",
),
(
"SHIFT+UP/DOWN",
"move the selected song up/down in the queue",
),
("ENTER", "play the selected song/seek to selected lyric"),
("p", "sna[p] back to the currently playing song/lyric"),
(
"g",
"go to the next pa[g]e/loop of the queue (ignored if not repeating queue)",
),
(
"BACKSPACE/DELETE",
"delete the selected (not necessarily currently playing!) song from the queue",
),
("d", "toggle [D]iscord rich presence"),
(
"a",
"[a]dd a song to the end of the queue (opens a prompt to enter the song name or ID: ENTER to confirm, ESC to cancel)",
),
(
"i",
"[i]nsert a song in the queue after the selected song (opens a prompt like 'a')",
),
(
",",
"add (comma-separated) tag(s) to all songs in the queue (opens a prompt like 'a')",
),
(
"s",
"toggle [s]tream (streams to maestro-music.vercel.app/listen-along/[USERNAME]), requires login",
),
("y", "toggle l[y]rics"),
(
"t",
"toggle [t]ranslated lyrics (if available, ignored if lyrics mode is off)",
),
("{/_", "focus queue"),
("}/+", "focus lyrics (} has issues on Windows)"),
(
"o",
"rel[o]ad song data (useful if you've changed e.g lyrics, tags, metadata, etc. while playing)",
),
("h", "toggle this [h]elp message"),
("f", "[f]ind a song in the queue (opens a prompt like 'a')"),
]
for key, desc in PLAY_CONTROLS:
if INDENT_CONTROL_DESC < len(key) <= _FARTHEST_RIGHT_CONTROL_DESC:
INDENT_CONTROL_DESC = len(key)
# endregion
# region visualizer
FPS = 60
STEP_SIZE = 512 # librosa default
VIS_SAMPLE_RATE = STEP_SIZE * FPS
VERTICAL_BLOCKS = {
0: " ",
1: "▁",
2: "▂",
3: "▃",
4: "▄",
5: "▅",
6: "▆",
7: "▇",
8: "█",
}
VISUALIZER_HEIGHT = 8 # should divide 80
WAVEFORM_HEIGHT = 6 # should also divide 80
VIS_FLATTEN_FACTOR = 3 # higher = more flattening; 1 = no flattening
WAVEFORM_FLATTEN_FACTOR = 20
# endregion
# region stream
STREAM_SAMPLE_RATE = 44100
STREAM_CHUNK_SIZE = 256
ICECAST_SERVER = (
"maestro-icecast.eastus2.cloudapp.azure.com" # Azure-hosted Icecast server
)
MAESTRO_SITE = "https://maestro-music.vercel.app"
# MAESTRO_SITE = "http://localhost:3000" # DEBUG
IMAGE_URL = f"{MAESTRO_SITE}/api/get_artwork/"
# endregion
# region auth
AUTH_SERVER = f"{MAESTRO_SITE}/api/"
# AUTH_SERVER = "http://localhost:5001/api/" # DEBUG
USER_EXISTS_URL = urljoin(AUTH_SERVER, "user_exists")
SIGNUP_URL = urljoin(AUTH_SERVER, "signup")
LOGIN_URL = urljoin(AUTH_SERVER, "login")
UPDATE_METADATA_URL = urljoin(AUTH_SERVER, "update_metadata")
UPDATE_ARTWORK_URL = urljoin(AUTH_SERVER, "update_artwork")
UPDATE_TIMESTAMP_URL = urljoin(AUTH_SERVER, "update_timestamp")
# endregion
# endregion
settings = {}
def print_to_logfile(*args, **kwargs):
if "file" in kwargs:
raise ValueError("file kwargs not allowed for 'print_to_logfile'")
print(
datetime.now().strftime("[%Y-%m-%d %H:%M:%S]"),
*args,
**kwargs,
file=open(LOGFILE, "a", encoding="utf-8"),
)
================================================
FILE: maestro/helpers.py
================================================
# region imports
import atexit
import curses
import logging
import os
import subprocess
import threading
logging.disable(logging.CRITICAL)
import click
import msgspec
from getpass import getpass
from random import randint
from time import sleep, time
from typing import Iterable
from urllib.parse import quote, quote_plus
from maestro import config
from maestro.config import print_to_logfile
# endregion
class Song:
def __init__(self, song_id: int):
if song_id < 1:
raise ValueError("Song ID must be greater than 0.")
self._song_id = song_id
self._metadata = None
self._metadata_changed = False
self._parsed_lyrics = False
self._parsed_override_lyrics = False
self._parsed_translated_lyrics = False
atexit.register(self._save)
def reset(self):
self._metadata = None
self._metadata_changed = False
self._parsed_lyrics = False
self._parsed_override_lyrics = False
self._parsed_translated_lyrics = False
def __eq__(self, value):
if not isinstance(value, type(self)):
return False
return value.song_id == self.song_id
def __hash__(self):
return hash(self.song_id)
def __repr__(self):
return f"Song(ID={self.song_id})"
@property
def override_lyrics_path(self):
return os.path.join(
config.OVERRIDE_LYRICS_DIR, f"{self.song_title}.lrc"
)
@property
def translated_lyrics_path(self):
return os.path.join(
config.TRANSLATED_LYRICS_DIR, f"{self.song_title}.lrc"
)
@property
def song_id(self):
return self._song_id
@property
def song_file(self):
"""e.g. song.mp3"""
return SONG_DATA[self._song_id]["filename"]
@property
def song_path(self):
"""e.g. /path/to/song.mp3"""
return os.path.join(config.settings["song_directory"], self.song_file)
@property
def song_title(self):
"""e.g. 'song'"""
return os.path.splitext(self.song_file)[0]
@song_title.setter
def song_title(self, v):
old_path = self.song_path
old_override_lyrics_path = self.override_lyrics_path
old_translated_lyrics_path = self.translated_lyrics_path
SONG_DATA[self._song_id]["filename"] = (
v + os.path.splitext(self.song_file)[1]
)
if os.path.exists(old_path):
os.rename(old_path, self.song_path)
if os.path.exists(old_override_lyrics_path):
os.rename(old_override_lyrics_path, self.override_lyrics_path)
if os.path.exists(old_translated_lyrics_path):
os.rename(old_translated_lyrics_path, self.translated_lyrics_path)
self._load_metadata()
self._metadata["tracktitle"] = v
self._metadata_changed = True
@property
def tags(self) -> set[str]:
return SONG_DATA[self._song_id]["tags"]
@tags.setter
def tags(self, v: set[str]):
SONG_DATA[self._song_id]["tags"] = v
@property
def clips(self) -> dict[str, list[int, int]]:
return SONG_DATA[self._song_id]["clips"]
@property
def set_clip(self) -> str:
return SONG_DATA[self._song_id]["set-clip"]
@set_clip.setter
def set_clip(self, v: str):
SONG_DATA[self._song_id]["set-clip"] = v
@property
def listen_times(self) -> dict[int | str, float]:
return SONG_DATA[self._song_id]["stats"]
def _load_metadata(self):
import music_tag
self._metadata = music_tag.load_file(self.song_path)
@property
def artist(self):
return self.get_metadata("artist") or "No Artist"
@artist.setter
def artist(self, v):
self.set_metadata("artist", v)
@property
def album(self):
return self.get_metadata("album") or "No Album"
@album.setter
def album(self, v):
self.set_metadata("album", v)
@property
def album_artist(self):
return self.get_metadata("albumartist") or "No Album Artist"
@album_artist.setter
def album_artist(self, v):
self.set_metadata("albumartist", v)
@property
def duration(self):
return self.get_metadata("#length")
@property
def artwork(self):
if self._metadata is None:
self._load_metadata()
return (
self._metadata["artwork"].first
if "artwork" in self._metadata
else None
)
@property
def raw_lyrics(self) -> str | None:
return self.get_metadata("lyrics")
@raw_lyrics.setter
def raw_lyrics(self, v):
if self._metadata is None:
self._load_metadata()
if v is None and "lyrics" in self._metadata:
del self._metadata["lyrics"]
else:
self._metadata["lyrics"] = v
self._parsed_lyrics = False
self._metadata_changed = True
@property
def raw_override_lyrics(self) -> str | None:
if not os.path.exists(self.override_lyrics_path):
return None
with open(self.override_lyrics_path, "r", encoding="utf-8") as f:
return f.read()
@raw_override_lyrics.setter
def raw_override_lyrics(self, v):
import safer
if v is None:
if os.path.exists(self.override_lyrics_path):
os.remove(self.override_lyrics_path)
else:
os.makedirs(config.OVERRIDE_LYRICS_DIR, exist_ok=True)
with safer.open(
self.override_lyrics_path, "w", encoding="utf-8"
) as f:
f.write(v)
self._parsed_override_lyrics = False
@property
def raw_translated_lyrics(self) -> str | None:
if not os.path.exists(self.translated_lyrics_path):
return None
with open(self.translated_lyrics_path, "r", encoding="utf-8") as f:
return f.read()
@raw_translated_lyrics.setter
def raw_translated_lyrics(self, v):
import safer
if v is None:
if os.path.exists(self.translated_lyrics_path):
os.remove(self.translated_lyrics_path)
else:
os.makedirs(config.TRANSLATED_LYRICS_DIR, exist_ok=True)
with safer.open(
self.translated_lyrics_path, "w", encoding="utf-8"
) as f:
f.write(v)
def _parse_lyrics(self, raw_lyrics):
if raw_lyrics is None:
return None
raw_lyrics_list = raw_lyrics.splitlines()
for line in raw_lyrics_list:
if line and not line.strip().startswith("["): # not LRC format
return raw_lyrics_list
import pylrc
return pylrc.parse(raw_lyrics) if raw_lyrics else None
@property
def parsed_lyrics(self):
if self._parsed_lyrics is False:
self._parsed_lyrics = self._parse_lyrics(self.raw_lyrics)
return self._parsed_lyrics
@property
def parsed_override_lyrics(self):
if self._parsed_override_lyrics is False:
self._parsed_override_lyrics = self._parse_lyrics(
self.raw_override_lyrics
)
return self._parsed_override_lyrics
@property
def parsed_translated_lyrics(self):
if self._parsed_translated_lyrics is False:
self._parsed_translated_lyrics = self._parse_lyrics(
self.raw_translated_lyrics
)
return self._parsed_translated_lyrics
def get_metadata(self, key, resolve=True):
"""
Get metadata value for `key`.
If 'resolve' is False, then a MetadataItem is returned instead of the
resolved value.
"""
if self._metadata is None:
self._load_metadata()
if key not in self._metadata:
return None
if resolve:
return self._metadata[key].first
return self._metadata[key]
def set_metadata(self, key, value):
if self._metadata is None:
self._load_metadata()
if key not in config.METADATA_KEYS:
raise ValueError(f"{key} is not a valid metadata key.")
if key.startswith("#"):
raise ValueError(f"{key} is not editable.")
if key == "tracktitle":
self.song_title = value # also change file names
elif key == "lyrics":
self.raw_lyrics = value # unset self._parsed_lyrics
elif value is None:
if key in self._metadata:
del self._metadata[key]
else:
self._metadata[key] = value
self._metadata_changed = True
def remove_from_data(self):
del SONG_DATA[self.song_id]
def _save(self):
if self._metadata_changed:
self._metadata.save()
class SongData:
def __init__(self):
self.songs = None
atexit.register(self._save)
def load(self):
self.songs = {}
with open(config.SONGS_INFO_PATH, "r", encoding="utf-8") as f:
s = f.read()
if not s:
return
d = msgspec.json.decode(s)
for k, v in d.items():
self.songs[int(k)] = v
if "tags" not in v:
v["tags"] = set()
else:
v["tags"] = set(v["tags"])
v["stats_"] = {}
for year in v["stats"]:
if year.isdigit():
v["stats_"][int(year)] = v["stats"][year]
else:
v["stats_"][year] = v["stats"][year]
v["stats"] = v.pop("stats_")
if "set-clip" not in v:
v["set-clip"] = "default"
def __getitem__(self, key):
if self.songs is None:
self.load()
return self.songs[key]
def __setitem__(self, key, value):
if self.songs is None:
self.load()
self.songs[key] = value
def __delitem__(self, key):
if self.songs is None:
self.load()
del self.songs[key]
def __iter__(self):
if self.songs is None:
self.load()
return iter(self.songs)
def items(self):
if self.songs is None:
self.load()
return self.songs.items()
def values(self):
if self.songs is None:
self.load()
return self.songs.values()
def _save(self):
import safer
if self.songs is not None:
with safer.open(config.SONGS_INFO_PATH, "wb") as f:
f.write(msgspec.json.encode(self.songs))
def add_song(self, filename, tags=None):
if tags is None:
tags = set()
if self.songs is None:
self.load()
if not self.songs:
song_id = 1
else:
song_id = max(self) + 1
self.songs[song_id] = {
"filename": os.path.split(filename)[1],
"tags": set(tags),
"clips": {},
"stats": {
config.CUR_YEAR: 0.0,
"total": 0.0,
},
"set-clip": "default",
}
song = Song(song_id)
song.set_metadata("tracktitle", song.song_title)
return song
SONG_DATA = SongData()
class Songs:
"""
Wrapper around dict of all `Song` objects.
"""
def __init__(self):
self._songs = None
self._song_data = SONG_DATA
def load(self):
self._songs = {Song(k) for k in self._song_data}
def __contains__(self, value: Song):
if self._songs is None:
self.load()
return value in self._songs
def __iter__(self) -> Iterable[Song]:
if self._songs is None:
self.load()
return iter(self._songs)
def __len__(self):
if self._songs is None:
self.load()
return len(self._songs)
SONGS = Songs()
def is_safe_username(url):
return quote(url, safe="") == url if url else False
def bounded_shuffle(lst, radius=-1):
"""
Randomly shuffle `lst`, but with the constraint that each element can only
move at most `radius` positions away from its original position.
To shuffle with no bounds, set `radius = -1`.
"""
n = len(lst)
if radius == -1:
radius = n
elif radius == 0:
return
index_at = list(range(n))
for i in range(n - 1, 0, -1):
j = randint(max(0, index_at[i] - radius), i)
index_at[j], index_at[i] = index_at[i], index_at[j]
lst[j], lst[i] = lst[i], lst[j]
def set_timeout(func, timeout, *args, **kwargs):
def wrapper():
sleep(timeout)
func()
threading.Thread(
target=wrapper, daemon=True, args=args, kwargs=kwargs
).start()
class Scroller:
def __init__(self, num_lines, win_size):
self.num_lines = num_lines
self.win_size = win_size
self.pos = 0
self.top = 0
def scroll_forward(self):
if self.pos < self.num_lines - 1:
if (
self.pos == self.halfway
and self.top < self.num_lines - self.win_size
):
self.top += 1
self.pos += 1
def scroll_backward(self):
if self.pos > 0:
if self.pos == self.halfway and self.top > 0:
self.top -= 1
self.pos -= 1
@property
def halfway(self):
return self.top + (self.win_size - 1) // 2
def resize(self, win_size=None):
if win_size is not None:
self.win_size = win_size
self.top = max(0, self.pos - (self.win_size - 1) // 2)
self.top = max(0, min(self.num_lines - self.win_size, self.top))
def refresh(self):
self.resize()
def fit_string_to_width(s, width, length_so_far):
if length_so_far + len(s) > width:
remaining_width = width - length_so_far
if remaining_width >= 3:
s = s[: (remaining_width - 3)] + "..."
else:
s = "." * remaining_width
length_so_far += len(s)
return s, length_so_far
def addstr_fit_to_width(stdscr, s, width, length_so_far, *args, **kwargs):
s, length_so_far = fit_string_to_width(s, width, length_so_far)
if s:
if length_so_far <= width:
stdscr.addstr(s, *args, **kwargs)
else:
stdscr.addstr(s[:-1], *args, **kwargs)
stdscr.insstr(s[-1], *args, **kwargs)
return length_so_far
class FFmpegProcessHandler:
def __init__(self, username, password):
self.process = None
self.username = username
self.password = password
def start(self):
threading.Thread(target=self._start, daemon=True).start()
def _start(self):
from spotdl.utils.ffmpeg import get_ffmpeg_path
self.process = subprocess.Popen(
# fmt: off
[
str(get_ffmpeg_path()),
"-re", # Read input at native frame rate
"-f", "s16le", # Raw PCM 16-bit little-endian audio
"-ar", str(config.STREAM_SAMPLE_RATE), # Set the audio sample rate
"-ac", "2", # Set the number of audio channels to 2 (stereo)
'-i', 'pipe:', # Input from stdin
"-f", "mp3", # Output format
# "-report", # DEBUG
f"icecast://{self.username}:{self.password}@{config.ICECAST_SERVER}:8000/{self.username}", # Azure-hosted maestro Icecast URL
],
# fmt: on
stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
def terminate(self):
if self.process is not None:
self.process.terminate()
self.process = None
def restart(self):
self.terminate()
self.start()
def write(self, chunk):
if self.process is not None:
try:
self.process.stdin.write(chunk)
except BrokenPipeError as e: # pylint: disable=unused-variable
print_to_logfile("FFmpeg processs error:", e)
class PlaybackHandler:
def __init__(
self,
stdscr: "curses._CursesWindow",
playlist: list[Song],
clip_mode,
visualize,
stream,
creds,
want_lyrics,
want_translated_lyrics,
):
from just_playback import Playback
self.stdscr = stdscr
self.scroller = Scroller(
len(playlist), stdscr.getmaxyx()[0] - 2 # -2 for status bar
)
self.playlist = playlist
self.i = 0
self._volume = 0
self.clip_mode = clip_mode
self.want_discord = False
self.want_vis = visualize # want to visualize
self._want_stream = stream # want to stream
self.username, self.password = creds
self.want_lyrics = want_lyrics
self.want_translated_lyrics = want_translated_lyrics and want_lyrics
self.playback = Playback()
self._paused = False
self.last_timestamp = 0
self.looping_current_song = config.LOOP_MODES["none"]
self.duration = 0
self.restarting = False
self.ending = False
self.prompting: None | tuple = None
self.clip = (0, 0)
self.italic = True
self.can_mac_now_playing = False
self.mac_now_playing = None
self.update_now_playing = False
self.discord_connected = 2
self.discord_rpc = None
self.discord_last_update = 0
self.can_update_discord = True # pypresence installed
self.discord_updating = False # lock
self._librosa = None
self.can_visualize = True
self.can_show_visualization = (
self.want_vis
# space to show visualization
and self.screen_height > config.VISUALIZER_HEIGHT + 5
)
self.audio_data = None
self.audio_data = {} # dict(song_id: (vis data, stream data))
self.audio_processing_thread = threading.Thread(
target=self._audio_processing_loop,
daemon=True,
)
self.audio_processing_thread.start()
self.compiled = None
self.ffmpeg_process = FFmpegProcessHandler(self.username, self.password)
if self.want_stream:
self.ffmpeg_process.start()
self.break_stream_loop = False
self.streaming_thread = threading.Thread(
target=self._streaming_loop,
daemon=True,
)
self.streaming_thread.start()
self.lyrics: list | None = None
self.translated_lyrics: list | None = None
self.lyrics_scroller = Scroller(0, 0)
self.lyrics_width = 50
self.lyric_pos = None
self.show_help = False
self.help_pos = 0
self._focus = 0 # 0: queue, 1: lyrics
def _load_audio(self, path, sr):
import numpy as np
# shape = (# channels, # frames)
audio_data = self._librosa.load(path, mono=False, sr=sr)[0]
if len(audio_data.shape) == 1: # mono -> stereo
audio_data = np.repeat([audio_data], 2, axis=0)
elif audio_data.shape[0] == 1: # mono -> stereo
audio_data = np.repeat(audio_data, 2, axis=0)
elif audio_data.shape[0] == 6: # 5.1 -> stereo
audio_data = np.delete(audio_data, (1, 3, 4, 5), axis=0)
return audio_data
def _audio_processing_loop(self):
import numpy as np
try:
from librosa import load, stft, amplitude_to_db
self._librosa = type(
"librosa",
(),
{
"load": staticmethod(load),
"stft": staticmethod(stft),
"amplitude_to_db": staticmethod(amplitude_to_db),
},
)
except ImportError:
self.can_visualize = False
self.can_show_visualization = False
print_to_logfile(
"Librosa not installed. Visualization will be disabled."
)
while True:
try:
keys_to_delete = []
for k in self.audio_data:
if k not in self.playlist[self.i : self.i + 5]:
keys_to_delete.append(k)
for k in keys_to_delete:
del self.audio_data[k]
for i in range(self.i, min(self.i + 5, len(self.playlist))):
processing_song = self.playlist[i]
if self.song != processing_song and (
self.song not in self.audio_data
or (
self.want_vis
and self.audio_data[self.song][0] is None
)
or (
self.want_stream
and self.audio_data[self.song][1] is None
)
):
break
if processing_song in self.audio_data and (
(
self.audio_data[processing_song][0] is not None
or not self.want_vis
)
and (
self.audio_data[processing_song][1] is not None
or not self.want_stream
)
or self._librosa is None
):
continue
processing_song_path = (
os.path.join( # NOTE: NOT SAME AS self.song_path
config.settings["song_directory"],
self.playlist[i].song_file,
)
)
if processing_song not in self.audio_data:
self.audio_data[processing_song] = [
(
self._librosa.amplitude_to_db(
np.abs(
self._librosa.stft(
self._load_audio(
processing_song_path,
sr=config.VIS_SAMPLE_RATE,
)
)
),
ref=np.max,
)
+ 80
if self.want_vis and self.can_visualize
else None
),
(
np.int16(
self._load_audio(
processing_song_path,
sr=config.STREAM_SAMPLE_RATE,
)
* (2**15 - 1)
* 0.5 # reduce volume (avoid clipping)
) # convert to 16-bit PCM
if self.want_stream
else None
),
]
else:
if (
self.audio_data[processing_song][0] is None
and self.want_vis
and self.can_visualize
):
self.audio_data[processing_song][0] = (
self._librosa.amplitude_to_db(
np.abs(
self._librosa.stft(
self._load_audio(
processing_song_path,
sr=config.VIS_SAMPLE_RATE,
)
)
),
ref=np.max,
)
+ 80
)
if (
self.audio_data[processing_song][1] is None
and self.want_stream
):
self.audio_data[processing_song][1] = np.int16(
self._load_audio(
processing_song_path,
sr=config.STREAM_SAMPLE_RATE,
)
* (2**15 - 1)
* 0.5 # reduce volume (avoid clipping)
) # convert to 16-bit PCM
except: # hacky fix # pylint: disable=bare-except
pass
sleep(1)
def _streaming_loop(self):
while True:
if (
self.want_stream
and self.username is not None
and self.audio_data is not None
and self.song in self.audio_data
and self.audio_data[self.song][1] is not None
and self.playback is not None
# is 0 for a while after resuming, and is -1 if playback is
# inactive or file is not loaded
and self.playback.curr_pos > 0
):
for fpos in range(
int(self.playback.curr_pos * config.STREAM_SAMPLE_RATE),
self.audio_data[self.song][1].shape[1],
config.STREAM_CHUNK_SIZE,
):
try:
# print_to_logfile(
# self.song_id,
# fpos / config.STREAM_SAMPLE_RATE,
# self.playback.curr_pos,
# ) # DEBUG
self.ffmpeg_process.write(
self.audio_data[self.song][1][
:,
fpos : fpos + config.STREAM_CHUNK_SIZE,
]
.reshape((-1,), order="F")
.tobytes()
if not self.paused
else b"\x00" * 4 * config.STREAM_CHUNK_SIZE
)
except KeyError as e:
print_to_logfile("KeyError in streaming loop:", e)
self.update_stream_metadata()
if self.break_stream_loop:
self.break_stream_loop = False
break
sleep(0.01)
# region properties
@property
def paused(self):
return self._paused
@paused.setter
def paused(self, value):
self._paused = value
if self.want_stream:
self.threaded_update_icecast_metadata()
@property
def volume(self):
return self._volume
@volume.setter
def volume(self, v):
self._volume = v
if self.playback is not None:
self.playback.set_volume(v / 100)
@property
def want_stream(self):
return self._want_stream
@want_stream.setter
def want_stream(self, value):
if self._librosa is None:
value = False
self._want_stream = value
@property
def song(self):
return self.playlist[self.i]
@property
def song_id(self):
return self.song.song_id
@property
def song_file(self):
return self.song.song_file
@property
def song_path(self):
return self.song.song_path
@property
def song_title(self):
return self.song.song_title
@property
def song_artist(self):
return self.song.artist
@property
def song_album(self):
return self.song.album
@property
def album_artist(self):
return self.song.album_artist
@property
def artwork(self):
return (
self.song.artwork.raw_thumbnail([1024, 1024])
if self.song.artwork
else None
)
@property
def screen_height(self):
return self.stdscr.getmaxyx()[0]
@property
def screen_width(self):
return self.stdscr.getmaxyx()[1]
@property
def can_show_lyrics(self):
return self.want_lyrics and self.lyrics is not None
@property
def can_show_translated_lyrics(self):
return (
self.want_translated_lyrics and self.translated_lyrics is not None
)
@property
def focus(self):
return self._focus if self.can_show_lyrics else 0
@focus.setter
def focus(self, value):
self._focus = value if self.can_show_lyrics else 0
# endregion
def seek(self, pos):
if self.playback is not None:
pos = max(self.clip[0] if self.clip_mode else 0, pos)
self.playback.seek(pos)
self.break_stream_loop = True
if self.can_mac_now_playing and self.mac_now_playing is not None:
self.mac_now_playing.pos = round(pos)
self.update_now_playing = True
self.last_timestamp = pos
def scroll_forward(self):
if self.show_help:
self.help_pos += 1
elif self.focus == 1:
if self.lyric_pos is None:
self.lyric_pos = self.lyrics_scroller.pos
self.lyric_pos = min(self.lyric_pos + 1, len(self.lyrics) - 1)
elif self.focus == 0:
self.scroller.scroll_forward()
def scroll_backward(self):
if self.show_help:
self.help_pos -= 1
elif self.focus == 1:
if self.lyric_pos is None:
self.lyric_pos = self.lyrics_scroller.pos
self.lyric_pos = max(self.lyric_pos - 1, 0)
elif self.focus == 0:
self.scroller.scroll_backward()
def snap_back(self):
if self.focus == 1:
self.lyric_pos = None
elif self.focus == 0:
self.scroller.pos = self.i
self.scroller.refresh()
def set_volume(self, v):
"""Set volume w/o changing self.volume."""
self.playback.set_volume(v / 100)
def quit(self):
self.playback.stop()
if self.ffmpeg_process is not None:
self.ffmpeg_process.terminate()
if self.discord_rpc is not None:
self.discord_rpc.close()
def prompting_delete_char(self):
if self.prompting[1] > 0:
self.prompting = (
self.prompting[0][: self.prompting[1] - 1]
+ self.prompting[0][self.prompting[1] :],
self.prompting[1] - 1,
self.prompting[2],
)
def update_screen(self):
self.output(self.playback.curr_pos)
def connect_to_discord(self):
try:
from pypresence import Client as DiscordRPCClient
except ImportError:
print_to_logfile(
"pypresence not installed. Discord presence will be disabled."
)
self.can_update_discord = False
discord_rpc = DiscordRPCClient(client_id=config.DISCORD_ID)
discord_rpc.start()
return discord_rpc
def update_discord_metadata(self):
if self.discord_updating:
return
if self.can_update_discord and self.want_discord:
self.discord_updating = True
t = time()
if self.discord_last_update + 15 > t:
sleep(15 - (t - self.discord_last_update))
song_name, artist_name, album_name = "", "", ""
# minimum 2 characters (Discord requirement)
new_song_name = self.song_title.ljust(2)
new_artist_name = "by " + self.song_artist
new_album_name = self.song_album.ljust(2)
if (
new_song_name != song_name
or new_artist_name != artist_name
or new_album_name != album_name
):
song_name = new_song_name
artist_name = new_artist_name
album_name = new_album_name
d = dict(
details=song_name,
state=artist_name,
large_image=(
f"{config.IMAGE_URL}/{self.username}?_={time()}"
if self.username
else "maestro-icon"
),
small_image="maestro-icon-small",
large_text=album_name,
buttons=(
[
{
"label": "Listen Along",
"url": f"{config.MAESTRO_SITE}/listen-along/{self.username}",
}
]
if self.username and self.want_stream
else None
),
)
d = {k: v for k, v in d.items() if v is not None}
try:
self.discord_rpc.set_activity(**d)
self.discord_last_update = time()
except Exception as e: # pylint: disable=bare-except
print_to_logfile("Discord update error:", e)
song_name, artist_name, album_name = "", "", ""
self.discord_connected = 2
try:
self.discord_rpc = self.connect_to_discord()
self.discord_connected = 1
self.discord_updating = False
self.update_discord_metadata()
except Exception as err:
print_to_logfile("Discord connection error:", err)
self.discord_connected = 0
finally:
self.discord_updating = False
def update_mac_now_playing_metadata(self):
from maestro.icon import img as default_artwork
if self.can_mac_now_playing:
self.mac_now_playing.paused = False
self.mac_now_playing.pos = 0
self.mac_now_playing.length = self.duration
self.mac_now_playing.cover = (
self.artwork if self.artwork else default_artwork
)
multiprocessing_put_word(
self.mac_now_playing.title_queue,
self.song_title,
)
multiprocessing_put_word(
self.mac_now_playing.artist_queue,
self.song_artist,
)
self.update_now_playing = True
def update_icecast_metadata(self):
import requests
return requests.post(
config.UPDATE_METADATA_URL,
data={
"mount": self.username,
"song": quote_plus(self.song_title),
"artist": quote_plus(self.song_artist),
"album": quote_plus(self.song_album),
"albumartist": quote_plus(self.album_artist),
"paused": int(self.paused),
},
auth=(self.username, self.password),
timeout=5,
)
def icecast_metadata_update_loop(self):
success = False
last_metadata_update_attempt = 0
while not success:
t = time()
if t - last_metadata_update_attempt > 5:
try:
response = self.update_icecast_metadata()
if response.ok:
last_metadata_update_attempt = 0
success = True
else:
raise Exception(
f"Icecast Server error {response.status_code}: {response.text}"
)
except Exception as e: # retry in 5 seconds
print_to_logfile("Update metadata failed:", e)
last_metadata_update_attempt = t
sleep(0.01)
def threaded_update_icecast_metadata(self):
threading.Thread(
target=self.icecast_metadata_update_loop, daemon=True
).start()
def update_stream_metadata(self): # artwork + icecast metadata
import requests
self.break_stream_loop = True
if (
self.discord_connected
or self.want_stream
and self.username is not None
):
if not requests.post(
config.UPDATE_ARTWORK_URL,
params={"mount": self.username},
files={"artwork": self.artwork},
auth=(self.username, self.password),
timeout=5,
).ok:
print_to_logfile("Failed to update artwork.")
if self.want_stream:
self.threaded_update_icecast_metadata()
def update_metadata(self):
def f():
self.update_mac_now_playing_metadata()
self.update_stream_metadata()
self.update_discord_metadata()
threading.Thread(target=f, daemon=True).start()
def initialize_discord(self):
self.want_discord = True
try:
self.discord_rpc = self.connect_to_discord()
self.discord_connected = 1
except Exception as e: # pylint: disable=broad-except,unused-variable
self.discord_connected = 0
print_to_logfile("Discord connection error:", e)
def threaded_initialize_discord(self):
threading.Thread(target=self.initialize_discord, daemon=True).start()
def output(self, pos):
from maestro.jit_funcs import render
screen_height = self.screen_height
if self.want_lyrics:
screen_width = self.screen_width - self.lyrics_width
else:
screen_width = self.screen_width
self.can_show_visualization = (
self.want_vis
and self.can_visualize
and screen_height > config.VISUALIZER_HEIGHT + 5
)
self.scroller.resize(
screen_height
- 3 # -3 for status bar
- 1 # -1 for header
- (self.prompting != None) # - add mode
# - visualizer
- (config.VISUALIZER_HEIGHT if self.can_show_visualization else 0)
)
if self.clip_mode:
pos -= self.clip[0]
self.stdscr.erase()
length_so_far = 0
if self.want_discord:
if self.discord_connected == 2:
length_so_far = addstr_fit_to_width(
self.stdscr,
"Connecting to Discord ... ",
screen_width,
length_so_far,
curses.color_pair(12),
)
elif self.discord_connected == 1:
length_so_far = addstr_fit_to_width(
self.stdscr,
"Discord connected! ",
screen_width,
length_so_far,
curses.color_pair(17),
)
else:
length_so_far = addstr_fit_to_width(
self.stdscr,
"Failed to connect to Discord. ",
screen_width,
length_so_far,
curses.color_pair(14),
)
visualize_message = ""
visualize_color = 12
if self.want_vis:
if self.audio_data is None and self.can_visualize:
self.audio_processing_thread = threading.Thread(
target=self._audio_processing_loop,
daemon=True,
)
self.audio_data = {}
self.audio_processing_thread.start()
if not self.can_visualize:
visualize_message = "Librosa is required for visualization."
visualize_color = 14
elif not self.can_show_visualization:
visualize_message = "Window too small for visualization."
visualize_color = 14
elif self.song not in self.audio_data:
visualize_message = "Loading visualization..."
visualize_color = 12
elif not self.compiled:
visualize_message = "Compiling renderer..."
visualize_color = 12
if self.want_stream:
prefix = " " if self.want_discord else ""
if self.username:
long_stream_message = (
prefix
+ f"Streaming at {config.MAESTRO_SITE}/listen-along/{self.username}"
)
short_stream_message = prefix + f"Streaming as {self.username}!"
if (
length_so_far
+ len(long_stream_message)
+ 2
+ (len(visualize_message) if visualize_message else -2)
< screen_width
):
length_so_far = addstr_fit_to_width(
self.stdscr,
long_stream_message,
screen_width,
length_so_far,
curses.color_pair(16),
)
else:
length_so_far = addstr_fit_to_width(
self.stdscr,
short_stream_message,
screen_width,
length_so_far,
curses.color_pair(16),
)
else:
length_so_far = addstr_fit_to_width(
self.stdscr,
prefix + "Please log in to stream.",
screen_width,
length_so_far,
curses.color_pair(14),
)
length_so_far = addstr_fit_to_width(
self.stdscr,
" " * (screen_width - length_so_far - len(visualize_message))
+ visualize_message,
screen_width,
length_so_far,
curses.color_pair(visualize_color),
)
self.stdscr.move(1, 0)
song_display_color = 5 if self.looping_current_song else 3
progress_bar_display_color = (
17 if (self.clip_mode and self.clip != (0, self.duration)) else 15
)
# for aligning song names
longest_song_id_length = max(
len(str(song.song_id)) for song in self.playlist
)
for j in range(
self.scroller.top, self.scroller.top + self.scroller.win_size
):
if j <= len(self.playlist) - 1:
length_so_far = 0
length_so_far = addstr_fit_to_width(
self.stdscr,
" "
* (
longest_song_id_length
- len(str(self.playlist[j].song_id))
)
+ f"{self.playlist[j].song_id} ",
screen_width,
length_so_far,
curses.color_pair(2),
)
if j == self.i:
length_so_far = addstr_fit_to_width(
self.stdscr,
f"{self.playlist[j].song_title} ",
screen_width,
length_so_far,
curses.color_pair(song_display_color) | curses.A_BOLD,
)
else:
length_so_far = addstr_fit_to_width(
self.stdscr,
f"{self.playlist[j].song_title} ",
screen_width,
length_so_far,
(
curses.color_pair(4)
if (j == self.scroller.pos)
else curses.A_NORMAL
),
)
length_so_far = addstr_fit_to_width(
self.stdscr,
f"{', '.join(self.playlist[j].tags)}",
screen_width,
length_so_far,
curses.color_pair(2),
)
self.stdscr.move((j - self.scroller.top) + 2, 0)
if self.prompting is not None:
# pylint: disable=unsubscriptable-object
if self.prompting[2] == config.PROMPT_MODES["tag"]:
adding_song_length = addstr_fit_to_width(
self.stdscr,
"Add tag(s) to songs: " + self.prompting[0],
screen_width,
0,
)
else:
adding_song_length = addstr_fit_to_width(
self.stdscr,
config.PROMPT_MODES_LIST[self.prompting[2]].capitalize()
+ " song: "
+ self.prompting[0],
screen_width,
0,
)
self.stdscr.move(self.stdscr.getyx()[0] + 1, 0)
length_so_far = 0
length_so_far = addstr_fit_to_width(
self.stdscr,
("| " if self.paused else "> ") + f"({self.song_id}) ",
screen_width,
length_so_far,
curses.color_pair(song_display_color + 10),
)
length_so_far = addstr_fit_to_width(
self.stdscr,
f"{self.song_title} ",
screen_width,
length_so_far,
curses.color_pair(song_display_color + 10) | curses.A_BOLD,
)
length_so_far = addstr_fit_to_width(
self.stdscr,
"%d/%d " % (self.i + 1, len(self.playlist)),
screen_width,
length_so_far,
curses.color_pair(12),
)
length_so_far = addstr_fit_to_width(
self.stdscr,
f"{'c' if self.clip_mode else ' '}",
screen_width,
length_so_far,
curses.color_pair(17) | curses.A_BOLD,
)
loop_char = " "
if self.looping_current_song == config.LOOP_MODES["one"]:
loop_char = "l"
elif self.looping_current_song == config.LOOP_MODES["inf"]:
loop_char = "L"
length_so_far = addstr_fit_to_width(
self.stdscr,
loop_char,
screen_width,
length_so_far,
curses.color_pair(15) | curses.A_BOLD,
)
volume_line_length_so_far = addstr_fit_to_width(
self.stdscr,
f"{'e' if self.ending else ' '} ",
screen_width,
length_so_far,
curses.color_pair(14) | curses.A_BOLD,
)
addstr_fit_to_width(
self.stdscr,
" " * (screen_width - volume_line_length_so_far - 1),
screen_width,
volume_line_length_so_far,
curses.color_pair(16),
)
self.stdscr.insstr( # hacky fix for curses bug
" ",
curses.color_pair(16),
)
self.stdscr.move(
screen_height
- 2
- (config.VISUALIZER_HEIGHT if self.can_show_visualization else 0),
0,
)
addstr_fit_to_width(
self.stdscr,
" " * (screen_width - 1),
screen_width,
0,
curses.color_pair(16),
)
self.stdscr.insstr( # hacky fix for curses bug
" ",
curses.color_pair(16),
)
self.stdscr.move(
self.stdscr.getyx()[0],
0,
)
song_data_length_so_far = addstr_fit_to_width(
self.stdscr,
self.song_artist + " - ",
screen_width,
0,
curses.color_pair(12),
)
if self.italic:
try:
song_data_length_so_far = addstr_fit_to_width(
self.stdscr,
self.song_album,
screen_width,
song_data_length_so_far,
curses.color_pair(12) | curses.A_ITALIC,
)
except: # pylint: disable=bare-except
self.italic = False
print_to_logfile("Failed to italicize text in curses.")
if not self.italic:
song_data_length_so_far = addstr_fit_to_width(
self.stdscr,
self.song_album,
screen_width,
song_data_length_so_far,
curses.color_pair(12),
)
addstr_fit_to_width(
self.stdscr,
f" ({self.album_artist})",
screen_width,
song_data_length_so_far,
curses.color_pair(12),
)
self.stdscr.move(
screen_height
- (config.VISUALIZER_HEIGHT if self.can_show_visualization else 0)
- 1,
0,
)
length_so_far = 0
secs = int(pos)
length_so_far = addstr_fit_to_width(
self.stdscr,
f"{format_seconds(secs)} / {format_seconds(self.duration)} ",
screen_width,
length_so_far,
curses.color_pair(progress_bar_display_color),
)
if not length_so_far >= screen_width:
if (
screen_width - length_so_far
>= config.MIN_PROGRESS_BAR_WIDTH + 2
):
progress_bar_width = screen_width - length_so_far - 2
bar = "|"
progress_block_width = (
progress_bar_width * 8 * pos
) // self.duration
for _ in range(progress_bar_width):
if progress_block_width > 8:
bar += config.HORIZONTAL_BLOCKS[8]
progress_block_width -= 8
elif progress_block_width > 0:
bar += config.HORIZONTAL_BLOCKS[progress_block_width]
progress_block_width = 0
else:
bar += " "
self.stdscr.addstr(
bar, curses.color_pair(progress_bar_display_color)
)
self.stdscr.insstr( # hacky fix for curses bug
"|", curses.color_pair(progress_bar_display_color)
)
else:
self.stdscr.addstr(
" " * (screen_width - length_so_far - 1),
curses.color_pair(16),
)
self.stdscr.insstr( # hacky fix for curses bug
" ", curses.color_pair(16)
)
# right align volume bar
if not volume_line_length_so_far >= screen_width:
self.stdscr.move(
screen_height
- 3
- (
config.VISUALIZER_HEIGHT
if self.can_show_visualization
else 0
),
volume_line_length_so_far,
)
if (
screen_width - volume_line_length_so_far
>= config.MIN_VOLUME_BAR_WIDTH + 10
):
bar = f"{str(int(self.volume)).rjust(3)}/100 |"
volume_bar_width = min(
screen_width - volume_line_length_so_far - (len(bar) + 1),
config.MAX_VOLUME_BAR_WIDTH,
)
block_width = int(volume_bar_width * 8 * self.volume / 100)
for _ in range(volume_bar_width):
if block_width > 8:
bar += config.HORIZONTAL_BLOCKS[8]
block_width -= 8
elif block_width > 0:
bar += config.HORIZONTAL_BLOCKS[block_width]
block_width = 0
else:
bar += " "
bar += "|"
bar = bar.rjust(screen_width - volume_line_length_so_far)
self.stdscr.addstr(bar, curses.color_pair(16))
elif screen_width - volume_line_length_so_far >= 7:
self.stdscr.addstr(
f"{str(int(self.volume)).rjust(3)}/100".rjust(
screen_width - volume_line_length_so_far
),
curses.color_pair(16),
)
if self.can_show_visualization:
if self.clip_mode:
pos += self.clip[0]
self.stdscr.move(
screen_height
- (
config.VISUALIZER_HEIGHT
if self.can_show_visualization
else 0
),
0,
)
if (
self.song not in self.audio_data
or self.audio_data[self.song][0] is None
):
self.stdscr.addstr(
(
(" " * (screen_width - 1) + "\n")
* config.VISUALIZER_HEIGHT
).rstrip()
)
elif not self.compiled:
if self.compiled is None:
self.compiled = False
def thread_func():
vdata = self.audio_data[self.song][0]
render(
screen_width,
vdata,
min(round(pos * config.FPS), vdata.shape[2] - 1),
config.VISUALIZER_HEIGHT,
)
self.compiled = True
threading.Thread(target=thread_func, daemon=True).start()
self.stdscr.addstr(
(
(" " * (screen_width - 1) + "\n")
* config.VISUALIZER_HEIGHT
).rstrip()
)
elif self.compiled:
vdata = self.audio_data[self.song][0]
rendered_lines = render(
screen_width,
vdata,
min(
round(pos * config.FPS),
# fmt: off
vdata.shape[2] - 1,
),
config.VISUALIZER_HEIGHT,
)
for i in range(len(rendered_lines)):
self.stdscr.addstr(rendered_lines[i][:-1])
self.stdscr.insstr(rendered_lines[i][-1])
if i < len(rendered_lines) - 1:
self.stdscr.move(self.stdscr.getyx()[0] + 1, 0)
if self.can_show_lyrics:
from grapheme import graphemes
# self.stdscr.redrawwin() # workaround for foreign characters
num_lines = min(
len(self.lyrics),
(
screen_height // 2
if self.can_show_translated_lyrics
else screen_height - 1
),
)
cur_lyric_i = None
is_timed = is_timed_lyrics(self.lyrics)
if is_timed:
for i, lyric in enumerate(self.lyrics):
if lyric.time > pos:
cur_lyric_i = i - 1
break
if cur_lyric_i is None:
cur_lyric_i = len(self.lyrics) - 1
self.lyrics_scroller.pos = (
self.lyric_pos or cur_lyric_i or self.lyrics_scroller.pos
)
self.lyrics_scroller.resize(num_lines)
if not is_timed:
self.lyrics_scroller.pos = min(
max(
self.lyrics_scroller.win_size // 2,
self.lyrics_scroller.pos,
),
self.lyrics_scroller.num_lines
- self.lyrics_scroller.win_size // 2,
)
self.stdscr.move(0, screen_width)
lyric_focus_msg = (
f"Focus: {'lyrics' if self.focus == 1 else 'queue'}"
)
addstr_fit_to_width(
self.stdscr,
" " * (self.lyrics_width - len(lyric_focus_msg))
+ lyric_focus_msg,
self.lyrics_width,
0,
curses.color_pair(19),
)
for i in range(
self.lyrics_scroller.top, self.lyrics_scroller.top + num_lines
):
vertical_pos = (i - self.lyrics_scroller.top) * (
2 if self.can_show_translated_lyrics else 1
) + 1
style = curses.color_pair(9)
# pylint: disable=unsubscriptable-object
lyric_text = get_lyric(self.lyrics[i]).strip()
if cur_lyric_i is not None:
if i == cur_lyric_i:
style = curses.color_pair(9) | curses.A_BOLD
self.stdscr.move(
vertical_pos,
screen_width + config.LYRIC_PADDING - 2,
)
self.stdscr.addstr("> ", style)
elif i == self.lyric_pos:
style = curses.color_pair(4)
self.stdscr.move(
vertical_pos,
screen_width + config.LYRIC_PADDING - 2,
)
self.stdscr.addstr("> ", style)
elif i < cur_lyric_i:
style = curses.color_pair(9) | curses.A_DIM
try:
width = 0
for g in graphemes(lyric_text):
width += 1
# NOTE: why -1? No one knows.
if width < self.lyrics_width - config.LYRIC_PADDING:
self.stdscr.move(
vertical_pos,
screen_width + config.LYRIC_PADDING + width - 1,
)
self.stdscr.addstr(g, style)
else:
break
except curses.error: # bottom right corner errors
break
if (
self.can_show_translated_lyrics
and i < len(self.translated_lyrics)
and self.stdscr.getyx()[0] < screen_height
):
style = curses.A_DIM
if i == cur_lyric_i:
style |= curses.A_BOLD
elif i == self.lyric_pos and is_timed:
style |= curses.color_pair(4)
try:
width = 0
for g in graphemes(
get_lyric(self.translated_lyrics[i]).strip()
):
width += 1
if (
width
< self.lyrics_width - config.LYRIC_PADDING - 1
):
self.stdscr.move(
vertical_pos + 1,
screen_width
+ config.LYRIC_PADDING
+ 1
+ width
- 1,
)
self.stdscr.addstr(g, style)
else:
break
except curses.error: # bottom right corner errors
break
elif self.want_lyrics:
self.stdscr.move(0, screen_width + config.LYRIC_PADDING)
addstr_fit_to_width(
self.stdscr,
"No lyrics found.",
self.lyrics_width - config.LYRIC_PADDING,
0,
curses.color_pair(4),
)
if self.show_help:
l = 15
r = self.screen_width - l
t = 5
b = self.screen_height - t
if l < r and t < b:
# draw border
self.stdscr.addch(t, l, curses.ACS_ULCORNER)
self.stdscr.addch(t, r, curses.ACS_URCORNER)
self.stdscr.addch(b, l, curses.ACS_LLCORNER)
self.stdscr.addch(b, r, curses.ACS_LRCORNER)
for x in range(l + 1, r):
self.stdscr.addch(t, x, curses.ACS_HLINE)
self.stdscr.addch(b, x, curses.ACS_HLINE)
for y in range(t + 1, b):
self.stdscr.addch(y, l, curses.ACS_VLINE)
self.stdscr.addch(y, r, curses.ACS_VLINE)
# draw text
self.stdscr.move(t + 1, l + 1)
i = max(
0,
min(self.help_pos, len(config.PLAY_CONTROLS) - (b - t - 1)),
)
while self.stdscr.getyx()[0] < b:
if i < len(config.PLAY_CONTROLS):
key, desc = config.PLAY_CONTROLS[i]
length_so_far = addstr_fit_to_width(
self.stdscr,
key
+ " " * (config.INDENT_CONTROL_DESC - len(key))
+ " ",
r - l - 1,
0,
curses.color_pair(18) | curses.A_BOLD,
)
length_so_far = addstr_fit_to_width(
self.stdscr,
desc
+ " " * (r - l - 1 - length_so_far - len(desc)),
r - l - 1,
length_so_far,
curses.color_pair(18),
)
i += 1
self.stdscr.move(self.stdscr.getyx()[0] + 1, l + 1)
else:
self.stdscr.addstr(" " * (r - l - 1))
if self.prompting is not None:
# pylint: disable=unsubscriptable-object
self.stdscr.move(
screen_height
- (
config.VISUALIZER_HEIGHT
if self.can_show_visualization
else 0
)
- 4, # 4 lines for status bar + adding entry
adding_song_length
+ (self.prompting[1] - len(self.prompting[0])),
)
self.stdscr.refresh()
def init_curses(stdscr):
curses.start_color()
curses.use_default_colors()
# region colors
curses.init_pair(1, curses.COLOR_WHITE, -1)
curses.init_pair(2, curses.COLOR_BLACK + 8, -1) # bright black
curses.init_pair(3, curses.COLOR_BLUE, -1)
curses.init_pair(4, curses.COLOR_RED, -1)
curses.init_pair(5, curses.COLOR_YELLOW, -1)
curses.init_pair(6, curses.COLOR_GREEN, -1)
curses.init_pair(7, curses.COLOR_MAGENTA, -1)
curses.init_pair(9, curses.COLOR_CYAN, -1)
curses.init_pair(11, curses.COLOR_WHITE, curses.COLOR_BLACK)
curses.init_pair(12, curses.COLOR_BLACK + 8, curses.COLOR_BLACK)
curses.init_pair(13, curses.COLOR_BLUE, curses.COLOR_BLACK)
curses.init_pair(14, curses.COLOR_RED, curses.COLOR_BLACK)
curses.init_pair(15, curses.COLOR_YELLOW, curses.COLOR_BLACK)
curses.init_pair(16, curses.COLOR_GREEN, curses.COLOR_BLACK)
curses.init_pair(17, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
curses.init_pair(18, -1, curses.COLOR_BLACK)
curses.init_pair(19, curses.COLOR_CYAN, curses.COLOR_BLACK)
# endregion
curses.curs_set(False)
stdscr.nodelay(True)
try:
curses.set_escdelay(25) # 25 ms
except: # pylint: disable=bare-except
pass
class SongParamType(click.ParamType):
name = "song"
def convert(self, value, param, ctx) -> Song:
if value.isdecimal():
value = int(value)
if type(value) == int:
song = Song(value)
if song in SONGS:
return song
self.fail(f"No song found with ID {value}.", param, ctx)
if not value.isdecimal():
results = search_song(value)
if not any(results):
self.fail(f"No song found matching '{value}'.", param, ctx)
for result in results:
if len(result) == 1:
return result[0]
if len(result) > 1:
break
if param is not None: # called by click
for song in sum(results, []):
print_entry(song, value)
self.fail("Multiple songs found", param, ctx)
self.fail("Invalid song argument", param, ctx)
CLICK_SONG = SongParamType()
def yt_embed_artwork(yt_dlp_info, crop):
import music_tag
import requests
yt_dlp_info["thumbnails"].sort(key=lambda d: d["preference"])
best_thumbnail = yt_dlp_info["thumbnails"][-1] # default thumbnail
if "width" not in best_thumbnail:
# different so that any square thumbnail is chosen
best_thumbnail["width"] = 0
best_thumbnail["height"] = -1
for thumbnail in yt_dlp_info["thumbnails"][:-1]:
# print(thumbnail)
if "height" in thumbnail and (
(
thumbnail["height"] == thumbnail["width"]
and (
(best_thumbnail["width"] != best_thumbnail["height"])
or (
thumbnail["height"] >= best_thumbnail["height"]
and thumbnail["width"] >= best_thumbnail["width"]
)
)
)
or (
thumbnail["height"] >= best_thumbnail["height"]
and (thumbnail["width"] >= best_thumbnail["width"])
and (best_thumbnail["width"] != best_thumbnail["height"])
)
):
best_thumbnail = thumbnail
response = requests.get(best_thumbnail["url"], timeout=5)
image_data = response.content
if best_thumbnail["width"] != best_thumbnail["height"] and crop:
from io import BytesIO
from PIL import Image
image = Image.open(BytesIO(image_data))
width, height = image.size
if width > height:
image = image.crop(
(
(width - height) // 2,
0,
(width + height) // 2,
height,
)
)
elif height > width:
image = image.crop(
(
0,
(height - width) // 2,
width,
(height + width) // 2,
)
)
image_data = BytesIO()
image.save(image_data, format="JPEG")
image_data = image_data.getvalue()
m = music_tag.load_file(yt_dlp_info["requested_downloads"][0]["filepath"])
m["artwork"] = image_data
m.save()
def clip_editor(stdscr, song: Song, name, start=None, end=None):
from just_playback import Playback
playback = Playback()
playback.load_file(song.song_path)
init_curses(stdscr)
if name in song.clips:
clip_start, clip_end = song.clips[name]
else:
clip_start, clip_end = 0, playback.duration
if start is not None:
clip_start = start
if end is not None:
clip_end = end
editing_start = True
change_output = True
playback.play()
playback.pause()
playback.seek(clip_start)
last_timestamp = playback.curr_pos
while True:
if playback.curr_pos >= clip_end:
playback.pause()
change_output = change_output or (
(playback.curr_pos - last_timestamp)
>= (playback.duration / (8 * (stdscr.getmaxyx()[1] - 2)))
)
if change_output:
clip_editor_output(
stdscr,
song,
playback.curr_pos,
playback.paused,
playback.duration,
clip_start,
clip_end,
editing_start,
)
c = stdscr.getch()
next_c = stdscr.getch()
while next_c != -1:
c, next_c = next_c, stdscr.getch()
if c == -1:
continue
change_output = False
if editing_start:
if c == curses.KEY_LEFT:
change_output = True
playback.pause()
clip_start = max(0, clip_start - 0.1)
playback.seek(clip_start)
elif c == curses.KEY_SLEFT:
change_output = True
playback.pause()
clip_start = max(0, clip_start - 1)
playback.seek(clip_start)
elif c == curses.KEY_RIGHT:
change_output = True
playback.pause()
clip_start = min(clip_start + 0.1, clip_end)
playback.seek(clip_start)
elif c == curses.KEY_SRIGHT:
change_output = True
playback.pause()
clip_start = min(clip_start + 1, clip_end)
playback.seek(clip_start)
elif c == curses.KEY_ENTER:
break
else:
c = chr(c)
if c == " ": # space
change_output = True
if playback.playing:
playback.pause()
else:
playback.resume()
elif c in "tT":
change_output = True
playback.pause()
playback.seek(clip_end - 1)
editing_start = False
elif c in "qQ":
return (None, None)
elif c in "\r\n":
break
else:
if c == curses.KEY_LEFT:
change_output = True
playback.pause()
clip_end = max(clip_end - 0.1, clip_start)
playback.seek(clip_end - 1)
elif c == curses.KEY_SLEFT:
change_output = True
playback.pause()
clip_end = max(clip_end - 1, clip_start)
playback.seek(clip_end - 1)
elif c == curses.KEY_RIGHT:
change_output = True
playback.pause()
clip_end = min(clip_end + 0.1, playback.duration)
playback.seek(clip_end - 1)
elif c == curses.KEY_SRIGHT:
change_output = True
playback.pause()
clip_end = min(clip_end + 1, playback.duration)
playback.seek(clip_end - 1)
elif c == curses.KEY_ENTER:
break
else:
c = chr(c)
if c == " ":
change_output = True
if playback.playing:
playback.pause()
else:
playback.resume()
elif c in "tT":
change_output = True
playback.pause()
playback.seek(clip_start)
editing_start = True
elif c in "qQ":
return (None, None)
elif c in "\r\n":
break
return clip_start, clip_end
def clip_editor_output(
stdscr,
song: Song,
pos,
paused,
duration,
clip_start,
clip_end,
editing_start,
):
stdscr.erase()
if stdscr.getmaxyx()[0] < 3:
stdscr.addstr("Window too small.", curses.color_pair(4))
stdscr.refresh()
return
screen_width = stdscr.getmaxyx()[1]
stdscr.insstr(
f"{format_seconds(clip_start, show_decimal=True)}"
+ (" <" if editing_start else ""),
curses.color_pair(7),
)
end_str = (
"> " if not editing_start else ""
) + f"{format_seconds(clip_end, show_decimal=True)}"
stdscr.move(0, screen_width - len(end_str))
stdscr.insstr(end_str, curses.color_pair(7))
stdscr.move(1, 0)
clip_bar_width = screen_width - 2
if clip_bar_width > 0:
bar = "|"
before_clip_block_width = round(
(clip_bar_width * 8 * clip_start) / duration
)
clip_block_width = round(
clip_bar_width * 8 * (clip_end - clip_start) / duration
)
num_chars_added = 0
stdscr.addstr("|", curses.color_pair(7))
while before_clip_block_width:
if before_clip_block_width >= 8:
stdscr.addstr(" ", curses.color_pair(7))
before_clip_block_width -= 8
else:
stdscr.addstr(
config.HORIZONTAL_BLOCKS[before_clip_block_width],
curses.color_pair(7) | curses.A_REVERSE,
)
clip_block_width -= 8 - before_clip_block_width
before_clip_block_width = 0
num_chars_added += 1
while num_chars_added < clip_bar_width:
if clip_block_width >= 8:
stdscr.addstr(config.HORIZONTAL_BLOCKS[8], curses.color_pair(7))
clip_block_width -= 8
elif clip_block_width > 0:
stdscr.addstr(
config.HORIZONTAL_BLOCKS[clip_block_width],
curses.color_pair(7),
)
clip_block_width = 0
else:
stdscr.addstr(" ", curses.color_pair(7))
num_chars_added += 1
stdscr.insstr("|", curses.color_pair(7))
stdscr.move(stdscr.getyx()[0] + 1, 0)
progress_bar_width = screen_width - 2
if progress_bar_width > 0:
bar = "|"
progress_block_width = (progress_bar_width * 8 * pos) // duration
for _ in range(progress_bar_width):
if progress_block_width > 8:
bar += config.HORIZONTAL_BLOCKS[8]
progress_block_width -= 8
elif progress_block_width > 0:
bar += config.HORIZONTAL_BLOCKS[progress_block_width]
progress_block_width = 0
else:
bar += " "
stdscr.addstr(bar, curses.color_pair(5))
stdscr.insstr("|", curses.color_pair(5)) # hacky fix for curses bug
stdscr.move(stdscr.getyx()[0] + 1, 0)
stdscr.move(stdscr.getyx()[0] + 1, 0) # 1-line spacing
# region pause indicator, song ID+title, tags
length_so_far = 0
length_so_far = addstr_fit_to_width(
stdscr,
("| " if paused else "> ") + f"({song.song_id}) ",
screen_width,
length_so_far,
curses.color_pair(3),
)
length_so_far = addstr_fit_to_width(
stdscr,
f"{song.song_title} ",
screen_width,
length_so_far,
curses.color_pair(3) | curses.A_BOLD,
)
length_so_far = addstr_fit_to_width(
stdscr,
f"{', '.join(song.tags)} ",
screen_width,
length_so_far,
curses.color_pair(2),
)
stdscr.move(stdscr.getyx()[0] + 1, 0)
# endregion
# region credits
length_so_far = 0
length_so_far = addstr_fit_to_width(
stdscr,
f"{song.artist} - ",
screen_width,
length_so_far,
curses.color_pair(2),
)
try:
length_so_far = addstr_fit_to_width(
stdscr,
song.album,
screen_width,
length_so_far,
curses.color_pair(2) | curses.A_ITALIC,
)
except: # pylint: disable=bare-except
print_to_logfile("Failed to italicize text in curses.")
length_so_far = addstr_fit_to_width(
stdscr,
song.album,
screen_width,
length_so_far,
curses.color_pair(2),
)
addstr_fit_to_width(
stdscr,
f" ({song.album_artist})",
screen_width,
length_so_far,
curses.color_pair(2),
)
stdscr.move(stdscr.getyx()[0] + 1, 0)
# endregion
stdscr.move(stdscr.getyx()[0] + 1, 0) # 1-line spacing
# region controls
controls = [
("t", "toggle between editing the start and end of the clip"),
(
"LEFT/RIGHT",
"move whichever clip end you are editing by 0.1 seconds",
),
(
"SHIFT+LEFT/RIGHT",
"move whichever clip end you are editing by 1 second",
),
("SPACE", "play/pause"),
("ENTER", "exit the editor and save the clip"),
("q", "exit the editor without saving the clip"),
]
for control in controls:
length_so_far = 0
length_so_far = addstr_fit_to_width(
stdscr,
f"{control[0]}: ",
screen_width,
length_so_far,
curses.A_BOLD,
)
length_so_far = addstr_fit_to_width(
stdscr,
f"{control[1]}",
screen_width,
length_so_far,
)
stdscr.move(stdscr.getyx()[0] + 1, 0)
# endregion
stdscr.refresh()
def get_username():
import keyring
return keyring.get_password("maestro-music", "username")
def get_password():
import keyring
return keyring.get_password("maestro-music", "password")
def signup(username=None, password=None, login_=True):
if username is None:
username = input("Username: ")
if not username:
click.secho("Username cannot be empty.", fg="red")
return
if not is_safe_username(username):
click.secho(
"Username must be URL-safe (no spaces or special characters).",
fg="red",
)
return
import requests
response = requests.get(
config.USER_EXISTS_URL, params={"user": username}, timeout=5
)
if response.status_code == 200:
click.secho(f"Username {username} already exists.", fg="red")
return
if password is None:
password = getpass("Password (8-1024 characters):")
if len(password) < 8 or len(password) > 1024:
click.secho("Passwords should be 8-1024 characters long.", fg="red")
return
confirm_password = getpass("Confirm password:")
if password != confirm_password:
click.secho("Passwords do not match.", fg="red")
return
response = requests.post(
config.SIGNUP_URL, auth=(username, password), timeout=5
)
if response.status_code == 201:
click.secho(f"Successfully signed up user '{username}'!", fg="green")
if login_:
login(username, password)
else:
click.secho(
f"Signup failed with status code {response.status_code}: {response.text}",
fg="red",
)
def login(username=None, password=None):
if username is None:
username = input("Username: ")
if not username:
click.secho("Username cannot be empty.", fg="red")
return
if not is_safe_username(username):
click.secho(
"Username must be URL-safe (no spaces or special characters).",
fg="red",
)
return
import keyring
current_username = keyring.get_password("maestro-music", "username")
if current_username == username:
click.secho(f"User '{username}' is already logged in.", fg="yellow")
return
if current_username is not None:
click.secho(
f"Logging in as user '{username}' will log out current user '{current_username}'.",
fg="yellow",
)
import requests
if password is None:
password = getpass("Password:")
response = requests.post(
config.LOGIN_URL, auth=(username, password), timeout=5
)
if response.status_code == 200:
click.secho(f"Successfully logged in user '{username}'!", fg="green")
keyring.set_password("maestro-music", "username", username)
keyring.set_password("maestro-music", "password", password)
else:
click.secho(
f"Login failed with status code {response.status_code}: {response.text}",
fg="red",
)
def format_seconds(secs, show_decimal=False, digital=True, include_hours=None):
"""Format seconds into a string.
show_decimal: whether to show the decimal part of the seconds
digital: whether to use digital format ([HH]:MM:SS) or words (e.g. 1h 2m 3s)
include_hours: whether to include hours in the output (e.g. 71:05 vs 1:11:05)
"""
h = int(secs // 3600)
if include_hours is None:
include_hours = h > 0
m = int(secs // 60)
if include_hours:
m %= 60
s = int(secs % 60)
if digital:
return (
(f"{h}:" if include_hours else "")
+ f"{m}:{s:02}"
+ (f".{secs%1:0.2f}"[2:] if show_decimal else "")
)
return (
(f"{h}h " if include_hours else "")
+ f"{m}m {s}"
+ (f".{secs%1:0.2f}"[2:] if show_decimal else "")
+ "s"
)
def search_song(phrase):
"""
CASE INSENSITIVE. Returns a tuple of three lists:
0: songs that match the phrase exactly
1: songs that start with the phrase
1: songs that contain the phrase but do not start with it
"""
phrase = phrase.lower()
results = [], [], [] # is, starts, contains but does not start
for song in SONGS:
song_title = song.song_title.lower()
if song_title == phrase:
results[0].append(song)
elif song_title.startswith(phrase):
results[1].append(song)
elif phrase in song_title:
results[2].append(song)
return results
def print_entry(
song: Song, highlight: str | None = None, year: int | str | None = None
):
"""
Pretty prints ([] means optional)
<song ID> <song name> <total duration> <seconds listened> <times listened> <clip> <tags>
[<artist> - <album> (<album artist>)]
highlight: a string to highlight (the first occurrence of) in the song name
"""
click.secho(f"{song.song_id} ", fg="bright_black", nl=False)
if highlight is None:
click.secho(song.song_title + " ", fg="blue", nl=False, bold=True)
else:
highlight_loc = song.song_title.lower().find(highlight.lower())
click.secho(
song.song_title[:highlight_loc],
fg="blue",
nl=False,
bold=True,
)
click.secho(
song.song_title[highlight_loc : highlight_loc + len(highlight)],
fg="yellow",
nl=False,
bold=True,
)
click.secho(
song.song_title[highlight_loc + len(highlight) :] + " ",
fg="blue",
nl=False,
bold=True,
)
click.secho(
format_seconds(
song.duration,
show_decimal=True,
digital=False,
)
+ " ",
nl=False,
)
if year is not None:
click.secho(
format_seconds(
song.listen_times.get(year, 0),
show_decimal=True,
digital=False,
)
+ " ",
fg="yellow",
nl=False,
)
click.secho(
f"{song.listen_times.get(year, 0) / song.duration:0.2f} ",
fg="green",
nl=False,
)
if song.set_clip in song.clips:
start, end = map(
lambda f: format_seconds(f, show_decimal=True),
song.clips[song.set_clip],
)
click.secho(
f"[{start}, {end}] ",
fg="magenta",
nl=False,
)
click.secho(", ".join(song.tags), fg="bright_black")
click.secho(
f"{(len(str(song.song_id))+1)*' '}{song.artist if song.artist else 'No Artist'} - ",
fg="bright_black",
nl=False,
)
click.secho(
(song.album if song.album else "No Album"),
italic=True,
fg="bright_black",
nl=False,
)
click.secho(
f" ({song.album_artist if song.album_artist else 'No Album Artist'})",
fg="bright_black",
)
def multiprocessing_put_word(q, word):
for c in word:
q.put(c)
q.put("\n")
def versiontuple(v):
return tuple(map(int, v.split(".")))
def pluralize(count, word, include_count=True):
return f"{count} " * include_count + word + ("s" if count != 1 else "")
def is_timed_lyrics(lyrics):
from pylrc.classes import Lyrics
return isinstance(lyrics, Lyrics)
def get_lyric(lyric_obj):
if isinstance(lyric_obj, str):
return lyric_obj
return lyric_obj.text # pylrc.classes.LyricLine
def set_lyric(lyrics, i, val):
if isinstance(lyrics[i], str):
lyrics[i] = val
else:
lyrics[i].text = val
def display_lyrics(lyrics, song, prefix: str = ""):
if prefix:
prefix += " "
if lyrics is None:
click.secho(
f'No {prefix}lyrics found for "{song.song_title}" (ID: {song.song_id}).',
fg="red",
)
return
if prefix:
prefix = prefix.capitalize() + "l"
else:
prefix = "L"
click.echo(f"{prefix.capitalize()}yrics for ", nl=False)
click.secho(song.song_title, fg="blue", bold=True, nl=False)
click.echo(f" (ID {song.song_id}):")
if is_timed_lyrics(lyrics):
for lyric in lyrics:
click.echo(
f"\t[{format_seconds(lyric.time, show_decimal=True)}] {lyric.text}"
)
else:
click.echo("\n".join([f"\t{lyric}" for lyric in lyrics]))
def filter_songs(
tags: set[str],
exclude_tags,
artists,
albums,
album_artists,
match_all,
combine_artists,
):
songs = []
for song in SONGS:
search_criteria = (
(
(
any(
artist.lower()
in song.artist.lower()
+ (
f", {song.album_artist.lower()}"
if combine_artists
else ""
)
for artist in artists
)
),
artists,
),
(
(any(album.lower() in song.album.lower() for album in albums)),
albums,
),
(
(
any(
album_artist.lower()
in song.album_artist.lower()
+ (
f", {song.artist.lower()}"
if combine_artists
else ""
)
for album_artist in album_artists
)
),
album_artists,
),
)
search_criteria = tuple(
c[0] for c in filter(lambda t: t[1], search_criteria)
)
if match_all:
if not search_criteria:
search_criteria = (True,)
search_criteria = all(search_criteria) and (
not tags or (tags <= song.tags) # subset
)
elif any(search_criteria):
search_criteria = True
elif tags:
search_criteria = tags & song.tags
else:
search_criteria = not search_criteria
if search_criteria and not exclude_tags & song.tags:
songs.append(song)
return songs
================================================
FILE: maestro/icon.py
================================================
img = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x04\x00\x00\x00\x04\x00\x08\x06\x00\x00\x00\x7f\x1d+\x83\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00DeXIfMM\x00*\x00\x00\x00\x08\x00\x01\x87i\x00\x04\x00\x00\x00\x01\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x03\xa0\x01\x00\x03\x00\x00\x00\x01\x00\x01\x00\x00\xa0\x02\x00\x04\x00\x00\x00\x01\x00\x00\x04\x00\xa0\x03\x00\x04\x00\x00\x00\x01\x00\x00\x04\x00\x00\x00\x00\x00\xd3\xdd\xea\x1d\x00\x00\x00\x1ciDOT\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00(\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00m.{a\xb5\x85\x00\x00@\x00IDATx\x01\xec\xdd\t\x94]U\x99\xb7q\x036\xd8*\x1f*4\xcb\x01m\xdan\xc4V\xcc\x00$L"S\x18\xc2\x1cl\x8d@\x83\x11\x88\x04ADTh%B\xa2\x08\x01T\x94\xb4H0i\x08H0D\x8cH\x18\x12\xc2\x10P\xa2L\x8d\x88\xb4\xda6\xa2\x80\xa8 4C\x90\x0cd\x7fgWSB\x92\xaaJ\x9d[w8g\xef_\xadU+P\xb9u\xef9\xef~\xf6\xde\xef\xff\xc9\xb9\xe7\xbe\xe2\x15\xbeT\xa0\xa8\xc0\xad\xb7\xde\xfa\xfa\x85\x0b\x17\x0e+\xbe\x0f\xbc\xe5\x96[\x8e-\xfe\xff\xd4\xe2\xcf)\xc5\xf7\xcc\xe2\xbf\xe7\x17\x7f\xdeU\xfc\xf9\xdb\xe2\xcfg\x8a\xefe\xc5\x7f\x07\xdfj\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xce0\x10{\xf2\xd8\x9b\x17\xf5\x8f=z\xec\xd5c\xcf>\xb3\xf8\x9eR\xfcw\xec\xe5\x8f\x8d\xbd}\xec\xf1\x8b\xff\x7f\xbd\xd0\xa3\x02*\x90Y\x05\xae\xbb\xee\xba7\xdct\xd3M;\xc5\xc5\xa0\xf8>\xa7\xf8\x9eS,\x06\xf7\x14\xdfO\x15\xdf\x02\xbd\x1a`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c` Q\x06\x8a\xde\xff\x7fc\xef\x1f3\xc0\x8bY\xe0\xd8\x98\rbF\xc8,\x169]\x15H\xab\x02\x97_~\xf9\xda\x85\xe9{O\xf1}H1\xb9\xcf,\xbe\xaf-&\xfb\xc3B>\xc9\x81\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c\xf4\xc0\xc0\xc313\xc4\xec\x103D\xcc\x121S\xa4\x95\x92\x9c\x8d\n$R\x81+\xaf\xbcr\xbdb\x12\x8f,\xbeO-\xbe\xe7\x17\xdfO\x17\xdf\xec\xad\x1a`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x8d2\x103E\xcc\x16\xa7\x16\xdf#c\xe6H$>9\r\x15\xa8W\x05\xe2\xe4+\xec\xdc\x01\xc5w|\x8f\xfe\xdd\xc5\xf7r\x81\x9f\xf0\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06Z\xc5@\xcc\x1c/f\x8f\x98A\x0e \x04\xea\x95!\x1dm\xcd*\x10/\xc3)&\xda\x89\xc5\x84\xbe\xa9\xf8^\xda\xaa\x89\xedym\x1a\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@?\x18\x88\x99\xe4\xa6\x98QbV\xa9Y\xbcr\xb8*P\xad\n\x14\xef\xb9Y\xa7\x98L{\x17\x93jZ\xf1\xed\xfd\xfb.\xddj\xf4\xd2-\xbf\x87\x1d\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81v0\x103\xcb\xb4\x98ab\x96\xa9V\xbar4*P\xc1\n\x14w\xe1|e1i\xf6,&\xcd\x7f\x14\xdfO\x16\xff\xdd\x8e\x89\xea5\xd4\x19\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18h\x1a\x031\xcb\xc4L\x13\xb3M\xcc8\x15\x8c^\x0eI\x05:S\x81\x89\x13\'\xaeUL\x8c\x91\xc5\x04\xb9\xa0\xf8~\\\xe8\'=0\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81T\x18\x88\x19\xe7\xc5\xac\xb3k\xcc>\x9dI]^U\x05:\\\x81\xe2}2o-&\xf5\xa9\xc5\xf7oS\x99\xdc\xce\xc3F\x85\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c\xf4\xc1@\xcc>\xa7\xc6,\xd4\xe18\xe6\xe5U\xa0\xf5\x15\x88\x97\xbf\x14\xf6+\xde\xbd\xff\xea\x02\xfc\x17\xfa\x98\x18M\xbb\xfc\xc6kX\x801\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x8a1\xf0B\xccD1\x1by\x8b@\xebs\xa8Whs\x05\xe6\xcf\x9f\xff\xe6\x02\xee/\x15\xdf\xbf\xaf\xd8\xc4#\x1a\xbc\xd7\x0b\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xe8\x18\x031#\xc5\xac\x143S\x9bc\x9a\x97S\x81\xe6V\xe0\xc5\x8f\xee\xbb\xa8\x08\xfdK\x04\x7f\xd6\x15\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@\xaf\x0c,)D\xc0E>R\xb0\xb9\x99\xd4\xb3\xb5\xa1\x02\xc5\xa4\x8e7\xf5\xbb\xce\xe4\xeeurw\xcc0\x1a\x13c\x82\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81j3\xf0b\x96\x1a\xd9\x86\xe8\xe6%T\xa0\xe1\n\x0c*l\xd5\x98b1\xb9\xc7\x82R\xed\x05\xc5\xf8\x18\x1f\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c\xd4\x82\x81{b\xc6*\x12\xda\xa0\x86S\x9a_T\x81fW\xe0\xe6\x9bo\x1e]X\xaa{-"\xb5XD\\}\xe0=n\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xa8\x11\x031k\xc5\xcc\xd5\xec\x1c\xe7\xf9T\xa0T\x05\n\x10\xf7.\xbe\xef\x12\xfc\x05\x7f\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\xade f\xaf\x98\xc1J\x856\x0fV\x81\x81V\xa0\x80n\xd7br/2\xc1[;\xc1\xd5W}1\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x1e\x18X\x143\xd9@s\x9d\xdfW\x81>+P\xbc\xffd\xd3\x02\xbe\x1f\xf4\x00\xa0K\x88jt\t\x91\xf1\xb3\x89`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c` \t\x06~\x103Z\x9f!\xce_\xaa@\xd9\n\\s\xcd5\xff\xaf0Lg\x17\x8b\x84\x8f\xf3\x13\xf4\xc9\x1e\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0:\x0c\xc4\x8f\x0f<;f\xb6\xb29\xcf\xe3U`\xa5\nL\x9c8q\xad\x02\xa6qE\xf0\xff#C\x98\x84!\xb4PWg\xa16\x16\xc6\x02\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0\x99\x0c\xfc1f\xb7\x98\xe1V\nu\xfeG\x05\xfaS\x81\xe2.\x93\xc3\x8b\xd0\xff\x9f\x82\xbf\xe0\x8f\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa06\x0c\xfcg\xccr\xfd\xc9|\x1e\xa3\x02\xaf\x987o\xdek\ns\xf4\xd5\xe2{\xb9I^\x9bI\xdeLs\xe8\xb9\x98h\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c\xd4\x98\x81\x98\xe5b\xa6\x8b\xd9N\xc4U\x81^+P\xdc@b\xf7\x02\x94\xdf\x08\xfe\x82?\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81z3\x10\xb3]\xccx\xbd\x06@\x7f\x91g\x05\x16,X\xb0A1\xb9g\x98\xe0\xf5\x9e\xe0\xc6\xcf\xf8a\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03=00#f\xbe<\xd3\xae\xb3^\xa9\x02\xc5\xfbC\xf6-\x00q\x93\xbf\x1a_\xe2\xd3\xc3\x04w\xc9\x96\xf1\xc4\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06^\xce\xc0\x1fc\xf6[)\x0c\xfa\x9f|*p\xf9\xe5\x97\xffmqI\xc8y\xc2#C\x88\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c` \x0f\x06b\x06\x8cY0\x9f\xe4\xebL_q\xd3M7\r-\x06\xfe~\x93<\x8fIn\x9c\x8d3\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\xd0\xcd@\xcc\x821\x13\x8a\xc6\xe9W`Pq\x13\x88O\x17\x03\xbf\xa4{\xf0\xfdi!\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x90\x1d\x03Kb6,"\xf0\xa0\xf4cp\x86gX\x18\x9e\r\x8bI=\xdf\xc4\xcenb\xbf\xfc}?\xfe\xdb\xfb\xc00\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x9730?f\xc5\x0c#r\xba\xa7\\\x0c\xe8VE\xf0\xff\xad\xf0/\xfcc\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\xc0*\x0c\xfc6f\xc6t\x13qFgV\xbc\xbf\xe3\xc8\xe2\xfb\xf9U\x06\xf8\xe5\xc6\xc7\x7f3\x80\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x062f f\xc6\x98\x1d3\x8a\xcai\x9d\xea5\xd7\\\xb3n1\x80\x17\x08\xfe\xec\x1e\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c\xf4\x87\x81\x98!c\x96L+\x1d\'~6\xc5\xe5\x1b\x1b\x17\x03w{\x7f\x06\xd8c,\x04\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\xd0\xcd@\xcc\x921S&\x1e\x9b\xd38\xbdb\xa0\x86\x16\x03\xf7H\xf7\xe0\xf9\xd3D\xc6\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x92\x0c<\x12\xb3e\x1a)9\xd1\xb3(>\xc6a\xaf\xc2\xd6<Sr`\xbd\xd7\xa7\x03\xef\xf5\x997o^\x981cF8\xfb\xec\xb3\xc3\x84\t\x13\xc2q\xc7\x1d\x17\x0e;\xec\xb0p\xc0\x01\x07\x84\x9dw\xde9l\xb1\xc5\x16\xe1\x1d\xefxG\xd8d\x93M\xc2\xc6\x1bo\x1c6\xdah\xa3\xf0\x867\xbc!\xac\xb7\xdez\xe1U\xafzUX{\xed\xb5}\xab\x01\x060\x80\x01\x0c`\x00\x03\x18\xc0@\xc7\x18\x88=i\xecMc\x8f\x1a{\xd5\xd8\xb3\xc6\xde5\xf6\xb0\xb1\x97\x8d=m\xecmc\x8f\x1b{\xdd\xd8\xf3\xc6\xde7\xf6\xc0\xb1\x17\x96Y\xaa/$b\xb6\x8c\x193\xd1\xf8\\\xef\xd3*\x06g|\xf1\xbd\xdcD\xaa\xceD\xba\xfa\xea\xab\xc3\xb9\xe7\x9e\x1bN8\xe1\x84p\xd0A\x07u-\x82\x9bm\xb6YX\x7f\xfd\xf5CA\x9bo5\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc8\x96\x81\xd8\x13\xc7\xde8\x8a\x82\xd8+\xc7\x9e9\xf6\xce\xb1\x87\x96i\xaa\x93ib\xc6\x8cY\xb3\xdei9\xad\xa3\x1fT\x0c\xc8Y&I\xe7&Ia\xc5\xbaL\xe6\xe7?\xff\xf9p\xf0\xc1\x07\x87\xad\xb7\xde:l\xb8\xe1\x86\xd9.\xe6\xe4\x06\xb9\x83\x01\x0c`\x00\x03\x18\xc0\x00\x0600\x10\x06b/\x1d{\xea\xd8[\xc7\x1e;^5\x10{n\x99\xa7s\x99\'f\xcebL\x07\x15\xdf\xbe:U\x81\xa9S\xa7\xfeM1\tf\x99\x08\xed\x9d\x08\xf1\x92\xa5s\xce9\'\x1c~\xf8\xe1a\xf8\xf0\xe1\xe1o\xff\xf6o\x85}\xf6\x1e\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03-d \xf6\xdc\xb1\xf7\x8e=x\xec\xc5\xbd\x8d\xa0\xbd\x19\xe8\xc5\xcc9+f\xd0N\xe5\xdf\xac_\xb7\xb8!\xc3\xab\n\x0b3W\xf8o=\xf8\x0b\x16,\xe8z\xbf\xd2\xbf\xfc\xcb\xbft\xbd\xa7i\xad\xb5\xd6\xb2\xb8\xb7pq/&\xb6\xfa\xaa\x01\x060\x80\x01\x0c`\x00\x03\x18\xc0@\x9f\x0c\xc4\x9e<\xdeo \xf6\xe8\xf1\xde\x02\xb1g\x97\x8dZ\x9f\x8db\x06\x8dY4\xeb0\xde\xee\x93/l\xd7k\x8a\xc2\xdf\x00\xf0\xd6\x01>s\xe6\xcc\xae\x9b\x95\xc4K\x8f\xd6Yg\x9d>\x17\x1f\x81U`\xc7\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@g\x19\x88={\xec\xdd\xe3\r\x07c//+\xb5.+\xc5,\x1a3i\xc1\xbc\xafVW\xe0\xfa\xeb\xaf_\xbf(\xf8m\x80n>\xd0\xe7\x9f\x7f~\x183fLx\xf3\x9b\xdf,\xf03\xce\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xa81\x03\xb1\xa7\x8f\xbd}\xec\xf1e\xa7\xe6g\xa7\x98Ic6mu\xfe\xcd\xfa\xf9\x8bK-6,\n}7\x80\x9b\x07\xf0\xb4i\xd3\xba\xee:\xfa\xc67\xbe\xd1\x02_\xe3\x05\xbeX\x18\x8c\x9f\x1a`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c\xf4\xc8@\xec\xf5\xe3\'\r\xc4\xde_\x96j^\x96\x8a\xd94f\xd4\xacCz\xabN>\x16\xb6\x80\xf5>\xc0\x0e\x1c\xd8Y\xb3fu}\x1e\xa9\x7f\xe9\x17\x9a\x89\x03\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xbc\x18\x88\x19\xe0\xb0\xc3\x0e\x0b1\x13\xc8V\x03\xcfV1\xa3\x92\x00\xc5*\xd2\xcc\xaf\x17/\xfb\xf7/\xff\xb76\x0e\xe8\x8d7\xde\x18&N\x9c\x18\xb6\xdcr\xcb\x1e\xad`1^~\xae\x06\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc8\x88\x81\x98\rbF\x88Y\x81\x0ch<k\xc5+\x01\xbc\x1d\xa0I\x06\xe0\xc5\x1b\xfey\xcf\x7f\x83\xe1\xff\x92K.\xe9z\xef\xcf\xfa\xeb\xafo1\xcfh1\'t\x08-\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xfe2\x10\xb3B\xbc_@\xcc\x0eD@c" \xde\x13\xc0\x8d\x01\x0b\xe2\x06\xf2U\\J\x11?\xea\xcf\xdd\xfe\x1b\x08\xff_\xf9\xcaW\xc2V[m%\xf4\x0b\xfd\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@\xbf\x19\x88\x19"f\t"\xa0\xbc\x08\x88\xd95f\xd8\x81d\xe0l\x7fw\xea\xd4\xa9\x7fS\x14p.\xf0\xfa\x0f^\x01[8\xf9\xe4\x93\xc3?\xfe\xe3?\xf6{\x82\x17\x80y\xac\x1a`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03+1\x103E\xcc\x161c\xc8d\xfd\xcfd1\xc3\xc6,\x9bm\x90o\xf0\xc4\x07\x15\x90\xcd\x02Z\xff@\xbb\xee\xba\xeb\xc2\xf8\xf1\xe3\xc3\x06\x1bl\xb0\xd2\xa4\x15\xee\xc9\r\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`` \x0c\xc4\x8c\x11\xb3F\xcc\x1c\xf2Y\xff\xf2Y\xcc\xb2E\xcd\x07\x15\xdf\xbe\xfaS\x81\xc2\x9a\x9c\r\xae5\xc35\x7f\xfe\xfcp\xd4QG\x85\xf5\xd6[O\xf0gl1\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x961\x103G\xcc\x1e1\x83\xc8jk\xcejE\xa6=\xab?\xd97\xfb\xc7\x14\x85\x1a\x0f\xa8\xbe\x81Z\xb0`A8\xe6\x98c\xc2\xeb^\xf7\xba\x96M\xf0\x02D\xcf\xad\x06\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\xc0J\x0c\xc4\x0c\x12\xb3H\xcc$r[\xdf\xb9-f\xdb\xec\x03~_\x05X\xb8p\xe1^E\x91\x96\x03\xa9g\x90\xe2\xc7s\x1c\x7f\xfc\xf1.\xf5\xb7\x08\xaf\xb4\x08\x935d\x15\x060\x80\x01\x0c`\x00\x03\x18\xc0@\xbb\x19\x88o\r\x88\xd9\xc4G\x08\xf6\x9c\xddb\xa6\x8d\xd9\xf6\xe6\x9bo\x1eU\x8c\x8d\xafU+P\xdc\\bhQ\xa0g\x84\xff\x9e\x01:\xe3\x8c3\xc2[\xde\xf2\x16\xc1O\xf8\xc7\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06*\xc3@\xcc(1\xab\xc8q=\xe7\xb8\x98qc\xd6]5\xfff\xfd\xffEA6.\x80y\x044\xabCs\xf1\xc5\x17\xfb8?\x0b|e\x16\xf8b\xa1r,j\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\xb0\x1a\x03\xf1\xe3\x03cv\x91\xe9V\xcft1\xeb\xc6\xcc\x9bu\xe8\xef>\xf9k\xae\xb9f\xdd\xc2\x8a\xdc\x0e\x94\x95A\x99;wn\x18=ztXk\xad\xb5V\x9b\\B\x98\x10\x8a\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0cT\x8d\x81\x98]b\x86\x89YF\xbe[9\xdf\xc5\xcc\x1b\xb3o1fy\x7f\x15\x85\xb8\x00\x1c+\xc3q\xd2I\'\xb9\xb3?\xabJ\xfc`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0\x96\x0c\xc4O\x0c\x88\x99F\xce[9\xe7\xc5\xec\x9bu\xfa/n\xfaw\x04(^\x82\xe2\xb2\xcb.\x0b\xc3\x86\r\xab\xe5$/@v\xdcj\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c\xfc\x95\x81\x98mb\xc6\x91\xf9^\xca|1\x03g)\x01\x8a\xf7@lU\x18\x90\xe7\xc1pk(j\x11\xc6\x8f\x1f\x1f\xd6Yg\x9d\xbfN\x16\x81\x9aP\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06\xea\xce@\xcc81\xeb\xc4\xcc#\xfbu}2\xc0\xf3E-\xb6*\xc65\x9f\xaf\xe2\x847,\x06\xff\xb7\x00\xb85L\x9b6-\xfc\xd3?\xfd\x93\xe0\xcf\x94b\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\xc92\xb0\xe9\xa6\x9bve\x1f\x19\xb0\xebj\x80\xdf\xc6L\x9c\x8b\x01\x18T\x0c\xfa\xfc\xdc\x07\xbe\xb8\xf4#|\xf4\xa3\x1f\rk\xaf\xbdv\xb2\x93\xbc\x00\xda\xb9\xa9\x01\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\xd0\xc5@\xcc>1\x03\xc5,\x94{\x1e\x8c\x99\xb8\xc8K\x83\x92\x97\x00\xc5`\x7f:\xf7\xc1\xfe\xeew\xbf\x1b\x86\x0e\x1dj!\xb4\x10b\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\xd91\x10\xb3P\xccD\xb9\xe7\xc2"\x1b\x7f*i\x01P\\\xe60\xb4\x18\xe4%9\x0f\xf4\xc4\x89\x13\xc3k_\xfb\xda\xec&\xb9\xab\x01\\\r\x81\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0ct3\x103Q\xccF9g\xc3\x98\x8dcF.j\x92\xde\xd7\xe5\x97_\xfe\xb7\xc5M\xff\xee\xcfu\x80\xe7\xcd\x9b\x17\xf6\xd8c\x0f\xc1\x9f\xe1\xc5\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\xf0"\x031#\xc5\xac\x94kN\x8c\x199f\xe5\xe4\x0c@qb\xe7\xe5:\xa8\x97^zi\xd8d\x93MLr\x0b=\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81U\x18\x88Y)f\xa6\\\xf3b\xf1V\x80o$%\x00\x8a\x81\xdc\'\xd7\xc1\xfc\xd2\x97\xbe\x14^\xfd\xeaW\x9b\xe4\xabL\xf2\x02p5Q\x03\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0\x8b\x81\x98\x99bv\xca57\xc6\xcc\x9c\x84\x04X\xb0`\xc1\x06\xc5\xc9\xfc1\xb7\x81\x8cw\xb6<\xf4\xd0C-h\x164\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\xfdd f\xa8\x98\xa5r\xcb\x8f13\xc7\xec\\{\tP\x9c\xc8\x8c\xdc\x06\xef\xea\xab\xaf\x0e\xc3\x87\x0f7\xc9\xfb9\xc9]\r\xe0j\x08\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0\x9b\x81\x98\xa5b\xa6\xca-G\xc6\xec\\\xd4\xa0\xbe_\x85\xb9\xd9=\xb7A\xbb\xec\xb2\xcb\xc2\xc6\x1bo,\xfc\x0b\xff\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06\x1ad f\xaa\x98\xadr\xcb\x931C\xd7\xd2\x00\\u\xd5U\xaf.n\xfc\xf7@N\x03v\xdey\xe7\x85\xf5\xd7_\xdf$op\x92\x17\xa0\xab\x9d\x1a`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03]\x0c\xc4l\x153VN\x992f\xe8\x98\xa5k\'\x01\x8a\x03\xffJN\x035i\xd2\xa4\xb0\xce:\xebX\xac,V\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06\x9a\xc4@\xccX1k\xe5\x94-c\x96\xae\x95\x00\xb8\xe9\xa6\x9b\xb6*\x0ezy.\x834~\xfcx\x13\xbcI\x13\xdcU\x00\xae\x82\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06Ve f\xae\\\xf2e\xcc\xd21S\x175\xa8\xfe\xd7\xc4\x89\x13\xd7*\x0e\xf8\xee\x1c\x06\xa78\xcfp\xe0\x81\x07\n\xff\xc2?\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x163\x10\xb3W\xcc`\x99d\xcd\xbbc\xb6\xae\xbc\x01(\x06d\\\x0e\x03r\xf3\xcd7\x87Q\xa3F\x99\xe4-\x9e\xe4\x05\xf0j\xac\x06\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@\x17\x031\x83\xc5,\x96C\xe6\x8c\xd9\xba\xd2\x02\xe0\x9ak\xae\xf9\x7f\xc5@\xfc1\xf5\xc1(.\xc7\x08;\xef\xbc\xb3E\xc8"\x84\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0\xcd\x0c\xc4,\x163Y\xea\xb93f\xeb\x98\xb1++\x01\nCqv\xea\x83\xb0`\xc1\x82\xb0\xddv\xdb\x99\xe4m\x9e\xe4\xae\x04p%\x04\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\xd0\xcd@\xccd1\x9b\xa5\x9e?c\xc6.\xce\xb9z_\x85\x81\xf9\xa7\xa2\xf8KR\x1e\x80\xf9\xf3\xe7\x87-\xb7\xdcR\xf8\x17\xfe1\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0ct\x98\x81\x98\xcdbFK9\x83\xc6\x8c\x1d\xb3v\xe5\x0c@a&\xaeL\xb9\xf0\xd1.\t\xff\x8cc1\xf1,\xf4j\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x8a0\x103Z\xeaW\x02\xc4\xac])\x01\xb0p\xe1\xc2]R\x0e\xff\xf1\xfd%.\xfb\x17|\x85\x7f\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0z\x0c\xc4\xac\x163[\xca\x994f\xee\x82\xbdj|\x15\x85^\x94j\xb1\xe3\x1d&\xdd\xf0\xafz\x93\xbc \x9fuU\x03\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0\x8b\x81\x98\xd9\x12\xfft\x80E\x95H\xff\x85\x89\xd8+\xd5\xf0_\\j\xe1\xa3\xfe,(6\x15\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0\x06\x0c\xc4\x8f\x08\x8c\x19.\xd5|\x1a\xb3w\xc7%@Q\xdc;S-\xf0\x81\x07\x1eh\xa2\xd7`\xa2\xbb\x1a\xc0\xd5\x10\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@d f\xb8T\xf3i\xcc\xde\xc59v\xee\xab\xb0+\x07\xa4Z\xdc\xf1\xe3\xc7\x0b\xff\xc2?\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x9a1\x10\xb3\\\xaa95f\xf0N\x19\x80A\xc5\x8b\xdf\x9bba\'M\x9ad\x92\xd7l\x92\x17\x93\xc0\x98\xa9\x01\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\xd0\xc5@\xcct)f\xd5\x98\xc1\x8b\xec3\xa8\xed\x12\xa0x\xff\xc1\x07S,\xe8y\xe7\x9d\x17\xd6Yg\x1d\x0b\x87\x85\x03\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@M\x19\x88\x99.f\xbb\x143k\xcc\xe2m\x17\x00E!\xff3\xb5b^v\xd9ea\xfd\xf5\xd77\xc9k:\xc9]\x05\xe0*\x08\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0\x9b\x81\x98\xedb\xc6K-\xb7\xc6,^\x9cc\xfb\xbe\x8a\x17\x1c\x99Z\x11\xaf\xbe\xfa\xea\xb0\xf1\xc6\x1b\x0b\xff\x1d\x08\xff\x83\x06\r\n\xeb\xae\xbbnXo\xbd\xf5\xc2\x1b\xde\xf0\x86\xf0\xc67\xbe1\xbc\xe5-o\to{\xdb\xdb\xc2\xdb\xdf\xfe\xf6\xb0\xe9\xa6\x9b\x86\xcd6\xdb,\xbc\xf3\x9d\xef\x0c\xefz\xd7\xbb\xc2\xbb\xdf\xfd\xee\xb0\xf9\xe6\x9b\xfbV\x03\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x8e1\x10{\xd2\xd8\x9b\xc6\x1e5\xf6\xaa\xb1g\x8d\xbdk\xecac/\x1b{\xda\xd8\xdb\xc6\x1e7\xf6\xba\xb1\xe7-\x12\x9b\xef6\xd7 f\xbc\x98\xf5R\xcb\xaf1\x93\xb7\xcd\x00\x14\xef;\xb86\xa5\x02\x16\x97P\x84\xe1\xc3\x87\x9b\x8cm\x98\x8cq\xf1{\xdd\xeb^\xd7\xb5 n\xb2\xc9&]\x0b\xe6{\xde\xf3\x9e\xe0[\r0\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06Rg \xca\x82\xd8\x03G9\x10{\xe2\xd8\x1b\x93\x02\xad\x97"1\xeb\xc5\xcc\x97R\x86\x8d\x99\xbc-\x02\xe0\xa6\x9bn\xda<\xa5\xc2\xc5s9\xf4\xd0CM\xbc\x16\x84\xffh9_\xf3\x9a\xd7\x84\x8d6\xda\xa8k\xa1\x8b\x864\xf5E\xdd\xf9i\\0\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06\xca0\x10{\xe4(\x05b\xcf\x1c{gW\n\xb4F\x08\xc4\xcc\x97Z\x8e\x8d\xd9\xbc\xe5\x12\xa00\r\x17\xa6T\xb8\xd3O?]\xf8ob\xf8\x8f7\xdb\xd8`\x83\r\xba\x16\xb1xYT\x99\xc5\xcfcm\x96\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\xb93\x10{\xe8(\x04bO\xed\xe6\xe4\xcd\x95\x011\xfb\xa5\x94ec6o\xa9\x00(^\xe0ME\xc1\x96\xa4R\xb4K/\xbd4\xbc\xfa\xd5\xaf&\x00\x06(\x00\xe2\xa5K\xd1X\xbe\xe3\x1d\xef\x10\xf8\xbd\x9d\x01\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03Md \xf6\xd8\xb1\xd7\xf6v\x81\x81\xcb\x80\x98\xfdb\x06L%\xcf\xc6l\x1e3z\xcb$@\xf1\xbe\x89\xd3R)\xd6\xfc\xf9\xf3\xbb\xccZQ,\x02\xa0\x81\x1a\xbc\xf2\x95\xaf\x0c\x7f\xf7w\x7f\xd7u\xb3\x93\xdc-\xad\xf3\xf7/\x15\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\xed` \xdeh0\xf6\xe0\xb1\x17\x97c\x1a\xcbq\xf1\xea\x8a\x98\x05S\xc9\xb51\xa3\xb7D\x00\x14\xef/xea\x17~\x9fJ\xa1\xf6\xdcsO\x93\xa6\x81\xe0\x1f\xefb\xfa\xf7\x7f\xff\xf7]w[m\xc7"\xe75l\xa6\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18X\x99\x81\xf8\x89X\xb1\'\x8f\xbd9\x11P^\x04\xc4,\x98J\xae\x8d\x19=f\xf5\xa6K\x80\xe2\x89\xf7O\xa5H\x13\'N4QJ\x84\xff\xb5\xd6Z+l\xb8\xe1\x86]\x1fob\xf1]y\xf1U\x0f\xf5\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xe8$\x03\xf1#\x08c\xaf\x1e{v2\xa0\xff2`\xd2\xa4I)I\x80\xfd[!\x00\xe6\xa6 \x00\xbe\xfb\xdd\xef\x86\xd7\xbe\xf6\xb5&G?\x04@\xbc\xb4(~D\x89\xbb\xf7\xdb\xd4:\xb9\xa9ym\xfca\x00\x03\x18\xc0\x00\x060\x80\x8153\x10{\xf6\xd8\xbb{{@\xff$@\xcc\x841\x1b\xa6\x90q\x8b\x7f\xac\x9f\xdbT\x01P\\R\xb0q\xf1\xa4\xcb\xeb^\x9c\xf8\xd9\x8fC\x87\x0e\x15\xfe\xd7\x10\xfe\xe3\xa2\xf1\xa67\xbd)\xb8\x8b\xff\x9a\x17Z\x9b\x91\x1aa\x00\x03\x18\xc0\x00\x060\x80\x01\x0cT\x89\x81\xd8\xc3\xc7^\x9e\x08X\xb3\x08\x88\xd90f\xc4\xba\xe7\xdc\x98\xd5cfo\x9a\x04(\x9e\xf0\x94\xba\x17%\x1e\xffQG\x1d%\xfc\xf7\x11\xfe\xd7^{\xed.k(\xf8\xdb\xc4\xaa\xb4\x899\x16<b\x00\x03\x18\xc0\x00\x060\x80\x81\xf2\x0c\xc4\x9e>^\x11\x10{|o\r\xe8]\x06\xc4\x8c\x98B\xd6\x8d\x99\xbd)\x02\xa0x\xbf\xfcZEA\x1e\xac{Q\xa6M\x9b\x06\xfe>\xc2\x7f\xfc\x9cQ\x97\xfa\x97_XmFj\x86\x01\x0c`\x00\x03\x18\xc0\x00\x060Pe\x06b\x8f\x1f{}\x12\xa0g\t\x10\x05\xc9\xf4\xe9\xd3S\x90\x00\x0f\xc6\xec>`\tP\x98\x84]\xeb\x1e\xfe\x8b\xcb!\xba>\xae\x0e\xf4\xabC\x1f\xdf\xfb\x12?N\xa4\xca\x8b\x96c\xb3\xa9b\x00\x03\x18\xc0\x00\x060\x80\x01\x0c```\x0c\xc4\x9e\xdf\xbd\xd0V\xcfC1#\xc6\xda\xc4\xccX\xf7\xdc\x1b\xb3{3\x04\xc0\x05u/\xc4\xf8\xf1\xe3\x19\xafU\xfe\xf5?\xbe\'\xe8mo{\x9b\xe0\xff\x9e\x81-\xa46"\xf5\xc3\x00\x060\x80\x01\x0c`\x00\x03\x18\xa8\x13\x031\x03\xb8?\xc0\xea" f\xc6\xba\xe7\xdeB\x00\\0 \x01PX\x90W\x16O\xf2x\x9d\x0bq\xd9e\x97\x85u\xd6Y\x87\x00x\x99\x00x\xfd\xeb_\xefr\x7f\xc1\x9f\xfc\xc1\x00\x060\x80\x01\x0c`\x00\x03\x18\xc8\x94\x81\xf8\xb6\x80\x98\t\\!\xfd\x92\x08\x88\x991f\xc7:g\xdf\x98\xddc\x86oX\x02\x14O\xb0G\x9d\x0b\x10\x8f}\xd8\xb0a\xc0~1\xfcG\xd3\xf7\x0f\xff\xf0\x0f\x16\xfaL\x17\xfa:\x99i\xc7\xea_R0\x80\x01\x0c`\x00\x03\x18\xc0@\xeb\x19\x88\xd9\xc0\xd5\x00/I\x80\x98\x1d\xeb\x9e\x7fc\x86\x1f\x88\x00\xf8\x8f:\x17\xe0\xa4\x93N\x12\xfe_\x0c\xff\xeb\xaf\xbf\xbe\x7f\xf5\x17\xfc\xc9\x1f\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c\xac\xc4@\xbc\x1a f\x05W\x03\xfc\x9f\x08\x88\x19\xb2\xce\x19\xb88\xf6\xe9\r\t\x80\xa9S\xa7\xfeM\xf1\xcbO\xd4\xf5\xe4\xe7\xce\x9d\x1b\xd6[o\xbd\xecA^k\xad\xb5\xc2\xc6\x1bo\xbc\xd2$gS[oS\xd5X\x8d1\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06\xea\xc4@\xcc\x0c\x83\x06\r\xca>?\xc5\x0c\x19\xb3d]sp\xcc\xf01\xcb\x97\x96\x00\xc5\xa5\x03{\xd7\xf8\xa4\xc3\xe8\xd1\xa3\xb3\x877\xbe\x8f\xc5\x1d\xfem<u\xdax\x1c+^1\x80\x01\x0c`\x00\x03\x18\xc0@\xe7\x18\x88\xd9\xc1\xfd\xd3^\xd1\x95%\xeb\x9c\x85c\x96/-\x00\x8a\x13\x9eV\xd7\x93\xbe\xf8\xe2\x8bC\xfc\x97\xef\x9c/c\x89\xe6*^\xcec\x01\xed\xdc\x02\xaa\xf6j\x8f\x01\x0c`\x00\x03\x18\xc0\x00\x060P7\x06b\x86\xc8\xfdJ\xea\x98%c\xa6\xack\x1e.\x04\xc0\xb7\x1a\x11\x00\x0f\xd7\xf5\x84\xb7\xdaj\xab\xac\xc3\xffF\x1bm\x146\xdf|s\xe1\xdf\xfb\xbb0\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xd2\x0c\xc4,\x113E\xce\xff\xa0\x1a3e]\xf3p!\x00\x1e*%\x00\x16.\\\xf8\x9e\xba\x9e\xec\x19g\x9c\x91-\xa8\xf1=;o}\xeb[KO\xf0\xbaYI\xc7\xcb\xa4c\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0\xf5\x0c\xc4l\x91\xf3}\x01&O\x9e\\[\t\x103}\xbf%@a\x0c>SG\x01p\xe3\x8d7v\xdd\xf0.GS\xb5\xf6\xdak\x87\xb7\xbf\xfd\xed\xc2?\xc3\x8b\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c4\x8d\x81\x981b\xd6\xc81c\xc5\x1b#\xc6\x8cY\xc7l\x1c3}\xbf\x05@q\x827\xd6\xf1$\x8f?\xfe\xf8,\xc1\x8c\x9f\xdd\xe9f\x7f\xad7\xa0,\xb3\x1ac\x00\x03\x18\xc0\x00\x060\x80\x01\x0c\xe4\xc8@\xcc\x1a1s\xe4(\x01b\xc6\xacc6\x8e\x99\xbe_\x02\xe0\xa6\x9bnzm\xf1\xe0\xa5u;\xc9\x05\x0b\x16\x84\r6\xd8 ;(\xe3]:7\xdbl\xb3\xa6\x19\xbe\x1c\x174\xe7l#\xc7\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@\xdf\x0c\xc4\xcc\x91\xe3\'\x04\xc4\x8c\x19\xb3f\xdd\xf2q\xcc\xf41\xdb\xafQ\x02\x14\x97\n\xec_\xc3\x93\x0b\xc7\x1csLv\xe1\x7f\xddu\xd7\r\xef|\xe7;\x85\x7f\x97xa\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03-g f\x8f\x98Ar\xbb\x12 f\xcd:f\xe4\x98\xed\xfb#\x00\xa6\xd4\xed\xe4\xe6\xcf\x9f\x1f^\xf7\xba\xd7e\x05b\x9cx\xff\xfc\xcf\xff\xdc\xf2I\xce\x84\xf6mB\xd5G}0\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06rb f\x90\xdc$@\xcc\x9a1s\xd6-\'\x17\x02\xe0\xdc\xfe\x08\x80\xbb\xebvbG\x1duTV\xe1?^z\xe3_\xfem49m4\xce\x15\xef\x18\xc0\x00\x060\x80\x01\x0c`\xa0:\x0c\xc4,\x92\xdb\xdb\x01b\xe6\xac[N.\x04\xc0\xdd}\n\x80\xf8\x1e\x81\xe2A\xcb\xebtb\xd7]w]Xo\xbd\xf5\xb2\x11\x00\xf1\xe6\x1b\xde\xf3_\x9d\xc5\xcfFd,0\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06rd f\x92\x9cn\x0c\x183g\xcc\x9eu\xca\xca1\xdb\xf7y\x1f\x80\xe2dF\xd6\xe9\x84\xe2\xb1\x8e\x1f?>\x9b\xf0\x1f?~\xc3\xdd\xfem09n0\xce\x19\xf7\x18\xc0@]\x19\x18<xp\xd8v\xdbm\xbde\xcf{\xb31\x80\x81$\x19\x88\xd9$\xa7\x8f\x08\x8c\xd9\xb3ny9f\xfc^\xaf\x02(\xfe\xf2\xd4:\x9dPa3\xc2\x86\x1bn\x98\x85\x00\x184hP\x88\x9f\xc1Y\xd7\x06\xc8qk\xde1\x80\x01\x0c` \x07\x06\xf6\xdak\xaf\xf0\x99\xcf|&\\t\xd1E\xe1\x8e;\xee\x08\x8b\x17/\x0e\x17\\p\x81\xfd[\xf8\xc3\x00\x06\x92e f\x94\x98U\x8a\x90\x99\xfcw\xcc\x9e1\x83\xd6)3\xc7\x8c\xdf\x97\x00\x98_\xa7\x939\xf9\xe4\x93\x93\x87\xac{"\xbd\xf5\xadoMv\xd1\xc8\xa1!t\x8e\x82\x0f\x060\x80\x81\xf4\x18\xd8}\xf7\xdd\xc3\'?\xf9\xc90m\xda\xb4\xb0h\xd1\xa2\xf0\xf4\xd3O\x87\x9e\xbe\x08\x80\xf4\xc6\xde|6\xa6\x18X\x99\x81\x98U\xbasK\xea\x7f\xc6\x0cZ\xa7\xcc\\\x1c\xeb\xfc\x1e\x05\xc0\xe5\x97_\xbev\xf1\x97O\xd7\xe9d\xfe\xf1\x1f\xff1\x0b\xd06\xdah#\xe1\x9f5\xc6\x00\x060\x80\x01\x0ct\x90\x81]v\xd9%|\xfc\xe3\x1f\x0f\xe7\x9f\x7f~W\xe3\xf7\xe4\x93O\xf6\x94\xf5{\xfc\x19\x01\xb0rP\x10\x9c\xd4\x03\x03i2\x103K\xea\xe1?\x9e_\xcc\xa0u\xca\xcc1\xe3\xc7\xac\xbf\x9a\x04X\xb8p\xe1{\xeat"_\xfd\xeaW\xb3\x00,\xdelb\xf3\xcd7\xd7\xf4u\xb0\xe9\xb3I\xa5\xb9I\x19W\xe3\x8a\x01\x0c\xf4\xc6\xc0\x8e;\xee\x18\x8e>\xfa\xe80e\xca\x94\xaeK=\x1f\x7f\xfc\xf1\x1e\x83}\x7f\x7fH\x00`\xad7\xd6\xfc\x1c\x1b)1\x103K.7g\x8fY\xb4N\xd99f\xfd\x9e\x04\xc0!u:\x89\xe1\xc3\x87\'/\x00\xe2Gk\xbc\xeb]\xef\x12\xfe\x85\x7f\x0c`\x00\x03\x18\xc0@\x8b\x18\xd8~\xfb\xed\xc3\xb8q\xe3\xc2\xd7\xbe\xf6\xb5p\xfd\xf5\xd7\x87\xdf\xff\xfe\xf7\xfd\xcd\xf5\xfd~\x1c\x01 \xe4\xa5\x14\xf2\x9c\x0b\x9e\xfbb f\x97\x1c>\x1e0f\xd1:e\xe7B\x00\x1c\xb2\x9a\x00(>"\xe0\xcc\xba\x9c\xc4\xb7\xbf\xfd\xed\xe4\xc3\x7f\xbc\x91\x86;\xfe[`\xfbZ`\xfd\x1d>0\x80\x01\x0c\x94c`\x9bm\xb6\tc\xc7\x8e\r_\xfe\xf2\x97\xc3\xb5\xd7^\x1b~\xf7\xbb\xdf\xf5;\xc4\x0f\xe4\x81\x04@\xb9q\xc2\xb5za\xa0\xde\x0c\xc4\x0c\x93\xc3M\x01c&\xadK~.\x8es\xf2j\x02\xa0\xf8\xe15u9\x811c\xc6$/\x006\xdexc\xff\xda\xd3\xa2\x7f\xed\xb1\xa9\xd4{S1~\xc6\x0f\x03\x18\xe8\x0f\x03#F\x8c\x08\xff\xfa\xaf\xff\x1a&O\x9e\x1c\xae\xba\xea\xaa\xf0\xc0\x03\x0f\x84\x15+V\x0c$\xc77\xfc\xbb?\xf9\xc9O\xec\xe9\xf6t\x0c` +\x06b\x96)\x02g\xd2\xdf1\x93\xd6%?\xc7\xac\xdf\x93\x00x\xb8\x0e\'p\xe3\x8d7\x86\xf5\xd7_?i\x98\xe2\xf9\xf5\xa7\xb9\xf1\x18M0\x060\x80\x01\x0c`\xe0=a\xcb-\xb7\x0c\x07\x1dtP8\xed\xb4\xd3\xc2\x9c9s\xc2/\x7f\xf9\xcb\xb0|\xf9\xf2\x86\x03{\xb3\x7f\xf1\xee\xbb\xef\xb6\xaf\x0b\x7f\x18\xc0@v\x0c\xe4\x90\xd9b6\xadC\x86.\x8e\xf1\xe1\x95\x04\xc0u\xd7]\xf7\x86\x9a\x1cx\x988qb\xd2\xe1\xff\x95\xaf|\xa5\xf7\xfd\xdb \xb2\xdb \x04\x18!\x16\x03\x18\xe8/\x03\xc3\x86\r\x0b\x1f\xf8\xc0\x07\xba\xfa\x81\xd9\xb3g\x87\x9f\xff\xfc\xe7a\xd9\xb2e\xcd\xce\xecM}>\x02\x00\xdf\xfd\xe5\xdb\xe3\xb0\x92\x12\x03\xf1~\x001\xdb\xa4|%@\xcc\xa6u\xc9\xd11\xf3\xffU\x02\x14\xef\xff\xdf\xb1.\x07\x1e-\x7f\xca\x10\xfd\xc3?\xfc\x83\xf0G\x00`\x00\x03\x18\xc0\x00\x06\n\x06\x86\x0c\x19\x12F\x8f\x1e\x1d&L\x98\x10f\xce\x9c\x19~\xfa\xd3\x9f\x86%K\x9645\x9c\xb7\xe3\xc9\x08\x00\xa1.\xa5P\xe7\\\xf0\\\x86\x81\x98mR\xcen[m\xb5Um\x04@\xcc\xfc/\x17\x00\xc7\xd6A\x00\xcc\x9a5+i\x80^\xff\xfa\xd7k\xf84\xfd\x18\xc0\x00\x060\x90-\x03\xfb\xee\xbbo8\xe9\xa4\x93\xc2\xc5\x17_\x1c\xee\xba\xeb\xae\xf0\xdcs\xcf\xb5#\x9f\xb7\xfc5\x08\x00\x81\xa9L`\xf2X\xbc\xa4\xc6@\xcc8)K\x80\x98Q\xeb\x90\xa5\x0b\x01p\xec\xcb\x05\xc09u8\xe8\xc3\x0e;,Yx\\\xfao\xb1Om\xb1w>\x98\xc6\x00\x06\xfab`\xd4\xa8Q\xe1S\x9f\xfaT\xf8\x8f\xff\xf8\x8f\x10o\x92\xf7\xec\xb3\xcf\xb6<\x88w\xea\x05\x08\x00s\xa1\xaf\xb9\xe0\xef\xf0\x91:\x03\xa9\xbf\x15 f\xd4:d\xe9B\x00\x9c\xf3r\x010\xa7\x0e\x07\xfd\xe67\xbf9Y\x01\xf0\xb6\xb7\xbd-\xdb\x7f\xf1I}\xd1s~6v\x0c` w\x06F\x8e\x1c\x19>\xf1\x89O\x84\xf8qx?\xfa\xd1\x8f\xc2SO=\xd5\xa9,\xde\x91\xd7%\x00\xac\x01\xb9\xaf\x01\xce\xdf\x1c\x88Y\'\xd5\xab\x00bF\xadC\x96.\x04\xc0\x9c\xbf\n\x80\xe2\x80\xef\xa9\xfaAO\x9b6-Yh^\xfb\xda\xd7\n\xff.\xf9\xc5\x00\x060\x80\x81$\x18\xd8i\xa7\x9d\xc21\xc7\x1c\x13\xce;\xef\xbc\xb0p\xe1\xc2\xf0\xe7?\xff\xb9#\xa1\xbbJ/J\x00\x08?\x020\x060\xf0\x9e\x103O\xaa\x12 f\xd5\xaa\xe7\xe9\x98\xf9\xff*\x00\n\x1b\xf0\xbfU?\xe0\x83\x0f>8Y`6\xddt\xd3$\x9a>\x0b\x9b\xcd\r\x03\x18\xc0@^\x0c\xec\xb0\xc3\x0e\xe1\xa8\xa3\x8e\n\xe7\x9e{n\xb8\xe1\x86\x1b\xc2\x1f\xff\xf8\xc7*\xe5\xee\xca\x1c\x0b\x01\x90\xd7\xbc\xb0\x0e\x1ao\x0c\xf4\xcc@\xcc<\xa9\n\x80\x98U\xab\x9e\xa7c\xe6\xef\x12\x00\xc5\x81\xbe\xbe\xea\x07\x1b\x8f\xef\x8do|c\x92\xc0l\xb0\xc1\x06\xc2\xbf\x7f\xf5\xc3\x00\x060\x80\x81\xca3\xb0\xddv\xdb\x85#\x8e8"|\xf5\xab_\r\xf3\xe6\xcd\x0b\x0f?\xfcpe\x02v\xd5\x0f\x84\x00\xe89\x0c\x08I\xea\x82\x81\xfc\x18\x88\xd9\'E\t\x10\xb3j\x1d2u\xcc\xfe\xaf(.\xcf\x1bV\xf5\x83\x9d:uj\x92\xa0\xac\xbd\xf6\xda!\xde\x14\xc3\xe2\x97\xdf\xe2g\xcc\x8d9\x060Pe\x06F\x8c\x18\x11>\xfc\xe1\x0f\x87\xb3\xce:+\\}\xf5\xd5\xe1\xc1\x07\x1f\x0c+V\xac\xa8z\xce\xae\xec\xf1\x11\x00\xe6{\x95\xe7\xbbc\xc3g;\x19\x88\xd9\'f\xa0\x14%@\xcc\xacU\xcf\xd51\xfbG\x01p`\xd5\x0ft\xcc\x981IB\x12MQ;\'\x9c\xd7\xb2\xc0c\x00\x03\x18\xc0\xc0\xaa\x0c\xc4\xcf0>\xe4\x90C\xc2\xe9\xa7\x9f\x1e\xae\xbc\xf2\xca\xf0\xeb_\xff:\xbc\xf0\xc2\x0b\x95\r\xd3u<0\x02\xc0\xbc[u\xde\xf9\x7fL\xe4\xcc@\xaaWv\xc7\xccZ\xf5\\\x1d\xb3\xff+\x8a\xf7\x02\x1c[\xf5\x03M\xf1\xee\xff\xf1c\xff\xde\xfd\xeew\x13\x00.\xfb\xc5\x00\x060\x80\x81\xb610l\xd8\xb0\x10\x1b\x94/|\xe1\x0b\xe1\x8a+\xae\x08\xbf\xf8\xc5/\xc2\xb2e\xcb\xea\x98\xa9ku\xcc\x04\x80\xb0\x97s\xd8s\xee\xf8_\x95\x81\x98\x81b\x16J\xed*\x80:|\x1a@\xcc\xfe\xaf(\xc2\xff\xa9U\x16\x003g\xceL\x0e\x8e\x08\xfb\x9b\xde\xf4\xa6\xb65|\xabN:\xffo!\xc6\x00\x060\x90>\x03C\x87\x0e\r\x07\x1ex`8\xe5\x94S\xc2\xacY\xb3\xc2}\xf7\xdd\x17\x96.]Z\xab\xe0\x9c\xca\xc1\x12\x00\xe9\xcf7k\xaa1\xc6@9\x06b\x16JM\x00\xc4\xf3\x89\xd9\xb5\xca\xd9:f\xffx\x05\xc0\x94*\x1f\xe4q\xc7\x1d\x97\x1c\x1c\xfe\xf5\xbf\xdc\x02aAU/\x0c`\x00\x03}30x\xf0\xe0\xb0\xff\xfe\xfb\x87\xcf}\xees\xe1\xd2K/\r\xf7\xdcsOx\xfe\xf9\xe7S\xc9\xcf\xb5?\x0f\x02\xa0o~\xcdo\xf5\xc1@~\x0c\xa4z\x15@\xcc\xaeU\xce\xd61\xfbG\x010\xb3\xca\x07\xb9\xf5\xd6[\'\'\x00\xbc\xf7?\xbfE\xce\xc6f\xcc1\x80\x81f2\xb0\xf7\xde{\x87\x13O<1\xcc\x981#\xdcq\xc7\x1da\xf1\xe2\xc5\xb5\x0f\xc9)\x9f\x00\x01`\xfe7s\xfe{.<\xa5\xc2@\x8a\xf7\x02\x88\xd9\xb5\xca\xd9:f\xff\xf8\x16\x80\xf9U=\xc8\x05\x0b\x16\x84u\xd6Y\')\x01\xb0\xd6Zk\xb9\xf3\xbf\xf7\xfbz\xfb\x07\x060\x80\x81~3\xb0\xc7\x1e{\x84\x13N8!L\x9f>=,Z\xb4(<\xfd\xf4\xd3)g\xe5$\xcf\x8d\x00\x10\xd8R\tl\xce\x03\xcb\xcdd ~"@\xccF)\xbd\x15 f\xd7\x98a\xab\x9a\xafc\xf6\x8fW\x00\xdcU\xd5\x03<\xfb\xec\xb3\x93\x02"\xc2\x1d?\xfb\xb2\x99\x13\xc7sY\x881\x80\x01\x0c\xa4\xc3\xc0\xae\xbb\xee\x1a\xe2\xe5\x83\xe7\x9f\x7f~W\xf3\xf0\xe4\x93O&\x19\x88s;)\x02 \x9d9j\xbd5\x96\x18h.\x031\x1b\xa5$\x00\xe2\xb9\xc4\x0c[\xd5|\x1d\xb3\x7f\xbc\x02\xe0\xc1\xaa\x1e\xe0\x07>\xf0\x81\xe4\x80\xd8l\xb3\xcd\x08\x00\xff\xf2\x87\x01\x0c`\x00\x03a\xc7\x1dw\x0cG\x1f}t\xf8\xf7\x7f\xff\xf7p\xd3M7\x85\xc7\x1e{,\xb7\\\x9c\xcd\xf9\x12\x00\xcd\r\x0c\x02\x98zb \x1d\x06b6JM\x00\xc4\x0c[\xd5|\x1d\xb3\x7f\xbc\x02\xe0\x99\xaa\x1e\xe0;\xde\xf1\x8e\xa4\x80Xo\xbd\xf54\xfd\x9a~\x0c`\x00\x03\x192\xb0\xfd\xf6\xdb\x87q\xe3\xc6\x85\xaf}\xedk\xe1\xfa\xeb\xaf\x0f\x8f>\xfah6\xe1\xd7\x89\x86@\x00\xa4\x13V\x04Oc\x89\x81\xe63\x103RJ\x12 f\xd8\xaa\xe6\xeb\xe2\xb8\x9e\x8e\x02`Y\x15\x0fp\xde\xbcy\xc9\xbd\'\xe4\xef\xff\xfe\xef5\xfe\x196\xfe6\x8a\xe6o\x14j\xaa\xa6Uf`\x9bm\xb6\t\x1f\xf9\xc8G\xc2\x97\xbf\xfc\xe5p\xed\xb5\xd7\x86\x87\x1ezH\x06\xce\xbc\x02\x04\x805\xab\xcak\x96c\xc3g\xa7\x19\x88\x19)%\x01\x10\xefk\x10\xb3l\x153v\xcc\xfe\xf1-\x00\x95<\xb8s\xce9\')\x10\xe2G\xffm\xbe\xf9\xe6\x04\x00\x01\x80\x01\x0c` !\x06F\x8c\x18\x11\x0e=\xf4\xd00y\xf2\xe40w\xee\xdc\xf0\xc0\x03\x0f\x84\x15+Vd\x1ew\x9d\xfe\xaa\x15 \x00\x04\xacN\x07,\xaf\x8f\xc1*3\x103R\xccJ)I\x80\x98e\xab\x9a\xb3++\x00\x0e?\xfc\xf0\xa4 \xf8\xbb\xbf\xfb;M\x7fBM\x7f\x95\x17Q\xc7f\x93\xc7@k\x18\xd8r\xcb-\xc3A\x07\x1d\x14N;\xed\xb40g\xce\x9c\xf0\xab_\xfd*,_\xbe|\xd5\xac\xe7\xffU`\xb5\n\x10\x00\xad\x99\x93\xd6:u\xc5@:\x0c\xc4\xac\x94\x92\x00\x88Y\x96\x00(y\xa5\xc1\xf0\xe1\xc3\x93\x82`\xd3M7%\x00\x08\x00\x0c`\x00\x035a`\xd8\xb0a!\xde\xc4g\xe2\xc4\x89a\xf6\xec\xd9\xe1\xfe\xfb\xef\x0fK\x97.]-\xd8\xf9\x81\n\xf4\xa7\x02\x04@:!E\xe04\x96\x18h\r\x031+\xa5$\x00b\x96%\x00J\x08\x80\x85\x0b\x17\x86W\xbf\xfa\xd5\xc9@\xb0\xee\xba\xebj\xfak\xd2\xf4[\xd4[\xb3\xa8\xab\xab\xbaV\x99\x81!C\x86\x84\xd1\xa3G\x87\t\x13&\x84\xcb.\xbb,\xdc{\xef\xbda\xc9\x92%\xfd\xc9u\x1e\xa3\x02\xfd\xaa\x00\x01`\r\xac\xf2\x1a\xe8\xd8\xf0Y\x15\x06bfJE\x02\xc4,\x1b3m\x15%@%\xdf\x020c\xc6\x8cd\x06?B\xbc\xd1F\x1b\x11\x00\x04\x00\x060\x80\x81\x8a0\xb0\xef\xbe\xfb\x86\x7f\xfb\xb7\x7f\x0b\x97\\rI\xd7\xdd\xd9\x9f{\xee\xb9~\x858\x0fR\x81F+@\x00\x08XU\tX\x8e\x03\x8bUf f\xa6T\x04@<\x8f\x98i\t\x80~^\x05\xf0\xf9\xcf\x7f>\xa9\xc1\x8f\x1f\x05Q\xe5\xc9\xe6\xd8l\x06\x18\xc0@\xaa\x0c\x8c\x1a5*|\xfa\xd3\x9f\x0e\x17^xa\xb8\xfd\xf6\xdb\xc3\xb3\xcf>\xdbh\x86\xf3{*\xd0p\x05\x08\x00kl\xaak\xac\xf3\xc2v3\x19H\xedm\x001\xd3\x12\x00\xfd\x14\x00\x87\x1crH2\x02`\x9du\xd6\x11\xfe+\xf2\xaf~\xcd\\\xa0<\x97\r\x0f\x03\xd5c`\xb7\xddv\x0b\xc7\x1f\x7f|\xf8\xd6\xb7\xbe\x15n\xbb\xed\xb6\xf0\xd4SO5\x1c\xd8\xfc\xa2\n4\xb3\x02\x04@\xf5\xd6\x0bk\xb81\xc1@5\x19\x88\xd9)\x95\xab\x00b\xa6%\x00\xfa)\x00\xb6\xdez\xebd\x06~\x83\r6 \x00\x08\x00\x0c`\x00\x03Mf`\xe7\x9dw\x0e\xc7\x1e{l\xf8\xe67\xbf\x19\x8a\xcf\xb4\r\x8f?\xfex3\xf3\x9a\xe7R\x81\xa6V\x80\x00\xa8f\xd0\x10\x00\x8d\x0b\x06\xaa\xc7@\xccN\xa9\x08\x80\x98i\t\x80~\n\x80\r7\xdc0\x99\x81\xdfd\x93M4\xfeMn\xfc-\xd6\xd5[\xac\x8d\x891i%\x03;\xec\xb0C\x18?~|\x982eJ\xb8\xe1\x86\x1b\xc2\x1f\xfe\xf0\x87\xa6\x863O\xa6\x02\xad\xae\x00\x01`\x8dl\xe5\x1a\xe9\xb9\xf1\x95\x12\x031;\xa5"\x00b\xa6%\x00\xfa!\x00\xae\xbe\xfa\xead\x06}\xd0\xa0A\xe1\xdd\xef~7\x01@\x00`\x00\x03\x18\xe8\'\x03\xdbm\xb7]8\xf2\xc8#\xc39\xe7\x9c\x13\xe6\xcd\x9b\x17\x1e~\xf8\xe1Vg3\xcf\xaf\x02-\xaf\x00\x01 \xa0\xa5\x14\xd0\x9c\x0b\x9e[\xc9@\xccN1C\xa5"\x01b\xb6\xad\x9a\x04\xa8\xdc\xa7\x00\x9c{\xee\xb9\xc9\x0c\xf8k^\xf3\x1aM\x7f?\x9b\xfeV.$\x9e\xdbF\x85\x81j20b\xc4\x880v\xec\xd8p\xd6Yg\x85\xb8A\xfe\xf6\xb7\xbf\r+V\xachy\x18\xf3\x02*\xd0\xee\n\x10\x00\xd5\\\x83\xec\r\xc6\x05\x03\xd5d f\xa8T\x04@\xcc\xb6\x04\xc0\x1a\xae\x028\xe1\x84\x13\x92\x19p\x1f\xffW\xcdE\xc5bo\\0\xd0y\x06.\xbf\xfcra\xbf\xdd)\xd4\xebu\xac\x02\x04@\xe7\xd7\x1c\xeb\xbe1\xc0@}\x18H\xe9\xe3\x00c\xb6%\x00\xd6 \x00\x0e:\xe8\xa0d\x04\x80\xf7\xff\xd7g\xa1\xb1)\x18+\x0c\xb4\x97\x81\xf8/\xfe\xbeT \x97\n\x10\x00\xed]_\xac\xe7\xea\x8d\x81z3\x90\xd2}\x00b\xb6%\x00\xd6 \x00\xe2\x9d\x9dS\xb9\xe4\xe3]\xefz\x97\xb7\x00x\x0b\x00\x060\x80\x81\x1e\x18 \x00r\x89\xbe\xce3V\x80\x00\xa8w\x18\x11&\x8d\x1f\x06\xda\xcb@\xccP\xa9\xe4\xc1\x98m\t\x805\x08\x80\xcd6\xdb,\x89\x01_w\xddu5\xfd=4\xfd\x16\xd0\xf6.\xa0\xea\xad\xdeUe\x80\x00\x10\x8cs\xaa\x00\x01`-\xae\xeaZ\xec\xb8\xb0YU\x06b\x96JA\x02\xc4lK\x00\xacA\x00\xac\xbf\xfe\xfaI\x0c\xf6\xeb^\xf7:\x02\x80\x00\xc0\x00\x060\xd0\x0b\x03\x04@N\xf1\xd7\xb9\x12\x00BVUC\x96\xe3\xc2fU\x19\x88Y*\x05\x01\x10\xb3-\x01\xd0\x87\x00\x88\x1f\xf9\x94\xc2@\xc7sx\xe3\x1b\xdf\xa8\xf1\xef\xa5\xf1\xaf\xeaB\xe3\xb8l\x82\x18h\x1f\x03\x04\x80P\x9cS\x05\x08\x80\xf6\xad-\xd6q\xb5\xc6@\x1a\x0c\xc4,\x95J.\x8c\x19\xb7J\x12\xa0R\x1f\x038c\xc6\x8cd\x06\xda\r\x00\xd3X|l"\xc6\x11\x03\xada\x80\x00\xc8)\xfe:W\x02\xa05\xeb\x88\xf5Y]1\x90.\x03)\xdd\x080f\\\x02\xa0\x97\xab\x00\xce>\xfb\xecd\x04\xc0;\xdf\xf9NW\x00\xb8\x02\x00\x03\x18\xc0@/\x0c\x10\x00BqN\x15 \x00\xd2\r)\x02\xa8\xb1\xc5@k\x18\x88Y*\x95+\x00b\xc6%\x00z\x11\x00\x13&LHb\xa0\x07\r\x1a\xa4\xe9\xef\xa5\xe9\xb7H\xb6f\x91TWu\xad\x1b\x03\x04@N\xf1\xd7\xb9\x12\x00\xd6\xe8\xba\xad\xd1\x8e\x17\xb3U` f\xaa\x14$@\xcc\xb8\x04@/\x02\xe0\xb8\xe3\x8eKb\x90}\x02\x80E\xb3\n\x8b\xa6c\xc0a\x95\x19 \x00\x84\xe2\x9c*@\x00X\x8f\xab\xbc\x1e;6|V\x95\x81T>\t f\\\x02\xa0\x17\x01p\xd8a\x87%!\x00\xd6[o=W\x00\xb8\x02\x00\x03\x18\xc0@\x1f\x0c\x10\x009\xc5_\xe7J\x00\x08XU\rX\x8e\x0b\x9bUf f\xaa\x14\xae\x00\x88\x19\x97\x00\xe8E\x00\x1cp\xc0\x01I\x0c\xf2\x1b\xde\xf0\x06\x8d\x7f\x1f\x8d\x7f\x95\x17\x1a\xc7f#\xc4@{\x18 \x00\x84\xe2\x9c*@\x00\xb4g]\xb1~\xab3\x06\xd2b f\xaa\x14\x04@\xcc\xb8\x04@/\x02`\xe7\x9dwNb\x90}\x04`Z\x8b\x8f\xcd\xc4xb\xa0\xf9\x0c\x10\x009\xc5_\xe7J\x004\x7f\r\xb1.\xab)\x06\xd2g \x95\x8f\x02\x8c\x19\x97\x00\xe8E\x00l\xb1\xc5\x16I\x08\x80\xb7\xbc\xe5-\xae\x00p\x05\x00\x060\x80\x81>\x18 \x00\x84\xe2\x9c*@\x00\xa4\x1fT\x84Qc\x8c\x81\xe63\x103U\nW\x00\xc4\x8cK\x00\xf4"\x00\xde\xf1\x8ew$1\xc8o{\xdb\xdb4\xfe}4\xfe\x16\xc8\xe6/\x90j\xaa\xa6uc\x80\x00\xc8)\xfe:\xd7Y\xb3f\xe9\x0b\xf4\x05\x18\xc0\x00\x06J2\x103U\n\x02 f\\\x02\xa0\x17\x01\xb0\xc9&\x9b$1\xc8o\x7f\xfb\xdbM\xf0\x92\x13\xbcn\xe1\xc5\xf1\n\xdc\x18\x18\x18\x03\x04\x80P\x9cS\x05.\xb8\xe0\x02}\x81\xbe\x00\x03\x18\xc0@I\x06b\xa6JA\x00\xc4\x8cK\x00\xf4"\x00R\xb9\xccc\xd3M75\xc1KNpaj`aJ\xfd\xd4\xafn\x0c\x10\x009\xc5_\xe7J\x00X\xa3\xeb\xb6F;^\xccV\x81\x81\x98\xa9R\x10\x001\xe3\x12\x00\xbd\x08\x80\x8d6\xda(\x89A\xdel\xb3\xcd\x08\x00\x02\x00\x03\x18\xc0@\x1f\x0c\x10\x00BqN\x15 \x00\x84\xa9*\x84)\xc7\x80\xc3\xba1\x103U\n\x02 f\\\x02\xa0\x17\x01\xf0\xfa\xd7\xbf>\x89A~\xe7;\xdf\xa9\xf1\xef\xa3\xf1\xaf\xdb\xe2\xe3xm\x98\x18h>\x03\x04@N\xf1\xd7\xb9\x12\x00\xcd_C\xac\xcbj\x8a\x81\xf4\x19\x88\x99*\x05\x01\x10?\xce\x90\x00\xe8E\x00\xac\xb7\xdezI\x0c\xf2\xbb\xde\xf5.\x02\x80\x00\xc0\x00\x060\xd0\x07\x03\x04\x80P\x9cS\x05\x08\x80\xf4\x83\x8a0j\x8c1\xd0|\x06b\xa6JA\x00\xc4\x8cK\x00\xf4"\x00^\xf5\xaaW%1\xc8\xef~\xf7\xbb5\xfe}4\xfe\x16\xc8\xe6/\x90j\xaa\xa6uc\x80\x00\xc8)\xfev\xf6\\\x17/^\x1c\xe2\xc7\xf0]r\xc9%\xe1\xbf\xff\xfb\xbf;r0\x04\x805\xbank\xb4\xe3\xc5l\x15\x18\x88\x99*\x05\x01\x103.\x01\xd0\x8b\x00X{\xed\xb5\x93\x18\xe4\xcd7\xdf\x9c\x00 \x000\x80\x01\x0c\xf4\xc1\x00\x01\xd0\x91\x1c\x9a\xfc\x8b>\xff\xfc\xf3\xe1\xde{\xef\r\x97]vY8\xf9\xe4\x93\xc3\x81\x07\x1e\x18\x06\x0f\x1e\xfc\xd7\xb9\xb8h\xd1\xa2\x8e\xd4\x80\x00\x10\xa6\xaa\x10\xa6\x1c\x03\x0e\xeb\xc6@\xccT)\x08\x80\x98q\t\x00\x02\xe0\xaf\xcdH\xdd&\xa2\xe3\xb5y`\x00\x03\xcd`\x80\x00\xe8H\x0eM\xeaE\x97.]\x1a\xee\xbf\xff\xfe0{\xf6\xec0i\xd2\xa4\xf0\xc1\x0f~0\x0c\x1d:\xb4\xcf\xfd\x95\x00\xb0~5c\xfd\xf2\x1c8\xc2@{\x18 \x00nm\x898xE\x95l\x84+\x00\xda3\x99,Z\xea\x8c\x01\x0ct\x9a\x01\x02 \xa9,\xde\xf2\x93Y\xbe|y\xf8\xd5\xaf~\x15\xe6\xcc\x99\x13\xbe\xf4\xa5/\x85\x83\x0f>8l\xb1\xc5\x16}\x86\xfd\x9e\x18\'\x00\xac}=q\xe1g\xb8\xc0@5\x19 \x00\x08\x80\xda\\\x02\xe2-\x00\xd5\\D,\xee\xc6\x05\x03\xd5a\x80\x00hyf\xae\xed\x0b\xbc\xf0\xc2\x0b\xe1\x81\x07\x1e\x08s\xe7\xce\rg\x9eyf8\xec\xb0\xc3\xc2\xf0\xe1\xc3K\x87\xfd\x9e\xe6;\x01P\x9d5\xa0\xa7\xf1\xf13\xe3\x83\x01\x0c\xbc\x9c\x01\x02\x80\x00 \x00\xfax?\xed\xcb\'\x8b\xff\xb6xb\x00\x03Ug\x80\x00\xa8m>o\xfa\x81?\xf4\xd0C\xe1\xba\xeb\xae\x0b_\xf9\xcaW\xc2\xe1\x87\x1f\x1e\xb6\xdez\xeb\xa6\x84\xfd\x9e\xe6\x00\x01`m\xec\x89\x0b?\xc3\x05\x06\xaa\xc9\x00\x01@\x00\x10\x00\x04@\xcb\x9aB\x0b\x7f5\x17~\xe3\x92\xee\xb8\x10\x00M\xcf\xd1\xb5x\xc2G\x1f}4,X\xb0 |\xfd\xeb_\x0f\xe3\xc6\x8d\x0b\xdbo\xbf}[\xd7u\x02 \xdd5\xc5~al1\x90\x1e\x03\x04\x00\x01@\x00\x10\x00mm\x14m$\xe9m$\xc6\xb4:cJ\x00\xd4"\xaf\x0f\xe8 \x1f{\xec\xb1p\xf3\xcd7\x87o|\xe3\x1b\xe1\xe8\xa3\x8f\x0e\xef{\xdf\xfb:\xbe\x86\x13\x00\xd5Y\x03\xac\xc7\xc6\x02\x03\x18X\x13\x03\x04\x00\x01@\x00\x10\x00\x1do\x1e\xd7\xb4P\xf9{\x9b\x19\x06\xfa\xc7\x00\x010\xa0l]\xb9_~\xe2\x89\'\xc2\x0f\x7f\xf8\xc30u\xea\xd4p\xdcq\xc7\x85]v\xd9\xa5\x92\xeb5\x01\xd0\xbf\xf9i\x1dS\'\x0c`\xa0\n\x0c\x10\x00\x04\x00\x01@\x00T\xb2\xa1\xac\xc2\x02\xe9\x18l\xd4uc\x80\x00\xa8\\\x86\xef\xf7\x01=\xf5\xd4S\xe1\xc7?\xfeq\x98>}z8\xe1\x84\x13\xc2\x1e{\xecQ\x9b\xb5\x99\x00\xb0V\xd6m\xadt\xbc\x98\xcd\x99\x01\x02\x80\x00 \x00\x08\x80\xda4\x999/\xd6\xce]\xb3\xd2\x1f\x06\x08\x80~\xe7\xed\x8e>\xf0\xd9g\x9f\rw\xdeyg\x981cF8\xf1\xc4\x13\xc3\xde{\xef]\xebu\x98\x00\xb0>\xf5g}\xf2\x18\x9c`\xa0\x1a\x0c\x10\x00\x04\x00\x01@\x00\xd4\xba\xf1\xb4\x99Tc31\x0e\xd5\x18\x07\x02\xa0\xa3\xb9\xbe\xc7\x17\xff\xcb_\xfe\x12\xee\xb9\xe7\x9ep\xe9\xa5\x97\x86\xcf}\xeesa\xbf\xfd\xf6\x0b\x83\x07\x0fNj\xdd%\x00\xaa1\xff\xad\xc3\xc6\x01\x03\x18\xe8\x0f\x03\x04\x00\x01@\x00\x10\x00I5\xa2\xfdY\xf8<\xc6\x06\x99*\x03\x04@\x8f\x19\xbcm?\\\xbati\xb8\xef\xbe\xfb\xc2\xacY\xb3\xc2)\xa7\x9c\x12\xde\xff\xfe\xf7\x87!C\x86$\xbf\xc6\x12\x00\xd6\xd4T\xd7T\xe7\x85\xed\x14\x19 \x00\x08\x00\x02\x80\x00H\xbe9Mq\xf1vN\x9a\x92\x9e\x18 \x00\xda\x96\xf5\xc3\xb2e\xcb\xc2/~\xf1\x8bp\xc5\x15W\x84/|\xe1\x0ba\xcc\x981a\xd8\xb0aY\xae\xa7\x04\x80\xf5\xa8\xa7\xf5\xc8\xcfp\x81\x81j2@\x00\x10\x00\x04\x00\x01\x90e\xc3jS\xaa\xe6\xa6d\\\x066.\x04@k\x04\xc0\x0b/\xbc\x10~\xfd\xeb_\x87+\xaf\xbc2\x9c~\xfa\xe9\xe1\x90C\x0e\t[n\xb9\xa5\xb5\xf3\xc5\xfd\x93\x00\x18\xd8\xbc\xb5\xee\xa9\x1f\x060\xd0N\x06\x08\x00\x02\x80\x00 \x004\xb1\x18\xc0@"\x0c\x10\x00\x03\x17\x00+V\xac\x08\x0f>\xf8`\x88\xb5<\xeb\xac\xb3\xc2\xd8\xb1c\xc3\x88\x11#\xcc\x91>\xe6\x08\x01 \xbc\xb43\xbcx-\xbca``\x0c\x10\x00\x04\x00\x01\xd0GSc\x81\x19\xd8\x02\xa3~\xea\x87\x81\xf62@\x00\x94\x17\x00\x0f?\xfcp\x987o^8\xe7\x9cs\xc2\x91G\x1e\x19\xb6\xddv[a\xbf\xe4\xbeH\x00\xb4w\x9e[W\xd5\x1b\x03\x18\x18\x08\x03\x04\x00\x01@\x00\x94lt\x062\xe1\xfc\xae\x05\x1b\x03\x18h%\x03\x04@\xdf\x02\xe0\x0f\x7f\xf8C\xb8\xe1\x86\x1b\xc2\x94)S\xc2\xf8\xf1\xe3\xc3{\xdf\xfb^a\xbf\t{ \x01`]k\xe5\xba\xe6\xb9\xf1\x85\x81\xe62@\x00\x10\x00\x04@\x13\x9a\x1f\x0bSs\x17&\xf5TO\x0c4\xc6\x00\x01\xf0\x92\x00x\xfc\xf1\xc7\xc3-\xb7\xdc\x12\xbe\xf9\xcdo\x86c\x8e9&\xec\xb4\xd3N\xc2~\x8b\xf6;\x02\xa0\xb1\xf9j\x9dS7\x0c`\xa0\x13\x0c\x10\x00\x04\x00\x01\xd0\xa2\x86\xa8\x13\x13\xdak\xdaH0\x907\x03\xb9\n\x80\xff\xfd\xdf\xff\r\xb7\xddv[\xf8\xd6\xb7\xbe\x15>\xf1\x89O\x84\x91#G\n\xfbm\xdc\xdb\x08\x80\xbc\xd7\x1d\xfb\x8e\xf1\xc7@\xbd\x18 \x00\x08\x00\x02\xa0\x8dM\x92\x05\xb2^\x0b\xa4\xf12^uc \x07\x01\xf0\xcc3\xcf\x84\xdbo\xbf=\\x\xe1\x85\xe1S\x9f\xfaT\x185j\x94\xb0\xdf\xe1}\x8c\x00\xb0V\xd6m\xadt\xbc\x98\xcd\x99\x01\x02\x80\x00 \x00:\xdc8\xe5\xbc\x009w\x1b0\x06\x9a\xcb@j\x02`\xf1\xe2\xc5\xe1\xee\xbb\xef\x0e\x97\\rI8\xe9\xa4\x93\xc2\xbe\xfb\xee+\xecWp\xcf"\x00\x9a;\x8f\xad\x8b\xea\x89\x01\x0c\xb4\x92\x01\x02\x80\x00 \x00*\xd8L\xb5r\xd2{n\x9b\n\x06\xd2e\xa0\xce\x02\xe0\xf9\xe7\x9f\x0f\xf7\xde{o\x989sf\x980aB\x18=zt\x182d\x88\xc0_\x83=\x8a\x00HwM\xb1_\x18[\x0c\xa4\xc7\x00\x01@\x00\x10\x005h\xae,\xbe\xe9-\xbe\xc6\xd4\x98\xb6\x82\x81\xba\x08\x80\xa5K\x97\x86\xfb\xef\xbf?\xcc\x9e=;L\x9c81|\xe0\x03\x1f\x08\xc3\x86\r\x13\xf6k\xba\x1f\x11\x00\xd6\xb3V\xacg\x9e\x13W\x18h\r\x03\x04\x00\x01@\x00\xd4\xb4\xe1\xb2(\xb6fQTWu\xad3\x03U\x14\x00\xcb\x97/\x0f\xbf\xfa\xd5\xaf\xc2\x9c9s\xc2i\xa7\x9d\x16\x0e:\xe8\xa0\xb0\xc5\x16[\x08\xfb\t\xed=\x04\x80u\xb3\xce\xeb\xa6c\xc7on\x0c\x10\x00\x04\x00\x01\x90P\x13\x96\xdb\x02\xe6|m\xda\x18X\x99\x81N\x0b\x80\x15+V\x84\x07\x1ex \\u\xd5Ua\xf2\xe4\xc9\xe1\xd0C\x0f\r\xc3\x87\x0f\x17\xf6\x13\xdfg\x08\x80\x95\xe7\xa1uI=0\x80\x81*3@\x00\x10\x00\x04@\xe2\x8dY\x95\x17 \xc7f\x83\xc4@s\x19\xe8\x94\x00\xf8\xf3\x9f\xff\x1c>\xf2\x91\x8f\x84\xad\xb7\xdeZ\xd8\xcfpO!\x00\x9a;\x8f\xad\x8b\xea\x89\x01\x0c\xb4\x92\x01\x02\x80\x00 \x002l\xd6Z\xb9\xa8xn\x9b\x16\x06:\xc7@\xa7\x04@\xfcW\x7f\xe3\xde\xb9q\xeft\xed\t\x80|\xc7\xbe\xd3\xecy}\xeca\xa0<\x03\x04\x00\x01@\x00$&\x00\xf6\xdcs\xcf\xf0\xcdo~3\xdcy\xe7\x9d\xe1\xd1G\x1f\r\x7f\xf8\xc3\x1f\xc2=\xf7\xdc\x13\xa6O\x9f\x1e\xf6\xdf\x7f\x7fMzb\xe3m\xe3+\xbf\xf1\xa5\\3\x02\x00\x0f\x9d\xe0\x9b\x00\xc0]\'\xb8\xf3\x9a\xb8\xc3@c\x0c\x10\x00\x04\x00\x01\x90H \xdcj\xab\xad\xba>+{\xd9\xb2e\xa1\xaf\xaf\x1bn\xb8\xa1\xeb\x8e\xdb\x16\xcd\xc6\x16MuS\xb7*3@\x00\xe0\xb3\x13|\x12\x00\xb8\xeb\x04w^\x13w\x18h\x8c\x01\x02\x80\x00 \x00\x12\x10\x00\xdbm\xb7]\xf8\xe9O\x7f\xdaW\xee_\xed\xef\x88\x80\xc6\x16M\x9b\x8d\xbaU\x99\x01\x02\x00\x9f\x9d\xe0\x93\x00\xc0]\'\xb8\xf3\x9a\xb8\xc3@c\x0c\x10\x00\x04\x00\x01\x90\x80\x00\x18H\xf3E\x044\xb6x\xdat\xd4\xad\x8a\x0c\x10\x00\xb8\xec\x04\x97\x03\xd9\x83V\xb3\xd3%~p\xc1\x05\x17x[[\x02=L\'\x98\xf5\x9a\xd6\xca\x9c\x19 \x00\x08\x00\x02\xa0\xe6\x9b\xe7)\xa7\x9cR\xa2]\xea\xfd\xa1D\x80\xcd0\xe7\xcd0\x95s\x9f6mZ\xef\x93\xbc\x85\x7f\xe3&\x80y\xaf\x1f\x04@\xde\xe3\x9f\xca\xfa\xe9<p\x9c\x0b\x03\x04\x00\x01@\x00\xd4\\\x00<\xf8\xe0\x83Mm\xeb\x89\x00\x1b`.\x1b`\x8a\xe7\xf9\x85/|\xa1\xa9\xebA\x7f\x9f\x8c\x00\xc8{\xdd \x00\xf2\x1e\xff\x14\xd7R\xe7\x84\xe9\x94\x19 \x00\x08\x00\x02\xa0\xc6\x02`\xf4\xe8\xd1\xfd\xed\xcfK?\x8e\x08\xb0\xf9\xa5\xbc\xf9\xa5zn\x04\x80y\xdb\t\xb6\t\x00\xdcu\x82;\xaf\x89;\x0c4\xc6\x00\x01@\x00\x10\x005\x16\x00\xff\xf6o\xffV:\xd8\x97\xfd\x05"\xa0\xb1\xc5\xd5\xa6\xa4n\x9d`\x80\x00\xc0]\'\xb8#\x00p\xd7\t\xee\xbc&\xee0\xd0\x18\x03\x04\x00\x01@\x00\xd4X\x00L\x9e<\xb9l\x9eo\xf8\xf1D@c\x8b\xac\xcdI\xdd\xda\xc9\x00\x01\x80\xb7v\xf2\xd6\xfdZ\x04\x00\xee\xbaY\xf0\'\x160P}\x06\x08\x00\x02\x80\x00\xa8\xb1\x008\xeb\xac\xb3\x1a\x0e\xf4\x8d\xfe"\x11P\xfd\x85\xdd\xe6\x9b\xef\x18\x11\x00\xf9\x8e}\'\xe7=\x01\x80\xbbN\xf2\xe7\xb5\xf1\x87\x81r\x0c\x10\x00\x04\x00\x01@\x004\xe4\x02\x88\x80r\x8b\xad\xcdI\xbd\xda\xc1\x00\x01\x80\xb3vp\xb6\xeak\x10\x00\xb8[\x95\t\xff\x8f\t\x0cT\x97\x01\x02\x80\x00 \x00\x08\x80\x86\x04@\xf7/\x11\x01\xd5]\xe0m\xbe\xf9\x8d\r\x01\x90\xdf\x98Wa\x9e\x13\x00\xb8\xab\x02\x87\x8e\x01\x87\x18\xe8\x1f\x03\x04\x00\x01@\x00\x10\x00\xddY~@\x7f\x12\x01\xfd[tmN\xea\xd4J\x06\x08\x00|\xb5\x92\xaf\xde\x9e\x9b\x00\xc0]ol\xf8960P=\x06\x08\x00\x02\x80\x00 \x00\x06\x14\xfcW\xfde"\xa0z\x0b\xbd\xcd7\x9f1!\x00\xf2\x19\xeb*\xcdk\x02\x00wU\xe2\xd1\xb1\xe0\x11\x03}3@\x00\x10\x00\x04\x00\x01\xb0j\x86o\xca\xff\x13\x01}/\xbe6\'\xf5i\x05\x03\x04\x00\xaeZ\xc1\xd5\x9a\x9e\x93\x00\xc0\xdd\x9a\x18\xf1\xf7\x18\xc1@u\x18 \x00\x08\x00\x02\x80\x00hJ\xe0\xef\xedI\x88\x80\xea,\xf86\xdf\xf4\xc7\x82\x00H\x7f\x8c\xab8\x8f\t\x00\xdcU\x91K\xc7\x84K\x0c\xf4\xcc\x00\x01@\x00\x10\x00\x04@o\xd9\xbd\xa9?\'\x02z^\x84mN\xea\xd2L\x06\x08\x00<5\x93\xa7\xfe>\x17\x01\x80\xbb\xfe\xb2\xe2qX\xc1@\xe7\x19 \x00\x08\x00\x02\x80\x00hj\xd0_\xd3\x93\x11\x01\x9d_\xf8m\xbe\xe9\x8e\x01\x01\x90\xee\xd8Vy\xde\x12\x00\xb8\xab2\x9f\x8e\r\x9f\x18X\x99\x01\x02\x80\x00 \x00\x08\x805e\xf6\x96\xfc=\x11\xb0\xf2blsR\x8ff0@\x00\xe0\xa8\x19\x1c\x95}\x0e\x02\x00we\x99\xf1x\xcc`\xa0s\x0c\x10\x00\x04\x00\x01@\x00\xb4$\xe0\xf7\xf7I\x89\x80\xcem\x006\xdf\xf4jO\x00\xa47\xa6u\x98\xa7\x04\x00\xee\xea\xc0\xa9c\xc4)\x06\xfe\x8f\x01\x02\x80\x00 \x00\x08\x80\xfef\xf5\x96>\x8e\x08\xb01\xdb\x98\x07\xce\x00\x010\xf0\x1a\xe2\xb0|\r\t\x80\xf25\xc3\x99\x9aa\x00\x03\x9db\x80\x00 \x00\x08\x00\x02\xa0\xa5\xc1\xbe\xec\x93\x13\x016\xc4Nm\x88)\xbc.\x01`\xfet\x82c\x02\x00w\x9d\xe0\xcek\xe2\x0e\x03\x8d1@\x00\x10\x00\x04\x00\x01P6\xa3\xb7\xe5\xf1D@c\x8b\xba\xcd0\xef\xba\x11\x00y\x8f\x7f\xa7\xe6?\x01\x80\xbbN\xb1\xe7u\xb1\x87\x81\xf2\x0c\x10\x00\x04\x00\x01@\x00\xb4%\xd07\xfa"D@\xf9\x85\xddf\x98o\xcd\x08\x80|\xc7\xbe\x93\xf3\x9e\x00\xc0]\'\xf9\xf3\xda\xf8\xc3@9\x06\x08\x00\x02\x80\x00 \x00\x1a\xcd\xe6m\xfd="\xa0\xdc\xe2n3\xcc\xb3^\x04@\x9e\xe3\xde\xe9\xf9N\x00\xe0\xae\xd3\x0cz}\x0cb\xa0\xff\x0c\x10\x00\x04\x00\x01@\x00\xb45\xc8\x0f\xf4\xc5\x88\x80\xfe/\xf06\xc3\xfcjE\x00\xe47\xe6U\x98\xe7\x04\x00\xee\xaa\xc0\xa1c\xc0!\x06\xfa\xc7\x00\x01@\x00\x10\x00\x04\xc0@3yG~\x9f\x08\xe8\xdf"o3\xcc\xabN\x04@^\xe3]\x95\xf9M\x00\xe0\xae*,:\x0e,b`\xcd\x0c\x10\x00\x04\x00\x01@\x00t$\xc07\xebE\x89\x805/\xf46\xc3|jD\x00\xe43\xd6U\x9a\xd7\x04\x00\xee\xaa\xc4\xa3c\xc1#\x06\xfaf\x80\x00 \x00\x08\x80\x1a\x0b\x80\xd3O?\xbdY9\xba\xf6\xcfC\x04\xf4\xbd\xd8\xdb\x0c\xf3\xa8\x0f\x01\x90\xc78Wm>\x13\x00\xb8\xab\x1a\x93\x8e\x07\x93\x18\xe8\x9d\x01\x02\x80\x00 \x00j,\x00>\xf3\x99\xcf\xd4>\xb87\xfb\x04\x88\x80\xde\x17|\x9ba\xfa\xb5!\x00\xd2\x1f\xe3*\xcec\x02\x00wU\xe4\xd21\xe1\x12\x03=3@\x00\x10\x00\x04@\x8d\x05\xc0^{\xed\xd5\xec\xfc\x9c\xcc\xf3\x11\x01=/\xfa6\xc3\xb4\xebB\x00\xa4=\xbeU\x9d\xbf\x04\x00\xee\xaa\xca\xa6\xe3\xc2&\x06Vg\x80\x00 \x00\x08\x80\x1a\x0b\x80\xb8\xa8\xdd\x7f\xff\xfd\xc9\x84\xf6V\x9c\x08\x11\xb0\xfa\xc2o3L\xb7&\x04@\xbac[\xe5yK\x00\xe0\xae\xca|:6|b`e\x06\x08\x00\x02\x80\x00\xa8\xb9\x008\xfe\xf8\xe3[\x91\x9b\x93{N"`\xe5\xc5\xdff\x98f=\x08\x804\xc7\xb5\xea\xf3\x95\x00\xc0]\xd5\x19u|\x18\xc5\xc0K\x0c\x10\x00\x04\x00\x01Ps\x01\x10\x17\xb4k\xae\xb9&\xb9\xc0\xde\xaa\x13"\x02^\xda\x00l\x86\xe9\xd5\x82\x00HoL\xeb0O\t\x00\xdc\xd5\x81S\xc7\x88S\x0c\xfc\x1f\x03\x04\x00\x01@\x00$ \x00\xb6\xd8b\x8bp\xcb-\xb7\xb4*3\'\xf9\xbcD\x80F \xc5F\x80\x00\xc0u\'\xb8&\x00p\xd7\t\xee\xbc&\xee0\xd0\x18\x03\x04\x00\x01@\x00$ \x00\xe2\x028x\xf0\xe0p\xce9\xe7\x84\xc5\x8b\x17\'\x19\xd8[uRD@c\x9b\x87M\xb7\x9au#\x00\xaa9.\xa9\xcf\x17\x02\x00w\xa93\xee\xfc0\x9e\x12\x03\x04\x00\x01@\x00$"\x00\xba\x17\xa6\x1dw\xdc1\\t\xd1E\xe1\xb9\xe7\x9ekUfN\xf2y\x89\x00\x9b{\xf7\x1c\xaa\xf3\x9f\x04\x00\x8e;\xc1/\x01\x80\xbbNp\xe75q\x87\x81\xc6\x18 \x00\x08\x00\x02 1\x01\xd0\xbd\x18F\x110c\xc6\x0c"\xa0\xa4\xae \x02\x1a\xdbL\xba\xb9\xf3gg\xebG\x00t\xb6\xfe\xb9\xf2O\x00\xe0.W\xf6\x9d7\xf6\xeb\xc8\x00\x01@\x00\x10\x00\x89\n\x80\xee\x05\xa9[\x04\xfc\xe5/\x7f)\x19\x85\xf3~8\x11`S\xef\x9eCu\xfa\x93\x00\xc0m\'x%\x00p\xd7\t\xee\xbc&\xee0\xd0\x18\x03\x04\x00\x01@\x00$.\x00\xba\x17\xc7(\x02.\xbe\xf8\xe2@\x04\x94\x13\x1bD@c\x9bK7w\xfelo\xfd\x08\x80\xf6\xd6\x1b\xdf\xffWo\x02\x00w\xe6\x02\x060P\x1f\x06\x08\x00\x02\x80\x00\xc8D\x00t/\xcc;\xed\xb4S\xb8\xe4\x92K\xc2\xf3\xcf?_.\tg\xfeh"\xa0>\x1b[7\xeb9\xfeI\x00\xe0\xb4\x13\xdc\x13\x00\xb8\xeb\x04w^\x13w\x18h\x8c\x01\x02\x80\x00 \x002\x13\x00\xdd\x8b\xe5\xce;\xef\x1c\xbe\xfd\xedo\x13\x01%\xc5\x06\x11\xd0\xd8f\xd3\xcd\x9d?[[?\x02\xa0\xb5\xf5\xc5o\xcf\xf5%\x00z\xae\x0b^\xd4\x05\x03\x18\xa8"\x03\x04\x00\x01@\x00d*\x00\xba\x17\xa4]v\xd9%\\z\xe9\xa5D\x00\x11\x10\xba\x99\xf0g}\x1b\x16\x02\xa0\xbecW\xe7yG\x00\xe0\xae\xce\xfc:v\xfc\xe6\xc6\x00\x01@\x00\x10\x00\x99\x0b\x80\xeeE/\x8a\x80\x993g\x86%K\x96\x94\x8c\xc2y?\xdc\x15\x01\x1a\x87\xee9T\x85?\t\x00<v\x82C\x02\x00w\x9d\xe0\xcek\xe2\x0e\x03\x8d1@\x00\x10\x00\x04\x00\x01\xb0\xd2\xbf\xfc\xee\xba\xeb\xaeD@\x03N\x83\x08hl\x13\xb2y7\xb7n\x04@s\xeb\x89\xcf\xfe\xd5\x93\x00\xe8_\x9d\xf0\xa4N\x18\xc0@\x15\x18 \x00\x08\x00\x02\x80\x00XI\x00t/L#G\x8e$\x02\x88\x80\x1e\xd9\xe8f\xc4\x9f\xd5kd\x08\x80\xea\x8dI\x0e\xf3\x84\x00\xc0]\x0e\x9c;G\x9c\xa7\xc2\x00\x01@\x00\x10\x00\x04@\x9f!/\x8a\x80\xef|\xe7;a\xe9\xd2\xa5\r\xc4\xe1|\x7f\xc5\x15\x01\x1a\x85N4\n\x04\x00\xee:\xc1\x1d\x01\x80\xbbNp\xe75q\x87\x81\xc6\x18 \x00\x08\x00\x02\x80\x00\xe8S\x00t/\xaeQ\x04\xcc\x9a5\x8b\x08(\xe94\x88\x80\xc66\xa7n\xee\xfcY\xae~\x04@\xb9z\xe1\xab9\xf5"\x00\x9aSG<\xaa#\x060\xd0\x0e\x06\x08\x00\x02\x80\x00 \x00\xfa%\x00\xba\x17\xa4\xddv\xdb-\\~\xf9\xe5D\x00\x11P\x8a\x9bn~\xfc\xd9\xda\xe6\x86\x00hm}\xf1\xdbs}\t\x80\x9e\xeb\x82\x17u\xc1\x00\x06\xaa\xc8\x00\x01@\x00\x10\x00\x04@CA.\x8a\x80\xd9\xb3g\x13\x01D@C\xfcTqCL\xe1\x98\x08\x00\xcdf\'8&\x00p\xd7\t\xee\xbc&\xee0\xd0\x18\x03\x04\x00\x01@\x00\x10\x00\x03\np\xbb\xef\xbe;\x11PR\x02\xc4\x87{k@c\x9b\x96\xcd\xbe\xef\xba\x11\x00}\xd7\x07?\xad\xa9\x0f\x01\xd0\x9a\xba\xe2U]1\x80\x81V0@\x00\x10\x00\x04\x00\x010 \x01\xd0\xbd0\xed\xb1\xc7\x1e\xe1\xbb\xdf\xfd\xae+\x02J\xca\x00"\xc0\xe6\xde=\x87\x9a\xf1\'\x01\x80\xa7fpT\xf69\x08\x00\xdc\x95e\xc6\xe31\x83\x81\xce1@\x00\x10\x00\x04\x00\x01\xd0\x14\x01\xd0\xbd\x90G\x11p\xc5\x15W\x84e\xcb\x96\x95\x8c\xc2y?\x9c\x08\xe8\xdcF\xd8\xcdn\n\x7f\x12\x008\xea\x04\xc7\x04\x00\xee:\xc1\x9d\xd7\xc4\x1d\x06\x1ac\x80\x00 \x00\x08\x00\x02\xa0\xa9\x02\xa0{1\xdes\xcf=\xc3\xf7\xbe\xf7="\xa0\xa4\xd7 \x02\x1a\xdb\xcc\xba\xb9\xcb\xfdO\x02\x00?\x9d\x98\x03\x04\x00\xee:\xc1\x9d\xd7\xc4\x1d\x06\x1ac\x80\x00 \x00\x08\x00\x02\xa0%\x02\xa0{Q\x1e5jT\x983g\x0e\x11@\x04\xb4\x94\xb3n\xder\xff\x93\x00h\xac\x19\xca\x9d\x9b\x81\x9e?\x01\x80\xbb\x812\xe4\xf71\x84\x81\xf61@\x00\x10\x00\x04\x00\x01\xd0\x96`F\x04\x944\x00/>\xdc\x15\x01\xed\xdb\x10Sh>\x08\x00\xbct\x82c\x02\x00w\x9d\xe0\xcek\xe2\x0e\x03\x8d1@\x00\x10\x00\x04\x00\x01\xd0\x16\x01\xd0\xbdHG\x11\xf0\xfd\xef\x7f?,_\xbe\xbc\xb1D\x9c\xe9o\x11\x01\x8dmr\xdd\xdc\xe5\xf2\'\x01\x80\x93N\xb0N\x00\xe0\xae\x13\xdcyM\xdca\xa01\x06\x08\x00\x02\x80\x00 \x00\xda*\x00\xba\x17\xeb\xbd\xf6\xda+\\y\xe5\x95D@I\xa1A\x044\xb6\xd9us\x97\xfa\x9f\x04\x00>:\xc18\x01\x80\xbbNp\xe75q\x87\x81\xc6\x18 \x00\x08\x00\x02\x80\x00\xe8\x88\x00\xe8^\xb4\xf7\xde{\xef\xf0\x83\x1f\xfc\x80\x08 \x02:\xcaa7\x8fu\xff\x93\x00h\xac\x19\xaa\xfb\xb8w\xfa\xf8\t\x00\xdcu\x9aA\xaf\x8fA\x0c\xf4\x9f\x01\x02\x80\x00 \x00\x08\x80J\x04\xaf}\xf6\xd9\'\\u\xd5UD\x00\x11P\t\x1e\xeb\xdaH\x10\x00\xfdo\x80\xea:\xc6U<n\x02\x00wU\xe4\xd21\xe1\x12\x03=3@\x00\x10\x00\x04\x00\x01P\xa9\xc0\xb5\xef\xbe\xfb\x86\xb9s\xe7\x86\x17^x\xa1d\x14\xce\xfb\xe1\xde\x1a\xd0\xf3&\x97\xdb\xe6O\x00\xe0\xa0\x13\xcc\x13\x00\xb8\xeb\x04w^\x13w\x18h\x8c\x01\x02\x80\x00 \x00\x08\x80J\t\x80\xee\xc5\x9c\x08hLh\x10\x01\x8dm\x86\xdd\xdc\xd5\xfdO\x02 \xef\xf1\xef\x14\xbf\x04\x00\xee:\xc5\x9e\xd7\xc5\x1e\x06\xca3@\x00\x10\x00\x04\x00\x01PI\x01\xd0\xbd\xa0G\x11p\xf5\xd5W\xbb"\xa0\xa4\x0f \x02\xcao\x88\xdd\xcc\xd5\xf9O\x02 \xcfq\xef4\xb3\x04\x00\xee:\xcd\xa0\xd7\xc7 \x06\xfa\xcf\x00\x01@\x00\x10\x00\x04@\xa5\x05@\xf7\x82\xbe\xdf~\xfb\x85k\xae\xb9\x86\x08h@\x04\x8c\x1e=\xba\x16c\xdc=\xd6\xfe\xec\xff&\xbej\xad\x08\x80\xc6k\xb7j-\xfd\x7f\xffkI\x00\xf4\xbfV\xb8R+\x0c`\xa0\xd3\x0c\x10\x00\x04\x00\x01@\x00\xd4*\x1c\xee\xbf\xff\xfe\xe1\xdak\xaf%\x02J\x88\x80e\xcb\x96\x85\xf3\xce;\xafV\xe3\xdc\xe9\xcd\xb1\xae\xafO\x00h,;\xc1.\x01\x80\xbbNp\xe75q\x87\x81\xc6\x18 \x00\x08\x00\x02\x80\x00\xa8\xd5\x03\xa6\xa3\x00\x00,\xeeIDATe0<\xe0\x80\x03\xc2u\xd7]\x17V\xacXQ"\n\xe7\xfd\xd0\xd9\xb3g\xd7r\xacm\xf0\xfd\xdf\xe0\t\x80\xfe\xd7\nW\xcd\xab\x15\x01\xd0\xbcZ\xe2R-1\x80\x81V3@\x00\x10\x00\x04\x00\x01P\xebP\x18/o\x9f7o\x1e\x11\xd0O\xb71q\xe2\xc4Z\x8fw\xab7\xc5\xba??\x01\xa0q\xec\x04\xc3\x04\x00\xee:\xc1\x9d\xd7\xc4\x1d\x06\x1ac\x80\x00 \x00\x08\x00\x02 \x89@H\x04\xf4\xcf\x00<\xf1\xc4\x13a\xc4\x88\x11I\x8c\xb9\x8d\x7f\xf5\x8d\x9f\x00X\xbd&8i}M\x08\x80\xd6\xd7\x18\xc7j\x8c\x01\x0c4\x8b\x01\x02\x80\x00 \x00\x08\x80\xa4\xc2\xe0\x81\x07\x1e\x18\xe6\xcf\x9f\xef\x8a\x80>|\xc0\x84\t\x13\x92\x1a\xf3fm\x88)<\x0f\x01\xa0A\xec\x04\xc7\x04\x00\xee:\xc1\x9d\xd7\xc4\x1d\x06\x1ac\x80\x00 \x00\x08\x00\x02 \xc90\x18E\xc0\xf5\xd7_O\x04\xf4 \x02\xe6\xce\x9d\x9b\xe4\x98k\x04\xde\x13\x08\x80\xc6\x9a!\xec\x0c\xacn\x04\xc0\xc0\xea\x87?\xf5\xc3\x00\x06\xda\xc9\x00\x01@\x00\x10\x00\x04@\xd2a\xf0\xfd\xef\x7f\x7fX\xb0`\x01\x11\xf02\x11p\xef\xbd\xf7&=\xe6\xed\xdcD\xab\xf6Z\x04\x80&\xb2\x13L\x12\x00\xb8\xeb\x04w^\x13w\x18h\x8c\x01\x02\x80\x00 \x00\x08\x80\xe4\xc3\xe0\xae\xbb\xee\x1a:\xd5\xa0\xbe,wW\xe6?\x7f\xf1\x8b_$?\xe6\xb96\x05\x04@c\xcdP\xae\xbc4\xeb\xbc;\xb5\xbe^p\xc1\x05\xd62=\x0c\x060\x80\x81\x92\x0c\x10\x00\x04\x00\x01Pr\xd24\xaba\xf2<\xado\xd4G\x8e\x1c\x19.\xbb\xec\xb2\xb0d\xc9\x92\xca\x84\xef*\x1c\xc8\x8f~\xf4#\x9be\xa2\xf3\x9e\x00h\xfd\xbab\xed^\xbd\xc6\x04\xc0\xea5\xc1\x89\x9a`\x00\x03Ue\x80\x00 \x00\x08\x80D\x83@U\x17\x9dv\x1c\xd7n\xbb\xed\x16\xbe\xf3\x9d\xef\x84\xa5K\x97V!oW\xee\x18\xa6N\x9dJ\x00$:\xef\t\x00\rg;\xd6\xd8U_\x83\x00\xc0\xdd\xaaL\xf8\x7fL`\xa0\xba\x0c\x10\x00\x04\x00\x01\x90h\x10\xc8q\xe1\xdd}\xf7\xdd\xc3\xe5\x97_.\xf8\xafA9\xc4\x1b$\xe6\xc8G\x0e\xe7L\x00T\xb7\xe1J\x99?\x02\x00w)\xf3\xed\xdc\xf0\x9d\x1a\x03\x04\x00\x01@\x00\x10\x00\xb5\x0f\x83{\xec\xb1G\x98={\xb6\xe0\xbf\x86\xe0\x1f\xff:\xde\x101\xb5\x8d\xcc\xf9\xbc\xd4\x9c\x11\x00/\xd5\x02\x17\xed\xab\x05\x01\xd0\xbeZ\xe3Z\xad1\x80\x81\x812@\x00\x10\x00\x04\x00\x01P\xdb@\xb8\xe7\x9e{\x86+\xae\xb8B\xf0\xefG\xf0\x8f\x0f\xf9\xd3\x9f\xfe\x14v\xdey\xe7\xda\x8e\xf7@7\xbc\x1c~\x9f\x00\xd0\x18v\x82s\x02\x00w\x9d\xe0\xcek\xe2\x0e\x03\x8d1@\x00\x10\x00\x04\x00\x01P\xbb@8j\xd4\xa8\xf0\xbd\xef}/,[\xb6\xac\x9f\xd1\xd7\xc3\x1e}\xf4\xd1p\xc0\x01\x07\xd4n\xacm\xee\xe56w\x02\xa0\\\xbd\xf0\xd5\x9cz\x11\x00\xcd\xa9#\x1e\xd5\x11\x03\x18h\x07\x03\x04\x00\x01@\x00\x10\x00\xb5\t\x85{\xed\xb5W\x983g\x8e\xe0_\xc2g\xbc\xf0\xc2\x0b]\xb2d\x87\x1dv\xa8\xcd8\xb7c\xf3K\xf55\x08\x00\xcdc\'\xd8&\x00p\xd7\t\xee\xbc&\xee0\xd0\x18\x03\x04\x00\x01@\x00\x10\x00\x95\x0f\x86{\xef\xbdw\xf8\xfe\xf7\xbf\x1f\x96/_^"\xfa\xe6\xfd\xd0\x18\xfc\xaf\xbe\xfa\xea\xb0\xef\xbe\xfbV~|m\xe0\x8dm\xe0=\xd5\x8d\x00h^-{\xaa\xaf\x9f\xf5\\_\x02\xa0\xe7\xba\xe0E]0\x80\x81*2@\x00\x10\x00\x04\x00\x01P\xd9\x80\xb8\xcf>\xfb\x84\x1f\xfc\xe0\x07\x82\x7f\t\x97!\xf8\xe7\xddl\x10\x00y\x8f\x7f\xa7\x1aM\x02\x00w\x9db\xcf\xebb\x0f\x03\xe5\x19 \x00\x08\x00\x02\x80\x00\xa8\x9c\x00\x88\xffj}\xd5UW\t\xfe\x82\x7f\xe5\xd8\xacz\xa3A\x00\x94o\x84\xaa>\xa6u8>\x02\x00wu\xe0\xd41\xe2\x14\x03\xff\xc7\x00\x01@\x00\x10\x00\x04@eB\xd6~\xfb\xed\x17\xe6\xce\x9d\x1b\xe2\xbfb\xfb\xea_\x05\xfc\x8b\xbf\x86\xe6\xe5\r\r\x01\x80\x87\x97\xf3\xd0\xdb\x7fo\xb5\xd5V!\xdeL\xf5C\x1f\xfaP8\xea\xa8\xa3\xc2I\'\x9d\x14\xbe\xf4\xa5/\x85\xb3\xcf>;\x9cu\xd6Y\xe1\xcc3\xcf\x0c\x93\'O\x0e\xa7\x9f~z\xf8\xecg?\xdb\xf5\x98\x0f|\xe0\x03a\xe4\xc8\x91a\xd8\xb0a\xab\xed\x19\x04\x00\xeezc\xcd\xcf\xb1\x81\x81\xea1@\x00\x10\x00\x04\x00\x01\xb0Z3\xd7\xee\xc5z\xff\xfd\xf7\x0f\xd7\\s\x8d\xe0\xdf\xbf\xcc\xdf\xf5(\xc1\xbfz\x1bj\xbb\xe7MO\xafG\x00\xe0\xe2\xe5\\\x8c\x181"\x8c\x1d;\xb6+\xcc\xcf\x9e=;\xdc}\xf7\xdd\xe1\xb1\xc7\x1e+\xb1\xd2\xac\xfe\xd0x/\x96\x07\x1f|0\xdcp\xc3\ra\xea\xd4\xa9\xe1\xc4\x13O\xecz\xde\xd5\x1f\xd9\xfa\x9f\\p\xc1\x05\x1d\xdf\xbf^^o\xffm\xfea\x00\x03u`\x80\x00 \x00\x08\x00\x02\xa0c\r\xd4\xe8\xd1\xa3\xc3u\xd7]\'\xf8\x97\xe8\x93\x05\x7f\xcdE_\xcd\x05\x01\x907\x1f\xdbo\xbf}8\xe1\x84\x13\xc2\xcc\x993\xc3/\x7f\xf9\xcb\xe4\xd7V\x02 o\xde\xfbZ\x0b\xfd\x1d60\xd0;\x03\x04\x00\x01@\x00\x10\x00m\x17\x00\x07\x1ex`\x987o^X\xb1bE\x89\xe8\x9b\xf7C\x05\xff\xde72\x9b\xfcK\xb5!\x00^\xaaE.\\\xec\xb1\xc7\x1e\xe1\x8c3\xce\x08?\xfe\xf1\x8f\xb3\xfb\x88T\x02 ?\xdes\x99\xd7\xce\x13\xdb\xadd\x80\x00 \x00\x08\x00\x02\xa0m\x02\xe0\xfd\xef\x7f\x7f\x98?\x7f\xbe\xe0_\xc2e\x08\xfe\x9a\x802M\x00\x01\x90\x07/\xef{\xdf\xfb\xc2\x97\xbf\xfc\xe5\xf0\x8b_\xfc\xa2\xc4j\x92\xdeC\t\x80<x/\xb3\x06z,&0\xb0f\x06\x08\x00\x02\x80\x00 \x00Z.\x00\xfe\xe5_\xfe%,X\xb0@\xf0/\xd1\x7f\x0b\xfek\xde\xc0l\xf2\xab\xd7\x88\x00X\xbd&)q2n\xdc\xb8\xae\xab\xa7\x96.]Zb5I\xf7\xa1\x04@\xda\xbc\xa74w\x9d\x0bV\xab\xc4\x00\x01@\x00\x10\x00\x04@\xcb\x04\xc0\x07?\xf8\xc1\xae\x1bE\xa5\xdb~6\xff\xcc\x04\x7fM\xc2@\x9a\x04\x02 =~\xe2]\xf7\'N\x9c\x18~\xf7\xbb\xdf5\x7f\xc1\xa9\xf93\x12\x00\xe9\xf1>\x90\xf5\xcf\xef\xe2\x01\x03\xfdc\x80\x00 \x00\x08\x00\x02\xa0\xe9\x02`\xcc\x981\xe1\xc6\x1bo\xacyk\xd9\xde\xc3\x17\xfc\xfb\xb7i\xd9\xdc\xfb\xae\x13\x01\xd0w}\xea\xc4\xcf\x96[n\xd9\xf5\xd1|\x8f>\xfah{\x17\xa3\x1a\xbd\x1a\x01\x90\x0e\xefu\x9a\x9b\x8e\x15wug\x80\x00 \x00\x08\x00\x02\xa0i\x02 ~\xa6\xf4\xcd7\xdf\\\xa3\xf6\xb1\xf3\x87*\xf8k$\x9a\xd9H\x10\x00\xf5\xe7i\xf0\xe0\xc1a\xd2\xa4I\x03\xfe\xb8\xbe\xce\xafn\xad?\x02\x02\xa0\xfe\xbc7s\xfd\xf3\\x\xc0@\xff\x18 \x00\x08\x00\x02\x80\x00\x18\xb0\x008\xe8\xa0\x83\xc2-\xb7\xdc\xd2\xfan/\xa1W\x10\xfc\xfb\xb7I\xd9\xcc\xcb\xd5\x89\x00(W\xaf\xaa\xf1u\xd8a\x87\x85\xfb\xef\xbf?\xa1\x95\xae\xb5\xa7B\x00\xd4\x9b\xf7\xaa\xcd?\xc7\x83\xa7\\\x18 \x00\x08\x00\x02\x80\x00hX\x00\x1cr\xc8!\xe1\xd6[omm\x87\x97\xd8\xb3\x0b\xfe\x1a\x8cV6\x18\x04@=\xf9\xday\xe7\x9d\xc3\xdc\xb9s\x13[\xedZ\x7f:\x04@=yo\xe5\x1a\xe8\xb91\x81\x8153@\x00\x10\x00\x04\x00\x01PZ\x00\xfc\xeb\xbf\xfek\xf8\xe1\x0f\x7f\xd8\xfa\xee.\xa1W\x10\xfc\xd7\xbc!\xd9\xb4\x07^#\x02`\xe05l7\x87\x9f\xfc\xe4\'\xc3\x93O>\x99\xd0j\xd7\xbeS!\x00\xea\xc7{\xbb\xe7\x97\xd7\xc3\x08\x06Vg\x80\x00 \x00\x08\x00\x02\xa0\xdf\x02\xe0\xd0C\x0f\r\xb7\xddv[\xfb\xba\xbb\x04^I\xf0_}\xe3\xb1\x19\xb7\xae&\x04@\xebj\xdbln\xb7\xd9f\x9bp\xe5\x95W&\xb0\xcau\xee\x14\x08\x80\xfa\xf0\xde\xec\xf9\xe3\xf9\x8c=\x06\x1ag\x80\x00 \x00\x08\x00\x02`\x8d\x02\xe0\xc3\x1f\xfepX\xb4hQ\xe7\xba\xbc\x1a\xbe\xb2\xe0\xdf\xf8\xc6dSo\xbcv\x04@\xe3\xb5k\'w\xf1\x93R\x1ey\xe4\x91\x1a\xael\xd5:d\x02\xa0\x1e\xbc\xb7sny-L``\xcd\x0c\x10\x00\x04\x00\x01@\x00\xf4*\x00\xc6\x8e\x1d\x1b~\xf2\x93\x9fT\xab\xe3\xab\xf8\xd1\x08\xfek\xdexl\xce\xad\xab\x11\x01\xd0\xba\xda6\x8b\xdb\x13O<1<\xff\xfc\xf3\x15_\xc9\xeaqx\x04@\xf5yo\xd6\xbc\xf1<\xc6\x1a\x03\xcdc\x80\x00 \x00\x08\x00\x02`5\x01\xf0\x91\x8f|$\xdc~\xfb\xed\xf5\xe8\x00+r\x94\x82\x7f\xf36&\x9b|\xe3\xb5$\x00\x1a\xaf];\xb8\x8b\x81\xd5W\xf3*@\x00T\x9b\xf7v\xcc)\xaf\x81\x01\x0c\x94g\x80\x00 \x00\x08\x00\x02\xe0\xaf\x02\xe0\x88#\x8e\x08w\xdcqG\xf3\xba\xb3\x0c\x9eI\xf0/\xbf\xf1\xd8\xac[W3\x02\xa0u\xb5\x1d\x08\xb7\xc3\x86\r\x0b\xf3\xe7\xcf\xcf`El\xef)\x12\x00\xd5\xe4} s\xc5\xef\x1aS\x0c\xb4\x9e\x01\x02\x80\x00 \x00\x08\x800n\xdc\xb8p\xd7]w\xb5\xb7s\xab\xf9\xab\t\xfe\xad\xdf\xa04\x01\xe5kL\x00\x94\xafY\xab9\xdbb\x8b-\xc2\xc2\x85\x0bk\xbe\xe2U\xf3\xf0\t\x80\xea\xf1\xde\xea\xf9\xe4\xf9\x8d9\x06\x06\xce\x00\x01@\x00\x10\x00\x19\x0b\x80\x8f~\xf4\xa3\xe1?\xff\xf3?\xab\xd9\xd9U\xf4\xa8\x04\xff\x81o<6\xef\xd6\xd5\x90\x00h]m\x1b\xe1v\xab\xad\xb6\xf2\x91\xa9-\\\xcb\t\x80j\xf1\xde\xc8\x1c\xf1;\xc6\x10\x03\xedg\x80\x00 \x00\x08\x80\x0c\x05\xc0QG\x1d\x15\xee\xb9\xe7\x9e\x16\xb6e\xe9=\xb5\xe0\xdf\xfe\rJSP\xbe\xe6\x04@\xf9\x9a\xb5\x8a\xb3x\xd9\xbfOOi\xed^@\x00T\x87\xf7V\xcd#\xcfk\x8c1\xd0|\x06\x08\x00\x02\x80\x00\xc8H\x00\x1c}\xf4\xd1\xe1\xa7?\xfdik;\xb2\xc4\x9e]\xf0o\xfe\xc6c3o]M\t\x80\xd6\xd5\xb6,\xb7W^yeb\xaba\xf5N\x87\x00\xa8\x0e\xefe\xe7\x87\xc7\x1b;\x0ct\x8e\x01\x02\x80\x00 \x002\x10\x00\x1f\xfb\xd8\xc7\xc2\xcf~\xf6\xb3\xeauo\x15>"\xc1\xbfs\x1b\x93\xa6\xa0\xf1\xda\x13\x00\x8d\xd7\xae\x99\xdc\xfd\xfb\xbf\xff{\x85W\xb7t\x0e\x8d\x00\xa8\x06\xef\xcd\x9c;\x9e\xcb\x98b\xa0\xf5\x0c\x10\x00\x04\x00\x01\x90\xb0\x008\xf6\xd8c\xc3}\xf7\xdd\x97N\xb7\xd7\x863\x11\xfc[\xbf\xf1\xd8\xdc[Wc\x02\xa0u\xb5\xed/\xb7\'\x9exb\x1bV*/\x11+@\x00t\x9e\xf7\xfe\xce\x0b\x8f3V\x18\xa8\x0e\x03\x04\x00\x01@\x00$(\x00>\xfe\xf1\x8f\x87\x9f\xff\xfc\xe7:\xc4\x12\x15\x10\xfc\xab\xb31i\x12\x1a\x1f\x0b\x02\xa0\xf1\xda5\x83\xbb\xbd\xf6\xda+,^\xbc\xb8\xc4\xca\xe3\xa1\x03\xa9\x00\x01\xd0Y\xde\x9b1g<\x871\xc4@\xfb\x19 \x00\x08\x00\x02 !\x010v\xecX\xc1\xbfd7)\xf8\xb7\x7f\xe3\xb1\xd9\xb7\xae\xe6\x04@\xebj\xbb&n\x87\x0e\x1d\xea\xadV%\xd7\xdf\x81>\x9c\x00\xe8\x1c\xefk\x9a\x0f\xfe\xde\xd8`\xa0\xba\x0c\x10\x00\x04\x00\x01\x90\x88\x00\x982eJX\xb1b\xc5@\xfb\xa9l~_\xf0\xaf\xee\xc6\xa4ih|l\x08\x80\xc6k7P\xeeb\x18\xf5\xd5\xde\n\x10\x00\x9d\xe3}\xa0\xf3\xc5\xef\x1b;\x0ct\x8e\x01\x02\x80\x00 \x00\x12\x10\x00\xe7\x9csN{\xbb\xae\x1a\xbf\x9a\xe0\xdf\xb9\r\xc7f\xdf\xfa\xda\x13\x00\xad\xafqO\x1c\x7f\xe8C\x1f\nqm\xf1\xd5\xde\n\x10\x00\x9d\xe1\xbd\xa79\xe0g\xc6\x02\x03\xf5a\x80\x00 \x00\x08\x80\x9a\x0b\x801c\xc6\x84\xe5\xcb\x97\xb7\xb7\xeb\xaa\xe1\xab\t\xfe\xf5\xd9\x984\x11\x8d\x8f\x15\x01\xd0x\xed\x1a\xe5n\xf0\xe0\xc1n\xb6\xda\xa1=\x81\x00h?\xef\x8d\xce\x13\xbfg\xac0P\x1d\x06\x08\x00\x02\x80\x00\xa8\xb9\x00\xb8\xe5\x96[:\xd4z\xd5\xe3e\x05\xff\xeal86\xff\xd6\x8f\x05\x01\xd0\xfa\x1a\xaf\xca\xf1\x84\t\x13\xea\xb1\x18&x\x94\xd3\xa7O\x0f\xab\x8e\x87\xffo\xff\x1cPs5\xc7@\xbd\x18 \x00\x08\x00\x02\xa0\xc6\x02`\xc7\x1dwt\xd9i/M\xad\xe0_\xaf\xcdH\xf3\xd0\x9c\xf1"\x00\x9aS\xc7\xfe\xf2\xb8\xc5\x16[\x84G\x1f}\xb4\x97U\xc8\x8f[]\x81+\xae\xb8\x82\x00\xa8q\x0f\xd3\xdfy\xe6q\xed]\xd7\xd4;\xfdz\x13\x00\x04\x00\x01P\xe3\xcd3~\xdc\x9f\xaf\x95+\xd0\x1d\xfc\xf7\xdbo?\x8da\x8d\xd9\xd6\x804\xd6\x80\x10\x00\x8d\xd5\xadQ\xde\xbe\xf8\xc5/\xae\xbc\x00\xf9\xbf\xb6V`\xfe\xfc\xf9\xd6y\xeb<\x060\x80\x81\x92\x0c\x10\x00\x04\x00\x01Pr\xd24\xda(\xb6\xe2\xf7N;\xed\xb4\xb66[U~1\xc1\xbf\xbd\xc1\xa7\x15<{\xce\x81\x8f!\x010\xf0\x1a\xf6\x97\xc3\xf8\xde\xff\x87\x1ez\xa8\xca\xcbb\xf2\xc7\xf6\xc3\x1f\xfeP\xe3_\xe3\x1e\xa6\xbfs\xcd\xe3\xda\xb7\xae\xa9u\x1e\xb5&\x00\x08\x00\x02\xa0\xc6\x9b\xe7Yg\x9d\x95|\x83\xb7\xa6\x13\x14\xfc\xf3\xd8\xacRmJb\x88\xdcu\xd7]\xc3\x81\x07\x1e\x18\x0e?\xfc\xf0\xf0\xc9O~2L\x9c81\xc4\xb9\x1d\xbf\xcf<\xf3\xcc0y\xf2\xe4p\xfa\xe9\xa7\x87SN9%\x1c{\xec\xb1\xe1\x90C\x0e\t\xa3F\x8d\n#F\x8cX-\xfc\x10\x00\xed\x9b\x0fq,|u\xb6\x02\xb7\xdf~\xfbjs \xd5\xb5\xc2y\xb5on\xab\xb5Z\xa7\xce\x00\x01@\x00\x10\x00\x04@g;\xb8\x06_]\xf0\xb7A\xd7i\x83\x1e2dH\x18=zt\x887\x8c\xbb\xf0\xc2\x0b\xc3\xad\xb7\xde\x1a~\xf7\xbb\xdf\x85\xa5K\x9768\x03\xfe\xef\xd7\x1e{\xec\xb1\xb0h\xd1\xa2\xf0\xedo\x7f;L\x9a4)L\x9b6m@\xcf\xd7\xe8/?\xf0\xc0\x03\xd9\x05\xb1\x85\x0b\x176Z.\xbf\xd7\xa4\n\xdcu\xd7]\xd9qW\xa7u\xcf\xb1\xda\xa71PM\x06\x08\x00\x02\x80\x00 \x00\x9a\xd4\x8a\xb5\xe7i\x04\xffjn&6\xf9\x95\xc7e\xe8\xd0\xa1a\xec\xd8\xb1\xe1\x1b\xdf\xf8F\xf8\xc9O~\x12\x16/^\xdc\x9e\t\xd2\xa1W\xc9M\x00\xec\xb0\xc3\x0ea\xd9\xb2e\x1d\xaa\xb6\x97\xed\xae@\x9c_\xd6\x9e\x95\xd7\x1e\xf5P\x0f\x0c``M\x0c\x10\x00\x04\x00\x01@\x00t\xf7R\x95\xfeS\xf0\xb7\xa1\xadiC\xeb\xf4\xdf\xc7K\xf2O8\xe1\x84p\xd5UW\x85\xa7\x9ez\xaa\xd2\xf3\xa9\xd9\x07\x97\x9b\x008\xf5\xd4S\x9b]B\xcf\xd7@\x05\xbe\xf2\x95\xaf\x10\x005\xeea:\xbdf{}}E\xae\x0c\x10\x00\x04\x00\x01P\xe3\xcd3\x87{\x00\x08\xfe6\xe8*o\xd0\xc3\x86\r\x0b\xc7\x1f\x7f|\x88\x97\x83/Y\xb2\xa4\x81\x08\x93\xc6\xaf\xe4&\x00\x16,X\x90\xc6\xc0\xd5\xfc,\xe2[j\xaa\xbc>86\xfb\x17\x060PE\x06\x08\x00\x02\x80\x00 \x00*\xd9\x02v\x07\xff}\xf7\xddW\x83WcF\xab\xb8\xf15\xe3\x98\xf6\xdak\xaf0}\xfa\xf4\xf0\xf8\xe3\x8fWr\xfe\xb4\xfb\xa0r\x12\x00\xf1\xc6\x8dO?\xfdt\xbbK\xec\xf5z\xa8\xc0G?\xfaQ\xfb\x83\xfd\x01\x03\x18\xc0@I\x06\x08\x00\x02\x80\x00(9i\x9a\x11\x1e\x9a\xf5\x1c)^\x01\xd0\x1d\xfc\xf7\xdbo?\x0bz\x8d\xd9l\x16\xe3U{\x9e\xc3\x0e;,\xc4\x8f\x1e\xf3\xb5r\x05r\x12\x00\xf1f\x8e\xbe\xaaQ\x81=\xf6\xd8\xc3>a\x9f\xc0\x00\x060P\x92\x01\x02\x80\x00 \x00JN\x9a*\x05\x92\x94\x04\x80\xe0\xef2\xb9*\xcd\xadU\x8f\xe5\xc8#\x8f\x0cw\xdcqG5RO\x05\x8f"\'\x01\xf0\xb9\xcf}\xae\x82#\x90\xdf!\xc5\x1bk\xae:O\xfd\xbf}\x04\x03\x18\xc0\xc0\x9a\x19 \x00\x08\x00\x02\x80\x00\xe8h\xe7(\xf8\xafy\xa1\xb6\x99u\xaeF\x07\x1f|p\xb8\xe7\x9e{::G\xea\xf0\xe29\t\x80\xf8\xd6\x0f_\x9d\xaf\xc0\xed\xb7\xdfN\x00\xd4\xb8\x7f\xb1\xafun_S{\xb5\'\x00\x08\x00\x02\xa0\xc6\x1bh\x9d\xaf\x00\x10\xfcm@UnBv\xdai\xa7\xf0\xfd\xef\x7f?\xacX\xb1\xa2\xf3I\xa7\x06G\x90\x93\x00\xb8\xf6\xdakk0"\xe9\x1f\xe2\xf9\xe7\x9fO\x00\xd4\xb8\x7f\xa9\xf2\xfa\xef\xd8\xf4\'\xa93@\x00\x10\x00\x04@\x8d7\xd0:\n\x00\xc1\xdf\xc6Z\xe5\x8du\xc8\x90!\xe1\xec\xb3\xcf\x0e\xcf>\xfbl\xfa\t\xaa\x89g\x98\x93\x00\xb8\xf3\xce;\x9bX9O\xd5h\x05\x0e=\xf4P\x02\xa0\xc6\xfdK\x95\xf7\x01\xc7\xa6OI\x9d\x01\x02\x80\x00 \x00j\xbc\x81\xd6I\x00\x08\xfe6\xd4\xaao\xa8\xf1\xce\xfe.\xf7o,\x8e\xe5$\x00~\xf9\xcb_6V$\xbf\xd5\xb4\n<\xf1\xc4\x13!\xca\xba\xaa\xaf)\x8e\xcf\xbe\x87\x01\x0cT\x91\x01\x02\x80\x00 \x00\x08\x80\xa65e==\x91\xe0o\xf3\xab\xe2\xe6\xb7\xea1M\x9a4)\xc4\x9b\x8a\xf9j\xac\x029\t\x80\x07\x1f|\xb0\xb1"\xf9\xad\xa6U\xe0;\xdf\xf9\x8e\xf0_\xe3\xdee\xd5\xf5\xd7\xff\xeb\x130\xd0^\x06\x08\x00\x02\x80\x00\xa8\xf1&Z\xe5+\x00\x04\xff\xf6.\xe66\xcf\xc6\xea\xbd\xed\xb6\xdb\x86\x1bo\xbc\xb1i\xc1$\xd7\'\xcaI\x00\xc4s\xf5\xd5\xd9\n\xc4\x9bsZ\xf3\x1a[\xf3\xd4M\xdd0\x80\x01\x02\x80\x00 \x00\x08\x80\xa6vr\x82\xbf\x8d\xa5.\xcd\xc5\xde{\xef\x1d\x84\xb9\xe6L\xff\x9c\x04\xc0\x7f\xfd\xd7\x7f5\xa7h\x9e\xa5\xa1\n\xdc\x7f\xff\xfd\xc2\x7f\x8d\xfb\x96\xba\xec\x0f\x8eS/\x932\x03\x04\x00\x01@\x00\xd4x#\xad\xd2\x15\x00\x82\xbf\xcd\xb2N\x9b\xe5\xb8q\xe3\xc2SO=\xd5P\x00\xf1K\xabW \'\x01p\xdbm\xb7\xad^\x00?i[\x05>\xfb\xd9\xcf\x12\x005\xee[\xea\xb4O8V}M\xaa\x0c\x10\x00\x04\x00\x01P\xe3\x8d\xb4\n\x02@\xf0\xb7A\xd6m\x83\xfc\xfc\xe7?\x1f\x96/_\xde\xb6\xc0\x92\xc3\x0b\xe5$\x00\xe2\xc7C\xfa\xeaL\x05\x1e~\xf8\xe10t\xe8P\x02\xa0\xc6}K\xdd\xf6\x0b\xc7\xab\xc7I\x91\x01\x02\x80\x00 \x00j\xbc\x91vR\x00\x08\xfe6\xc5:n\x8a_\xfc\xe2\x17\xc3\x8a\x15+:\x93^\x12~\xd5\x9c\x04\xc0\xd7\xbe\xf6\xb5\x84G\xb2\xda\xa7v\xf2\xc9\'\x0b\xff5\xeeY\xea\xb8g8f\xbdN\x8a\x0c\x10\x00\x04\x00\x01P\xe3\xcd\xb4\x13\x02@\xf0\xb7\x19\xd6u3<\xe3\x8c3\xaa\x9dnj|t9\t\x80c\x8f=\xb6\xc6#U\xdfC\x8f\x1f\xbf8x\xf0`\x02\xa0\xc6=K]\xf7\x0e\xc7\xad\xefI\x8d\x01\x02\x80\x00 \x00j\xbc\x99\xb6S\x00\x08\xfe6\xc0:o\x80\xc2\x7fk\x83_N\x02\xe0}\xef{_k\x8b\xe9\xd9{\xac\xc0\x87?\xfca\xe1\xbf\xc6\xfdJ\x9d\xf7\x0f\xc7\xae\xffI\x8d\x01\x02\x80\x00 \x00j\xbc\xa1\xc6\xcf.o\xf5\x97\xe0o\xe3\xab\xfb\xc6w\xdcq\xc7\x85\xc8\xb1\xaf\xd6U \'\x01\x10\xe7\xc3\xaf\x7f\xfd\xeb\xd6\x15\xd33\xafV\x81\xef}\xef{\xc2\x7f\x8d{\x95\xba\xef!\x8e_\x1f\x94\x1a\x03\x04\x00\x01@\x00\xd4xS\xfd\xe8G?\xbaZ\xa3\xd4\xac\x1f\x08\xfe6\xbc\x146\xbc1c\xc6\x84\xe7\x9e{\xaeY\xd3\xc2\xf3\xf4R\x81\xdc\x04\xc0\xf4\xe9\xd3{\xa9\x84\x1f7\xbb\x02\xbf\xfb\xdd\xef\xc2\xd6[oM\x00\xd4\xb8WIa/q\x0ez\xa2\x94\x18 \x00\x08\x00\x02\xa0\xc6\x9bjl\x8a\x9e\x7f\xfe\xf9\xa6\xf6[\x82\xbfM.\x95Mn\xe4\xc8\x91\xe1\xb1\xc7\x1ek\xea\xfc\xf0d=W 7\x01\x10\xc5\x92\xaf\xd6W`\xd9\xb2e\xe1\xe0\x83\x0f\x16\xfek\xdc\xa7\xa4\xb2\x9f8\x0f\xbdQJ\x0c\x10\x00\x04\x00\x01P\xf3\x8du\xce\x9c9M\xe9\xc2\x04\x7f\x9b[J\x9b[\xbcY\xd8\x9dw\xde\xd9\x94\xb9\xe1I\xd6\\\x81\xdc\x04@\x9c+\xff\xfd\xdf\xff\xbd\xe6\xc2x\xc4\x80*0e\xca\x14\xe1\xbf\xe6=JJ\xfb\x8as\xd1\'\xa5\xc2\x00\x01@\x00\x10\x005\xdf\\w\xdbm\xb7\xf0\xec\xb3\xcf6\xdcd\t\xfe6\xb4T6\xb4\x97\x9f\xc7\xb9\xe7\x9e\xdb\xf0\x9c\xf0\x8b\xe5+\x90\xa3\x00\x88\x1f)\xe9\xabu\x15X\xb0`\x81\xbb\xfe\xd7\xbc?y\xf9\x9a\xec\xbf\xf5\x1a\x18\xa8\x0e\x03\x04\x00\x01@\x00$\xb0\xc1\xc6\x9b\x9c-_\xbe\xbcT\'&\xf8Wg!\xb6)6w,\x0e:\xe8\xa0\x10/\x1d\xf6\xd5\xbe\n\xe4(\x00\xb6\xdaj\xab\xf0\xe7?\xff\xb9}E\xce\xe8\x95~\xf6\xb3\x9f\x85X_kcs\xd7F\xf5TO\x0c` 2@\x00\x10\x00\x04@\x02\x02 N\xe6\x8f}\xecc\xe1\xa9\xa7\x9eZc\x8b(\xf8[\xfcSn\x00\x86\r\x1b\x16b\x18\xf5\xd5\xde\n\xe4(\x00\xe2<\x9a<yr{\x0b\x9d\xc1\xab=\xf2\xc8#a\xc7\x1dw\x14\xfe\x13\xe9MR\xdeo\x9c\x9b~\xaa\xae\x0c\x10\x00\x04\x00\x01\x90\xd0&\xbb\xd3N;\x85\x993g\x86g\x9eyf\xb56\xf1/\x7f\xf9K\x88\xf7\x0b\xd8o\xbf\xfd4V\t\x8dy]7\x9fV\x1d\xf7\xd7\xbf\xfe\xf5\xd5\xd8\xf7\x83\xd6W \xde\xa9\xbdUcZ\xe5\xe7\x8d\xc2\xe9\xb7\xbf\xfdm\xeb\x0b\x9c\xc9+\xfc\xe9O\x7f\n\xfb\xee\xbbo\x96,U\x99s\xc7&\xe8b -\x06\x08\x00\x02\x80\x00H0\x0c\xc6\xa6\xf4\x90C\x0e\t\x9f\xf9\xccg\xc2I\'\x9d\x14>\xfc\xe1\x0f\x87-\xb7\xdcRS\x95\xe0X\xdb\x94_\xda\x94\xa3\x00[\xbcxq&Q\xa9Z\xa7\xf9\xc7?\xfe1\xdb\xf5e\xdc\xb8q\xd5\x1a\x8c\x9a\x1e\xcd\xa3\x8f>\x1a\xf6\xde{\xefl9\xb2\x96\xbf\xb4\x96\xab\x85Z`\xa0\xb5\x0c\x10\x00\x04\x00\x01 \x14j\xb80\x90\x04\x03\xb3f\xcd\xaai\xf4\xa9\xffa?\xf9\xe4\x93I0\xd4h\xd39{\xf6\xec\xfa\x0fb\x07\xcf ^\xf6\xbf\xe7\x9e{f\xcdP\xa3\xec\xf9\xbd\xd6\x06%\xf5U\xdf\x14\x19 \x00\x08\x00\x02@\xf8\xd3ta\xa0\xf6\x0c\xec\xba\xeb\xaea\xe9\xd2\xa5\x1d\x8c0y\xbft|\xdbQ\x8aMR\x7f\xcfi\xf8\xf0\xe1>\x16\xb0\xc1)\xf0\xcb_\xfe2\x8c\x1c92k~\xfa\xcb\x99\xc7\t\xa3\x18\xc0@3\x18 \x00\x08\x00\x02@\xf8\xd3xa\xa0\xf6\x0c\xcc\x981\xa3\xc1\xf8\xe1\xd7\x9aQ\x81\xe7\x9e{\xae\xf6\x0c\r\xb4\xa9\xda}\xf7\xdd\xc3\xe3\x8f?\xde\x8crf\xf3\x1c7\xdcpC\x181bD\xf6\xec\x0c\x94=\xbf/\x14b\x00\x03e\x18 \x00\x08\x00\x02@\xf8\xd3|a\xa0\xd6\x0c\xc4\x7f}\xed\xcf\'`d\x93\xaa:p\xa2\xcf?\xff|\xad\x19*\xd38\xf5\xf5\xd81c\xc6\x84(C|\xad\xb9\x02S\xa7N\xc5\x8c\xbd\x07\x03\x18\xc0@\x07\x18 \x00\x08\x00\x02\xa0\x03\x13\xaf\xaf\x06\xd2\xdf\xb1\xb8\x18(\xc7\xc0\x84\t\x13\xd6\x9c6<\xa2\xa5\x15X\xb2d\x89&\xee\xc5\xbd\xe4\x98c\x8e\xf1v\x94>h{\xe2\x89\'\xc2q\xc7\x1d\x87\x17\xbd\x07\x060\x80\x81\x0e1@\x00\x10\x00\x04@\x87&\x9f\x90W.\xe4\xa9\x97z\xf5\xc6\xc0\xa2E\x8b\xfa\x88\x1b\xfe\xaa\x1d\x15\x88\xef\xe3\xeem|r\xfc\xf9\x11G\x1c\x11\x9e}\xf6\xd9v\x94\xbeV\xafq\xcb-\xb7\x84\xf8i\x1d92\xe1\x9c\xeda\x18\xc0@U\x18 \x00\x08\x00\x02\x80\x00\xd0\x8ca\xa0\xb6\x0c\xec\xb0\xc3\x0e\xe1\x85\x17^\xa8U\x08J\xf1`\x7f\xfa\xd3\x9f\xd6\x96\xa1V5d\x1f\xfc\xe0\x07\xdd\x13\xe0E\xd8\xe3\xc7s~\xf1\x8b_\xc4\x88\xbd\x06\x03\x18\xc0@\x05\x18 \x00\x08\x00\x02\xa0\x02\x13\xb1U\r\xa8\xe7e\x9bSg\xe0\xa4\x93NJ1O\xd7\xee\x9c\x16.\\\xa8\xa9\xeba/\x89\x1fmw\xef\xbd\xf7\xd6n<\x9by\xc0\xd7^{\xad\xbb\xfc\xf7\xc0F\xeak\xb3\xf3\xd3\x7f`\xa0\xba\x0c\x10\x00\x04\x00\x01`c\xd6\xb8c\xa0\xb6\x0c\xcc\x993\xa7\x99Y\xc5s5X\x81\xd9\xb3g\xd7\x96\xa1V7\xa9\xc3\x86\r\x0b\xd3\xa7O\x0f+V\xach\xb0\xba\xf5\xfc\xb5\xf8\xb6\x90\xb1c\xc7\xe2\xc2\xfe\x82\x01\x0c`\xa0b\x0c\x10\x00\x04\x00\x01P\xb1I\xd9\xeaf\xd4\xf3W\xd7\xc8\x1a\x9b\xf2c\xf3\x9b\xdf\xfc\xa6\x9e\xe9(\xb1\xa3\xfe\xf2\x97\xbf\xac\xc1[\xc3^2n\xdc\xb8\xf0\xd0C\x0f%6\xf2\xab\x9fN<\xc7SO=5\x0c\x192\x04\x13k`\xc2\x9a_~\xcdW35\xc3\xc0\xc0\x19 \x00\x08\x00\x02\xc0\x06\xadI\xc3@-\x19\x88\x9f\x1f\x9e\xdb\xbf\xaa\xae\x1e\xb7\xaa\xf1\x93\xa3\x8f>\xba\x96\x0c\xb5\xbb\x91\xdcb\x8b-\xc2\xd7\xbf\xfe\xf5\x10\xdf\x13\x9f\xda\xd7\x03\x0f<\x10N>\xf9\xe40t\xe8P,\xd8S0\x80\x01\x0cT\x98\x01\x02\x80\x00 \x00*<A\xdb\xdd\x9cz\xbd\x81[U5l_\r\xe3g\xae\xfb\xaaF\x05\xde\xfb\xde\xf7j\xf6J\xec%\xbb\xec\xb2K\x88o_Y\xbati5\x06p\x00Gq\xd7]w\x85O\x7f\xfa\xd3a\xf0\xe0\xc1\x18(\xc1\x80\xbd\xa2}{\x85Z\xab5\x06Vf\x80\x00 \x00\x08\x00\x1b\xb6\xa6\r\x03\xb5d\xe0S\x9f\xfa\xd4\x00b\x8b_mV\x05\xfe\xe7\x7f\xfe\xa7\x96\xfcT\xa1!\x1c9rd\xb8\xe8\xa2\x8b\xc23\xcf<\xd3\xac\xe1h\xcb\xf3<\xf1\xc4\x13a\xc6\x8c\x19a\xdf}\xf75\xf6\xf6\x0f\x0c`\x00\x035c\x80\x00 \x00\x08\x80\x9aM\xda*4\xad\x8eae\x93\xaa\x1e\x9d\xa9\xc7\x19g\x9c\xd1\x96\xb0\xe3E\xfa\xae\xc0\xc5\x17_\xac\xf9\x1b\xe0>\xb2\xed\xb6\xdb\x86\xc9\x93\'\x87\x9f\xfd\xecg}\x17\xbb\x83\x7f\xfb\xd4SO\x85\xb9s\xe7\x86O~\xf2\x93!\xde\xd8\xd0\xba\xd7\x99uO\xdd\xd5\x1d\x03\x18\x18(\x03\x04\x00\x01@\x00\x0c\xb0q\x1b\xe8$\xf4\xfb\x16r\x0c4\xc6\xc0\x94)S:\x18\x87\xbctw\x05\x8e8\xe2\x08a\xb0\x89\xfb\xc8^{\xed\x15"\xdb\xf1.\xfa\x9d\xfe\x8a7\xf4\xbb\xf4\xd2K\xc3\x91G\x1e\xe9\xbd\xfdM\x1cck~ck\xbe\xba\xa9\x1b\x06\x9a\xc3\x00\x01@\x00\x10\x006u\xcd;\x06j\xc9\xc0\xb7\xbe\xf5\xadN\xe7\xa3\xec_\xffO\x7f\xfa\x93\xf7~\xb7p\xfd\xd8q\xc7\x1dC|\xab\xcb\xacY\xb3B\xbc\xc9^+oz\x19\xefG\xf0\xf3\x9f\xff\xbc\xeb\xb5>\xf7\xb9\xcf\x85\xddv\xdb\xad\x96\xeb\x82\x80\xd0\x9c\x80\xa0\x8e\xea\x88\x81t\x19 \x00\x08\x00\x02\xa0\x85\xcd\x9b\xc53\xdd\xc5\xd3\xd8v~l\xa7N\x9d\x9a}\x00\xeft\x01.\xbc\xf0B!\xb1\x8d{\xc8\xf0\xe1\xc3C\xbc\xf9e\x0c\xe8\xd3\xa7O\x0f\xd7_\x7f}\x887\xe1\x8b\x1f\x87\x19/\xd1\xef\xeb+~\xf2\xc0\xef\x7f\xff\xfb\xf0_\xff\xf5_a\xd1\xa2E\xe1{\xdf\xfb^\xd7\x95\x06\xf1\x06~\xa3G\x8fvY\x7f\x1b\xc7\xd1\xfe\xd1\xf9\xfd\xc3\x18\x18\x83\x9c\x19 \x00\x08\x00\x02\xc0\xa6\xaf\x81\xc7@-\x198\xf7\xdcs\xfb\xca;\xfe\xae\xc5\x15\x88\xff\x1a=j\xd4\xa8Z\xb2\x93j\xe3\x17\xdf\x9b\xbf\xcd6\xdb\x84\xed\xb6\xdb.l\xbf\xfd\xf6!~:C\xfc\xf6\x9e}a\'U\xe6\x9d\x17\xb61P\x9e\x01\x02\x80\x00 \x00\x84?\r<\x06j\xc9\xc0\xa4I\x93Z\x1cq=}_\x15\xb8\xf1\xc6\x1bk\xc9\x8df\xb1|\xb3\xa8fj\x86\x01\x0c` \x1d\x06\x08\x00\x02\x80\x00\x10\xfe4\xf1\x18\xa8%\x03\xc7\x1e{l_\xf9\xd4\xdf\xb5\xb8\x02\x07\x1f|p-\xb9\xd1\xc4\xa6\xd3\xc4\x1aKc\x89\x01\x0c`\xa0<\x03\x04\x00\x01@\x00\x08\x7f\x9ax\x0c\xd4\x92\x81}\xf6\xd9\xa7\xc5\x11\xd7\xd3\xf7V\x81\x9bo\xbe\xb9\x96\xcch\x14\xcb7\x8aj\xa6f\x18\xc0\x00\x06\xd2b\x80\x00 \x00\x08\x00\xe1O#\x8f\x81Z20d\xc8\x90\xf0\x97\xbf\xfc\xa5\xb7\x8c\xea\xe7-\xaa\xc0\xf2\xe5\xcb\xc3\x01\x07\x1cPKf4\xb1i5\xb1\xc6\xd3xb\x00\x03\x18(\xcf\x00\x01@\x00\x10\x00\xc2\x9fF\x1e\x03\xb5e\xe0\x8e;\xeehQ\xcc\xf5\xb4\xbdU\xe0\x92K.\xa9-/\x1a\xc5\xf2\x8d\xa2\x9a\xa9\x19\x060\x80\x81\xb4\x18 \x00\x08\x00\x02@\xf8\xd3\xccc\xa0\xb6\x0c\x9c\x7f\xfe\xf9\xbd\xe5T?oA\x05\x1e}\xf4\xd1\xae\xbb\xcck\x06\xd3j\x06\x8d\xa7\xf1\xc4\x00\x060\x90\x0f\x03\x04\x00\x01@\x00\x08\x7f\xb5\r\x7f6\xab|6\xab\xde\xc6\xfaC\x1f\xfaP\x0bb\xae\xa7\xec\xa9\x02/\xbc\xf0B\x18;v\xac\xf5\xc2\x9e\x81\x01\x0c`\x00\x03\x18\xa81\x03\x04\x00\x01@\x00\xd4x\x02\xf7\x16\x8a\xfc\\0\xce\x89\x81G\x1ey\xa4\xa7\xbc\xeagM\xae\xc0\xb4i\xd34|\xf6\x0b\x0c`\x00\x03\x18\xc0@\xcd\x19 \x00\x08\x00\x02\xa0\xe6\x938\xa7\xa0\xe7\\\x89\x8d\x9e\x18\xf8\xe67\xbf\xd9\xe4\xa8\xeb\xe9V\xad\xc0}\xf7\xdd\x17\x86\r\x1b\xa6\xe9\xb3_`\x00\x03\x18\xc0\x00\x06j\xce\x00\x01@\x00\x10\x005\x9f\xc4=\x05"?\x13\x94sb`\xe4\xc8\x91a\xe9\xd2\xa5\xabfV\xff\xdf\xa4\n\xfc\xe9O\x7f\n\xb1\xc691\xe5\\\xad\xa1\x18\xc0\x00\x060\x90*\x03\x04\x00\x01@\x00\x10\x00\x1a{\x0c\xd4\x9e\x81\xef\x7f\xff\xfbM\x8a\xbb\x9e\xe6\xe5\x15X\xbcxq\x183fL\xed\xf9H\xb5\x89s^\x02\n\x060\x80\x01\x0c\x94e\x80\x00 \x00\x08\x00\xe1Os\x8f\x81\xda30j\xd4(W\x01\xbc<\xb97\xe1\xbf\xe3M\xff>\xfe\xf1\x8f\xd7\x9e\x8d\xb2\x8d\x91\xc7k\xa61\x80\x01\x0c` e\x06\x08\x00\x02\x80\x00\x10\xfe4\xf8\x18H\x82\x81\x8b/\xbe\xb8\t\xb1\xd7S\xc4\n\xc4\xf0\x7f\xca)\xa7$\xc1E\xcaM\x9cs\x13R0\x80\x01\x0c`\xa0,\x03\x04\x00\x01@\x00\x08\x7f\x9a|\x0c$\xc1\xc0\xd6[o\x1d\xe2\xe7\xd4\xfb\x1aX\x05b\xf8?\xf9\xe4\x93\x93`\xa2lS\xe4\xf1\x1ai\x0c`\x00\x03\x18H\x9d\x01\x02\x80\x00 \x00\x84?\x8d>\x06\x92a\xe0\xa8\xa3\x8e\n+V\xac\x18X\x02\xce\xf8\xb7\x97/_\x1eN<\xf1\xc4dxH\xbd\x89s~\x82\n\x060\x80\x01\x0c\x94e\x80\x00 \x00\x08\x00\xe1O\xb3\x8f\x81\xa4\x18\xb8\xe8\xa2\x8b2\x8e\xf0\x8d\x9fz\xbc\xe1\xdfq\xc7\x1d\x97\x14\x0be\x9b"\x8f\xd7Hc\x00\x03\x18\xc0@\xea\x0c\x10\x00\x04\x00\x01 \xfci\xf81\x90\x14\x03C\x86\x0c\t7\xddtS\xe3I8\xc3\xdf|\xe4\x91G\xc2\xfb\xdf\xff\xfe\xa48H\xbd\x81s~B\n\x060\x80\x01\x0c4\xc2\x00\x01@\x00\x10\x00\xc2\x9f\xa6\x1f\x03\xc910b\xc4\x88p\xdf}\xf7e\x18\xe5\xcb\x9f\xf2]w\xdd\x15\xde\xf7\xbe\xf7%\xc7@#M\x91\xdf\xd1Lc\x00\x03\x18\xc0@\xea\x0c\x10\x00\x04\x00\x01 \xfci\xfc1\x90$\x03;\xed\xb4Sx\xf0\xc1\x07\xcb\'\xe2L~#\xde+!\xbe]b\xd8\xb0aI\x8e\x7f\xea\r\x9c\xf3\x13R0\x80\x01\x0c`\xa0\x11\x06\x08\x00\x02\x80\x00\x10\xfe4\xff\x18H\x96\x81\xf8/\xdb?\xff\xf9\xcf3\x89\xf4\xfd?\xcd\xdf\xff\xfe\xf7\xe1\xf0\xc3\x0fOv\xdc\x1bi\x88\xfc\x8eF\x1a\x03\x18\xc0\x00\x06r`\x80\x00 \x00\x08\x00\xe1O\x08\xc0@\xd2\x0c\xc4\x8f\x07\\\xb4hQ\xff\xd3q\xe2\x8f\xbc\xea\xaa\xab\xc2\xb6\xdbn\x9b\xf4\x98\xe7\xd0\xc09GA\x05\x03\x18\xc0\x00\x06\x1aa\x80\x00 \x00\x08\x00\xe1O\x10\xc0@\xf2\x0c\xc4\xcb\xdcg\xce\x9c\x99x\xb4\xef\xfb\xf4~\xfd\xeb_\x87q\xe3\xc6%?\xd6\x8d4C~G\x13\x8d\x01\x0c`\x00\x03\xb90@\x00\x10\x00\x04\x80\xf0\'\x10` \x1b\x06\x8e=\xf6\xd8\xf0\xe4\x93O\xf6\x9d\x94\x13\xfb\xdb\xa7\x9f~:\x9cy\xe6\x99a\xe8\xd0\xa1\xd9\x8cs.M\x9c\xf3\x14X0\x80\x01\x0c`\xa0,\x03\x04\x00\x01@\x00\x08\x7fB\x01\x06\xb2b`\x97]v\t\xb7\xdcrKb1\x7f\xf5\xd3y\xee\xb9\xe7\xc2\xc5\x17_\xec\x0e\xff\xe6wV\xf3\xbbl#\xec\xf1\xc2\x13\x060\x90\x1b\x03\x04\x00\x01@\x00h\x0e5\x87\x18\xc8\x92\x81\x8f}\xecc\xe17\xbf\xf9\xcd\xea\xc9\xb9\xe6?y\xf6\xd9g\xc3\xf4\xe9\xd3\x05\x7f\xf3:\xcby\x9d[#\xef|\x85W\x0c`\xa0,\x03\x04\x00\x01@\x00h\x125\x89\x18\xc8\x96\x81xo\x80\xb3\xcf>;<\xfe\xf8\xe35\x8f\xfd!\xc4;\xfbO\x992%l\xbf\xfd\xf6\xd9\x8eg\xd9&\xc8\xe35\xce\x18\xc0\x00\x060\x90\x1b\x03\x04\x00\x01@\x00\x08\x7f\xc2\x02\x06\xb2g`\xcb-\xb7\x0c\x93&M\xaa\xdd\x15\x01\xcb\x96-\x0b\x0b\x16,\x08G\x1f}t\x18<xp\xf6\xe3\x98[\x13\xe7|\x05\x17\x0c`\x00\x03\x18(\xcb\x00\x01@\x00\x10\x00\xc2\x9f\xd0\x80\x01\x0c\xbc\xc8@\x0c\xd1\x1f\xff\xf8\xc7\xc3\xf5\xd7_\x1f\x96,YR\xc9\xab\x02\x96/_\x1en\xbf\xfd\xf6\xae\x1b\xfb\xed\xb4\xd3N\xc6\xce\xfc\xc5\x00\x060\x80\x01\x0c`\xa0\xdf\x0c\x10\x00\x04\x00\x01`\xc1\xe8\xf7\x82Q\xd60z<+]g\x06\xb6\xddv\xdb0a\xc2\x84\xf0\xa3\x1f\xfd\xa8\xe32\xe0\x99g\x9e\t\xf3\xe7\xcf\x0f\x9f\xfd\xecg]\xe2o\xcd\xb6fc\x00\x03\x18\xc0\x00\x06\x1af\x80\x00 \x00\x08\x00\x0bH\xc3\x0bH\x9d\xc3\x9dc\'\'\xca0\x10\xdf"p\xc4\x11G\x84\xf3\xcf??\xdc}\xf7\xdd-\x17\x02\x0f=\xf4P\xb8\xee\xba\xeb\xc2Yg\x9d\x15\xc6\x8c\x19\x13\x86\x0c\x19b\x9eZ\xab1\x80\x01\x0c`\x00\x03\x18\x180\x03\x04\x00\x01@\x00XH\x06\xbc\x90\x94\tR\x1e+x\xa7\xc0@\x0c\xe4\xfb\xec\xb3O\xf8\xc4\'>\x11\xce=\xf7\xdc0w\xee\xdc\xf0\xe3\x1f\xff8\xfc\xeaW\xbf\xea\xba\xa1`\xbcL\xbf\xb7\xaf\xa5K\x97v=\xe6\x7f\xfe\xe7\x7f\xc2]w\xdd\xd5\x15\xf4\xe3]\xfb\'N\x9c\x18\x0e=\xf4\xd0\xb0\xddv\xdb\x99\x93\xd6e\x0c`\x00\x03\x18\xc0\x00\x06Z\xc2\x00\x01@\x00\x10\x00\x16\x97\x96,.)\x84<\xe7@V4\xca@\xbc\x97\xc06\xdbl\xd3\x15\xe6\xe3]\xf9\xdf\xfb\xde\xf7v},\xdf\xd6[om\xbeYs1\x80\x01\x0c`\x00\x03\x18\xe8\x18\x03\x04\x00\x01@\x00X\x80:\xb6\x005\x1a\xae\xfc\x9e`\x8e\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xf2\x0c\x10\x00\x04\x00\x01@\x00\x10\x00\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@\x06\x0c\x10\x00\x04\x00\x01\x90\xc1DgG\xcb\xdbQ5S3\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xd4\x18 \x00\x08\x00\x02\x80\x00`{1\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x0c\x18 \x00\x08\x00\x02 \x83\x89\x9e\x9a\xb9t>l<\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06\xca3@\x00\x10\x00\x04\x00\x01\xc0\xf6b\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x190@\x00\x10\x00\x04@\x06\x13\x9d\x1d-oG\xd5L\xcd0\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06Rc\x80\x00 \x00\x08\x00\x02\x80\xed\xc5\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x062`\x80\x00 \x00\x08\x80\x0c&zj\xe6\xd2\xf9\xb0\xf1\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18(\xcf\x00\x01@\x00\x10\x00\x04\x00\xdb\x8b\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0cd\xc0\x00\x01@\x00\x10\x00\x19Ltv\xb4\xbc\x1dU35\xc3\x00\x060\x80\x01\x0c`\x00\x03\x18H\x8d\x01\x02\x80\x00 \x00\x08\x00\xb6\x17\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc8\x80\x01\x02\x80\x00 \x002\x98\xe8\xa9\x99K\xe7\xc3\xc6c\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0<\x03\x04\x00\x01@\x00\x10\x00l/\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x90\x01\x03\x04\x00\x01@\x00d0\xd1\xd9\xd1\xf2vT\xcd\xd4\x0c\x03\x18\xc0\x00\x060\x80\x01\x0c` 5\x06\x08\x00\x02\x80\x00 \x00\xd8^\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c` \x03\x06\x08\x00\x02\x80\x00\xc8`\xa2\xa7f.\x9d\x0f\x1b\x8f\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xf2\x0c\x10\x00\x04\x00\x01@\x00\xb0\xbd\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@\x06\x0c\x10\x00\x04\x00\x01\x90\xc1DgG\xcb\xdbQ5S3\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xd4\x18 \x00\x08\x00\x02\x80\x00`{1\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x0c\x18 \x00\x08\x00\x02 \x83\x89\x9e\x9a\xb9t>l<\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06\xca3@\x00\x10\x00\x04\x00\x01\xc0\xf6b\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x190@\x00\x10\x00\x04@\x06\x13\x9d\x1d-oG\xd5L\xcd0\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06Rc\x80\x00 \x00\x08\x00\x02\x80\xed\xc5\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x062`\x80\x00 \x00\x08\x80\x0c&zj\xe6\xd2\xf9\xb0\xf1\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18(\xcf\x00\x01@\x00\x10\x00\x04\x00\xdb\x8b\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0cd\xc0\x00\x01@\x00\x10\x00\x19Ltv\xb4\xbc\x1dU35\xc3\x00\x060\x80\x01\x0c`\x00\x03\x18H\x8d\x01\x02\x80\x00 \x00\x08\x00\xb6\x17\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc8\x80\x01\x02\x80\x00 \x002\x98\xe8\xa9\x99K\xe7\xc3\xc6c\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0<\x03\x04\x00\x01@\x00\x10\x00l/\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x90\x01\x03\x04\x00\x01@\x00d0\xd1\xd9\xd1\xf2vT\xcd\xd4\x0c\x03\x18\xc0\x00\x060\x80\x01\x0c` 5\x06\x08\x00\x02\x80\x00 \x00\xd8^\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c` \x03\x06\x08\x00\x02\x80\x00\xc8`\xa2\xa7f.\x9d\x0f\x1b\x8f\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xf2\x0c\x10\x00\x04\x00\x01@\x00\xb0\xbd\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@\x06\x0c\x10\x00\x04\x00\x01\x90\xc1DgG\xcb\xdbQ5S3\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xd4\x18 \x00\x08\x00\x02\x80\x00`{1\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x0c\x18 \x00\x08\x00\x02 \x83\x89\x9e\x9a\xb9t>l<\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06\xca3@\x00\x10\x00\x04\x00\x01\xc0\xf6b\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x190@\x00\x10\x00\x04@\x06\x13\x9d\x1d-oG\xd5L\xcd0\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06Rc\x80\x00 \x00\x08\x00\x02\x80\xed\xc5\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x062`\x80\x00 \x00\x08\x80\x0c&zj\xe6\xd2\xf9\xb0\xf1\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18(\xcf\x00\x01@\x00\x10\x00\x04\x00\xdb\x8b\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0cd\xc0\x00\x01@\x00\x10\x00\x19Ltv\xb4\xbc\x1dU35\xc3\x00\x060\x80\x01\x0c`\x00\x03\x18H\x8d\x01\x02\x80\x00 \x00\x08\x00\xb6\x17\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc8\x80\x01\x02\x80\x00 \x002\x98\xe8\xa9\x99K\xe7\xc3\xc6c\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0<\x03\x04\x00\x01@\x00\x10\x00l/\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x90\x01\x03\x04\x00\x01@\x00d0\xd1\xd9\xd1\xf2vT\xcd\xd4\x0c\x03\x18\xc0\x00\x060\x80\x01\x0c` 5\x06\x08\x00\x02\x80\x00 \x00\xd8^\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c` \x03\x06\x08\x00\x02\x80\x00\xc8`\xa2\xa7f.\x9d\x0f\x1b\x8f\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xf2\x0c\x10\x00\x04\x00\x01@\x00\xb0\xbd\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@\x06\x0c\x10\x00\x04\x00\x01\x90\xc1DgG\xcb\xdbQ5S3\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xd4\x18 \x00\x08\x00\x02\x80\x00`{1\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x0c\x18 \x00\x08\x00\x02 \x83\x89\x9e\x9a\xb9t>l<\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06\xca3@\x00\x10\x00\x04\x00\x01\xc0\xf6b\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x190@\x00\x10\x00\x04@\x06\x13\x9d\x1d-oG\xd5L\xcd0\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06Rc\x80\x00 \x00\x08\x00\x02\x80\xed\xc5\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x062`\x80\x00 \x00\x08\x80\x0c&zj\xe6\xd2\xf9\xb0\xf1\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18(\xcf\x00\x01@\x00\x10\x00\x04\x00\xdb\x8b\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0cd\xc0\x00\x01@\x00\x10\x00\x19Ltv\xb4\xbc\x1dU35\xc3\x00\x060\x80\x01\x0c`\x00\x03\x18H\x8d\x01\x02\x80\x00 \x00\x08\x00\xb6\x17\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc8\x80\x01\x02\x80\x00 \x002\x98\xe8\xa9\x99K\xe7\xc3\xc6c\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0<\x03\x04\x00\x01@\x00\x10\x00l/\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x90\x01\x03\x04\x00\x01@\x00d0\xd1\xd9\xd1\xf2vT\xcd\xd4\x0c\x03\x18\xc0\x00\x060\x80\x01\x0c` 5\x06\x08\x00\x02\x80\x00 \x00\xd8^\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c` \x03\x06\x08\x00\x02\x80\x00\xc8`\xa2\xa7f.\x9d\x0f\x1b\x8f\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xf2\x0c\x10\x00\x04\x00\x01@\x00\xb0\xbd\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@\x06\x0c\x10\x00\x04\x00\x01\x90\xc1DgG\xcb\xdbQ5S3\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xd4\x18 \x00\x08\x00\x02\x80\x00`{1\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x0c\x18 \x00\x08\x00\x02 \x83\x89\x9e\x9a\xb9t>l<\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06\xca3@\x00\x10\x00\x04\x00\x01\xc0\xf6b\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x190@\x00\x10\x00\x04@\x06\x13\x9d\x1d-oG\xd5L\xcd0\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06Rc\x80\x00 \x00\x08\x00\x02\x80\xed\xc5\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x062`\x80\x00 \x00\x08\x80\x0c&zj\xe6\xd2\xf9\xb0\xf1\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18(\xcf\x00\x01@\x00\x10\x00\x04\x00\xdb\x8b\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0cd\xc0\x00\x01@\x00\x10\x00\x19Ltv\xb4\xbc\x1dU35\xc3\x00\x060\x80\x01\x0c`\x00\x03\x18H\x8d\x01\x02\x80\x00 \x00\x08\x00\xb6\x17\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc8\x80\x01\x02\x80\x00 \x002\x98\xe8\xa9\x99K\xe7\xc3\xc6c\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0<\x03\x04\x00\x01@\x00\x10\x00l/\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x90\x01\x03\x04\x00\x01@\x00d0\xd1\xd9\xd1\xf2vT\xcd\xd4\x0c\x03\x18\xc0\x00\x060\x80\x01\x0c` 5\x06\x08\x00\x02\x80\x00 \x00\xd8^\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c` \x03\x06\x08\x00\x02\x80\x00\xc8`\xa2\xa7f.\x9d\x0f\x1b\x8f\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xf2\x0c\x10\x00\x04\x00\x01@\x00\xb0\xbd\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@\x06\x0c\x10\x00\x04\x00\x01\x90\xc1DgG\xcb\xdbQ5S3\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xd4\x18 \x00\x08\x00\x02\x80\x00`{1\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x0c\x18 \x00\x08\x00\x02 \x83\x89\x9e\x9a\xb9t>l<\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06\xca3@\x00\x10\x00\x04\x00\x01\xc0\xf6b\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x190@\x00\x10\x00\x04@\x06\x13\x9d\x1d-oG\xd5L\xcd0\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06Rc\x80\x00 \x00\x08\x00\x02\x80\xed\xc5\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x062`\x80\x00 \x00\x08\x80\x0c&zj\xe6\xd2\xf9\xb0\xf1\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18(\xcf\x00\x01@\x00\x10\x00\x04\x00\xdb\x8b\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0cd\xc0\x00\x01@\x00\x10\x00\x19Ltv\xb4\xbc\x1dU35\xc3\x00\x060\x80\x01\x0c`\x00\x03\x18H\x8d\x01\x02\x80\x00 \x00\x08\x00\xb6\x17\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc8\x80\x01\x02\x80\x00 \x002\x98\xe8\xa9\x99K\xe7\xc3\xc6c\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0<\x03\x04\x00\x01@\x00\x10\x00l/\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x90\x01\x03\x04\x00\x01@\x00d0\xd1\xd9\xd1\xf2vT\xcd\xd4\x0c\x03\x18\xc0\x00\x060\x80\x01\x0c` 5\x06\x08\x00\x02\x80\x00 \x00\xd8^\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c` \x03\x06\x08\x00\x02\x80\x00\xc8`\xa2\xa7f.\x9d\x0f\x1b\x8f\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xf2\x0c\x10\x00\x04\x00\x01@\x00\xb0\xbd\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@\x06\x0c\x10\x00\x04\x00\x01\x90\xc1DgG\xcb\xdbQ5S3\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xd4\x18 \x00\x08\x00\x02\x80\x00`{1\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x0c\x18 \x00\x08\x00\x02 \x83\x89\x9e\x9a\xb9t>l<\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06\xca3@\x00\x10\x00\x04\x00\x01\xc0\xf6b\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x190@\x00\x10\x00\x04@\x06\x13\x9d\x1d-oG\xd5L\xcd0\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06Rc\x80\x00 \x00\x08\x00\x02\x80\xed\xc5\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x062`\x80\x00 \x00\x08\x80\x0c&zj\xe6\xd2\xf9\xb0\xf1\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18(\xcf\x00\x01@\x00\x10\x00\x04\x00\xdb\x8b\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0cd\xc0\x00\x01@\x00\x10\x00\x19Ltv\xb4\xbc\x1dU35\xc3\x00\x060\x80\x01\x0c`\x00\x03\x18H\x8d\x01\x02\x80\x00 \x00\x08\x00\xb6\x17\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc8\x80\x01\x02\x80\x00 \x002\x98\xe8\xa9\x99K\xe7\xc3\xc6c\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\xa0<\x03\x04\x00\x01@\x00\x10\x00l/\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x90\x01\x03\x04\x00\x01@\x00d0\xd1\xd9\xd1\xf2vT\xcd\xd4\x0c\x03\x18\xc0\x00\x060\x80\x01\x0c` 5\x06\x08\x00\x02\x80\x00 \x00\xd8^\x0c`\x00\x03\x18\xc0\x00\x060\x80\x01\x0c` \x03\x06\x08\x00\x02\x80\x00\xc8`\xa2\xa7f.\x9d\x0f\x1b\x8f\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xf2\x0c\x10\x00\x04\x00\x01@\x00\xb0\xbd\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x18\xc0@\x06\x0c\x10\x00\x04\x00\x01\x90\xc1DgG\xcb\xdbQ5S3\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\xd4\x18 \x00\x08\x00\x02\x80\x00`{1\x80\x01\x0c`\x00\x03\x18\xc0\x00\x060\x80\x81\x0c\x18 \x00\x08\x00\x02 \x83\x89\x9e\x9a\xb9t>l<\x060\x80\x01\x0c`\x00\x03\x18\xc0\x00\x06\xca3@\x00\x10\x00\x04\x00\x01\xc0\xf6b\x00\x03\x18\xc0\x00\x060\x80\x01\x0c`\x00\x03\x190@\x00\xb4F\x00\xfc\x7f\x00\x00\x00\xff\xffT\xaenr\x00\x00@\x00IDAT\xed\xddm\x8c\x9d\xe5y\xe0q\x8fMl\x02\xcc\xda\xc6\xc62\xd8\x18\x03\xf1KlG\xe2\xc5\x16R\xbet\xd9\xa4\x9b\x97f\x15@IPH\xf1\x07\xb6\x0b\x96 $awK$`c\xa4\r\xc9.I\x90\xa8\x16\xc5-\xe9\x06\x12ba!\xed\xa6\xc1\xd46.\xb6Aj\xa4j\xf3\xa2\xaa\xea\xc7\xdd\xa2m*E\xaa\xba\xbb\xc9\x97\xd2R={_f\x06\xc6\x83=\x1e\x1f\xdf\xc7~\xee\xe7\xfa\x8d4\x19;\xf3\xc2s\xee\xe7w?>\xd7\x7f\xce\x9cY\xf0\xdak\xafu}y]\xb4hQ\xb7`\xc1\x82\xe6_\xb7m\xdb\xd6}\xe0\x03\x1f\xf0j\r\x18`\x80\x01\x06\x18`\x80\x01\x06\x18`\x80\x81\x11\x0c\xc4L5\x84\xd90f\xdc\xbe\xcc\xdbq\x1c\x0b\xfat0\x02\x80h \x9c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x02\xc0x\xbeQ/\x00\x8c\xe1\x11\x07\x1e\x01\xe0\x82\xe5\x1f-\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\x18\x18\xdd\x80\x00 \x004\xf3\x10\x10\x01`\xf4\x8d\xee"i\xed\x18`\x80\x01\x06\x18`\x80\x01\x06\x18`@\x00\x10\x00\x04\x80\x11~v\xc6\xc5\xd3\xc5\x93\x01\x06\x18`\x80\x01\x06\x18`\x80\x01\x06Z3 \x00\x08\x00\x02\x80\x00\xe0\tT\x18`\x80\x01\x06\x18`\x80\x01\x06\x18` \x81\x01\x01@\x00\x10\x00\x12l\xf4\xd6\xca\xa4\xe3U\xd3\x19`\x80\x01\x06\x18`\x80\x01\x06\x18\xa8o@\x00\x10\x00\x04\x00\x01@\xede\x80\x01\x06\x18`\x80\x01\x06\x18`\x80\x81\x04\x06\x04\x00\x01@\x00H\xb0\xd1\xd5\xd3\xfa\xf5\xd4\x9aZS\x06\x18`\x80\x01\x06\x18`\x80\x81\xd6\x0c\x08\x00\x02\x80\x00 \x00\xa8\xbd\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c0\x90\xc0\x80\x00 \x00\x08\x00\t6zke\xd2\xf1\xaa\xe9\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c\xd47 \x00\x08\x00\x02\x80\x00\xa0\xf62\xc0\x00\x03\x0c0\xc0\x00\x03\x0c0\xc0@\x02\x03\x02\x80\x00 \x00$\xd8\xe8\xeai\xfdzjM\xad)\x03\x0c0\xc0\x00\x03\x0c0\xc0@k\x06\x04\x00\x01@\x00\x10\x00\xd4^\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\x18H`@\x00\x10\x00\x04\x80\x04\x1b\xbd\xb52\xe9x\xd5t\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\xea\x1b\x10\x00\x04\x00\x01@\x00P{\x19`\x80\x01\x06\x18`\x80\x01\x06\x18` \x81\x01\x01@\x00\x10\x00\x12lt\xf5\xb4~=\xb5\xa6\xd6\x94\x01\x06\x18`\x80\x01\x06\x18`\xa05\x03\x02\x80\x00 \x00\x08\x00j/\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c$0 \x00\x08\x00\x02@\x82\x8d\xdeZ\x99t\xbcj:\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\xf5\r\x08\x00\x02\x80\x00 \x00\xa8\xbd\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c0\x90\xc0\x80\x00 \x00\x08\x00\t6\xbazZ\xbf\x9eZSk\xca\x00\x03\x0c0\xc0\x00\x03\x0c0\xd0\x9a\x01\x01@\x00\x10\x00\x04\x00\xb5\x97\x01\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\x12\x18\x10\x00\x04\x00\x01 \xc1Fo\xadL:^5\x9d\x01\x06\x18`\x80\x01\x06\x18`\x80\x81\xfa\x06\x04\x00\x01@\x00\x10\x00\xd4^\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\x18H`@\x00\x10\x00\x04\x80\x04\x1b]=\xad_O\xad\xa95e\x80\x01\x06\x18`\x80\x01\x06\x18h\xcd\x80\x00 \x00\x08\x00\x02\x80\xda\xcb\x00\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\t\x0c\x08\x00\x02\x80\x00\x90`\xa3\xb7V&\x1d\xaf\x9a\xce\x00\x03\x0c0\xc0\x00\x03\x0c0\xc0@}\x03\x02\x80\x00 \x00\x08\x00j/\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c$0 \x00\x08\x00\x02@\x82\x8d\xae\x9e\xd6\xaf\xa7\xd6\xd4\x9a2\xc0\x00\x03\x0c0\xc0\x00\x03\x0c\xb4f@\x00\x10\x00\x04\x00\x01@\xede\x80\x01\x06\x18`\x80\x01\x06\x18`\x80\x81\x04\x06\x04\x00\x01@\x00H\xb0\xd1[+\x93\x8eWMg\x80\x01\x06\x18`\x80\x01\x06\x18`\xa0\xbe\x01\x01@\x00\x10\x00\x04\x00\xb5\x97\x01\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\x12\x18\x10\x00\x04\x00\x01 \xc1FWO\xeb\xd7SkjM\x19`\x80\x01\x06\x18`\x80\x01\x06Z3 \x00\x08\x00\x02\x80\x00\xa0\xf62\xc0\x00\x03\x0c0\xc0\x00\x03\x0c0\xc0@\x02\x03\x02\x80\x00 \x00$\xd8\xe8\xad\x95I\xc7\xab\xa63\xc0\x00\x03\x0c0\xc0\x00\x03\x0c0P\xdf\x80\x00 \x00\x08\x00\x02\x80\xda\xcb\x00\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\t\x0c\x08\x00\x02\x80\x00\x90`\xa3\xab\xa7\xf5\xeb\xa95\xb5\xa6\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\xad\x19\x10\x00\x04\x00\x01@\x00P{\x19`\x80\x01\x06\x18`\x80\x01\x06\x18` \x81\x01\x01@\x00\x10\x00\x12l\xf4\xd6\xca\xa4\xe3U\xd3\x19`\x80\x01\x06\x18`\x80\x01\x06\x18\xa8o@\x00\x10\x00\x04\x00\x01@\xede\x80\x01\x06\x18`\x80\x01\x06\x18`\x80\x81\x04\x06\x04\x00\x01@\x00H\xb0\xd1\xd5\xd3\xfa\xf5\xd4\x9aZS\x06\x18`\x80\x01\x06\x18`\x80\x81\xd6\x0c\x08\x00\x02\x80\x00 \x00\xa8\xbd\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c0\x90\xc0\x80\x00 \x00\x08\x00\t6zke\xd2\xf1\xaa\xe9\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c\xd47 \x00\x08\x00\x02\x80\x00\xa0\xf62\xc0\x00\x03\x0c0\xc0\x00\x03\x0c0\xc0@\x02\x03\x02\x80\x00 \x00$\xd8\xe8\xeai\xfdzjM\xad)\x03\x0c0\xc0\x00\x03\x0c0\xc0@k\x06\x04\x00\x01@\x00\x10\x00\xd4^\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\x18H`@\x00\x10\x00\x04\x80\x04\x1b\xbd\xb52\xe9x\xd5t\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\xea\x1b\x10\x00\x04\x00\x01@\x00P{\x19`\x80\x01\x06\x18`\x80\x01\x06\x18` \x81\x01\x01@\x00\x10\x00\x12lt\xf5\xb4~=\xb5\xa6\xd6\x94\x01\x06\x18`\x80\x01\x06\x18`\xa05\x03\x02\x80\x00 \x00\x08\x00j/\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c$0 \x00\x08\x00\x02@\x82\x8d\xdeZ\x99t\xbcj:\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\xf5\r\x08\x00\x02\x80\x00 \x00\xa8\xbd\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c0\x90\xc0\x80\x00 \x00\x08\x00\t6\xbazZ\xbf\x9eZSk\xca\x00\x03\x0c0\xc0\x00\x03\x0c0\xd0\x9a\x01\x01@\x00\x10\x00\x04\x00\xb5\x97\x01\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\x12\x18\x10\x00\x04\x00\x01 \xc1Fo\xadL:^5\x9d\x01\x06\x18`\x80\x01\x06\x18`\x80\x81\xfa\x06\x04\x00\x01@\x00\x10\x00\xd4^\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\x18H`@\x00\x10\x00\x04\x80\x04\x1b]=\xad_O\xad\xa95e\x80\x01\x06\x18`\x80\x01\x06\x18h\xcd\x80\x00 \x00\x08\x00\x02\x80\xda\xcb\x00\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\t\x0c\x08\x00\x02\x80\x00\x90`\xa3\xb7V&\x1d\xaf\x9a\xce\x00\x03\x0c0\xc0\x00\x03\x0c0\xc0@}\x03\x02\x80\x00 \x00\x08\x00j/\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c$0 \x00\x08\x00\x02@\x82\x8d\xae\x9e\xd6\xaf\xa7\xd6\xd4\x9a2\xc0\x00\x03\x0c0\xc0\x00\x03\x0c\xb4f@\x00\x10\x00\x04\x00\x01@\xede\x80\x01\x06\x18`\x80\x01\x06\x18`\x80\x81\x04\x06\x04\x00\x01@\x00H\xb0\xd1[+\x93\x8eWMg\x80\x01\x06\x18`\x80\x01\x06\x18`\xa0\xbe\x01\x01@\x00\x10\x00\x04\x00\xb5\x97\x01\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\x12\x18\x10\x00\x04\x00\x01 \xc1FWO\xeb\xd7SkjM\x19`\x80\x01\x06\x18`\x80\x01\x06Z3 \x00\x08\x00\x02\x80\x00\xa0\xf62\xc0\x00\x03\x0c0\xc0\x00\x03\x0c0\xc0@\x02\x03\x02\x80\x00 \x00$\xd8\xe8\xad\x95I\xc7\xab\xa63\xc0\x00\x03\x0c0\xc0\x00\x03\x0c0P\xdf\x80\x00 \x00\x08\x00\x02\x80\xda\xcb\x00\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\t\x0c\x08\x00\x02\x80\x00\x90`\xa3\xab\xa7\xf5\xeb\xa95\xb5\xa6\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\xad\x19\x10\x00\x04\x00\x01@\x00P{\x19`\x80\x01\x06\x18`\x80\x01\x06\x18` \x81\x01\x01@\x00\x10\x00\x12l\xf4\xd6\xca\xa4\xe3U\xd3\x19`\x80\x01\x06\x18`\x80\x01\x06\x18\xa8o@\x00\x10\x00\x04\x00\x01@\xede\x80\x01\x06\x18`\x80\x01\x06\x18`\x80\x81\x04\x06\x04\x00\x01@\x00H\xb0\xd1\xd5\xd3\xfa\xf5\xd4\x9aZS\x06\x18`\x80\x01\x06\x18`\x80\x81\xd6\x0c\x08\x00\x02\x80\x00 \x00\xa8\xbd\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c0\x90\xc0\x80\x00 \x00\x08\x00\t6zke\xd2\xf1\xaa\xe9\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c\xd47 \x00\x08\x00\x02\x80\x00\xa0\xf62\xc0\x00\x03\x0c0\xc0\x00\x03\x0c0\xc0@\x02\x03\x02\x80\x00 \x00$\xd8\xe8\xeai\xfdzjM\xad)\x03\x0c0\xc0\x00\x03\x0c0\xc0@k\x06\x04\x00\x01@\x00\x10\x00\xd4^\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\x18H`@\x00\x10\x00\x04\x80\x04\x1b\xbd\xb52\xe9x\xd5t\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\xea\x1b\x10\x00\x04\x00\x01@\x00P{\x19`\x80\x01\x06\x18`\x80\x01\x06\x18` \x81\x01\x01@\x00\x10\x00\x12lt\xf5\xb4~=\xb5\xa6\xd6\x94\x01\x06\x18`\x80\x01\x06\x18`\xa05\x03\x02\x80\x00 \x00\x08\x00j/\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c$0 \x00\x08\x00\x02@\x82\x8d\xdeZ\x99t\xbcj:\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\xf5\r\x08\x00\x02\x80\x00 \x00\xa8\xbd\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c0\x90\xc0\x80\x00 \x00\x08\x00\t6\xbazZ\xbf\x9eZSk\xca\x00\x03\x0c0\xc0\x00\x03\x0c0\xd0\x9a\x01\x01@\x00\x10\x00\x04\x00\xb5\x97\x01\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\x12\x18\x10\x00\x04\x00\x01 \xc1Fo\xadL:^5\x9d\x01\x06\x18`\x80\x01\x06\x18`\x80\x81\xfa\x06\x04\x00\x01@\x00\x10\x00\xd4^\x06\x18`\x80\x01\x06\x18`\x80\x01\x06\x18H`@\x00\x10\x00\x04\x80\x04\x1b]=\xad_O\xad\xa95e\x80\x01\x06\x18`\x80\x01\x06\x18h\xcd\x80\x00 \x00\x08\x00\x02\x80\xda\xcb\x00\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\t\x0c\x08\x00\x02\x80\x00\x90`\xa3\xb7V&\x1d\xaf\x9a\xce\x00\x03\x0c0\xc0\x00\x03\x0c0\xc0@}\x03\x02\x80\x00 \x00\x08\x00j/\x03\x0c0\xc0\x00\x03\x0c0\xc0\x00\x03\x0c$0 \x00\x08\x00\x02@\x82\x8d\xae\x9e\xd6\xaf\xa7\xd6\
gitextract_c46jkt4m/
├── .github/
│ └── workflows/
│ └── cross-build.yml
├── .gitignore
├── LICENSE
├── data/
│ └── icons/
│ └── maestro_icon.icns
├── dist/
│ └── maestro_music-2.0.6-py3-none-any.whl
├── install-scripts/
│ ├── mac
│ ├── ubuntu
│ └── windows.nsi
├── maestro/
│ ├── __init__.py
│ ├── __main__.py
│ ├── __version__.py
│ ├── config.py
│ ├── helpers.py
│ ├── icon.py
│ ├── jit_funcs.py
│ ├── mac_presence.py
│ └── main.py
├── readme.md
├── requirements.txt
├── scripts/
│ ├── add_album_art.py
│ ├── custom_album_art.py
│ └── rename_tracktitles.py
├── setup.py
├── specs/
│ ├── maestro-mac.spec
│ ├── maestro-ubuntu.spec
│ └── maestro-windows.spec
└── uninstall-scripts/
└── unix
SYMBOL INDEX (206 symbols across 7 files)
FILE: maestro/config.py
function print_to_logfile (line 238) | def print_to_logfile(*args, **kwargs):
FILE: maestro/helpers.py
class Song (line 27) | class Song:
method __init__ (line 28) | def __init__(self, song_id: int):
method reset (line 42) | def reset(self):
method __eq__ (line 50) | def __eq__(self, value):
method __hash__ (line 55) | def __hash__(self):
method __repr__ (line 58) | def __repr__(self):
method override_lyrics_path (line 62) | def override_lyrics_path(self):
method translated_lyrics_path (line 68) | def translated_lyrics_path(self):
method song_id (line 74) | def song_id(self):
method song_file (line 78) | def song_file(self):
method song_path (line 83) | def song_path(self):
method song_title (line 88) | def song_title(self):
method song_title (line 93) | def song_title(self, v):
method tags (line 114) | def tags(self) -> set[str]:
method tags (line 118) | def tags(self, v: set[str]):
method clips (line 122) | def clips(self) -> dict[str, list[int, int]]:
method set_clip (line 126) | def set_clip(self) -> str:
method set_clip (line 130) | def set_clip(self, v: str):
method listen_times (line 134) | def listen_times(self) -> dict[int | str, float]:
method _load_metadata (line 137) | def _load_metadata(self):
method artist (line 143) | def artist(self):
method artist (line 147) | def artist(self, v):
method album (line 151) | def album(self):
method album (line 155) | def album(self, v):
method album_artist (line 159) | def album_artist(self):
method album_artist (line 163) | def album_artist(self, v):
method duration (line 167) | def duration(self):
method artwork (line 171) | def artwork(self):
method raw_lyrics (line 181) | def raw_lyrics(self) -> str | None:
method raw_lyrics (line 185) | def raw_lyrics(self, v):
method raw_override_lyrics (line 197) | def raw_override_lyrics(self) -> str | None:
method raw_override_lyrics (line 204) | def raw_override_lyrics(self, v):
method raw_translated_lyrics (line 220) | def raw_translated_lyrics(self) -> str | None:
method raw_translated_lyrics (line 227) | def raw_translated_lyrics(self, v):
method _parse_lyrics (line 240) | def _parse_lyrics(self, raw_lyrics):
method parsed_lyrics (line 254) | def parsed_lyrics(self):
method parsed_override_lyrics (line 260) | def parsed_override_lyrics(self):
method parsed_translated_lyrics (line 268) | def parsed_translated_lyrics(self):
method get_metadata (line 275) | def get_metadata(self, key, resolve=True):
method set_metadata (line 291) | def set_metadata(self, key, value):
method remove_from_data (line 312) | def remove_from_data(self):
method _save (line 315) | def _save(self):
class SongData (line 320) | class SongData:
method __init__ (line 321) | def __init__(self):
method load (line 325) | def load(self):
method __getitem__ (line 350) | def __getitem__(self, key):
method __setitem__ (line 355) | def __setitem__(self, key, value):
method __delitem__ (line 360) | def __delitem__(self, key):
method __iter__ (line 365) | def __iter__(self):
method items (line 370) | def items(self):
method values (line 375) | def values(self):
method _save (line 380) | def _save(self):
method add_song (line 387) | def add_song(self, filename, tags=None):
class Songs (line 417) | class Songs:
method __init__ (line 422) | def __init__(self):
method load (line 426) | def load(self):
method __contains__ (line 429) | def __contains__(self, value: Song):
method __iter__ (line 434) | def __iter__(self) -> Iterable[Song]:
method __len__ (line 439) | def __len__(self):
function is_safe_username (line 448) | def is_safe_username(url):
function bounded_shuffle (line 452) | def bounded_shuffle(lst, radius=-1):
function set_timeout (line 472) | def set_timeout(func, timeout, *args, **kwargs):
class Scroller (line 482) | class Scroller:
method __init__ (line 483) | def __init__(self, num_lines, win_size):
method scroll_forward (line 489) | def scroll_forward(self):
method scroll_backward (line 498) | def scroll_backward(self):
method halfway (line 505) | def halfway(self):
method resize (line 508) | def resize(self, win_size=None):
method refresh (line 514) | def refresh(self):
function fit_string_to_width (line 518) | def fit_string_to_width(s, width, length_so_far):
function addstr_fit_to_width (line 529) | def addstr_fit_to_width(stdscr, s, width, length_so_far, *args, **kwargs):
class FFmpegProcessHandler (line 540) | class FFmpegProcessHandler:
method __init__ (line 541) | def __init__(self, username, password):
method start (line 546) | def start(self):
method _start (line 549) | def _start(self):
method terminate (line 571) | def terminate(self):
method restart (line 576) | def restart(self):
method write (line 580) | def write(self, chunk):
class PlaybackHandler (line 588) | class PlaybackHandler:
method __init__ (line 589) | def __init__(
method _load_audio (line 677) | def _load_audio(self, path, sr):
method _audio_processing_loop (line 692) | def _audio_processing_loop(self):
method _streaming_loop (line 826) | def _streaming_loop(self):
method paused (line 873) | def paused(self):
method paused (line 877) | def paused(self, value):
method volume (line 883) | def volume(self):
method volume (line 887) | def volume(self, v):
method want_stream (line 893) | def want_stream(self):
method want_stream (line 897) | def want_stream(self, value):
method song (line 903) | def song(self):
method song_id (line 907) | def song_id(self):
method song_file (line 911) | def song_file(self):
method song_path (line 915) | def song_path(self):
method song_title (line 919) | def song_title(self):
method song_artist (line 923) | def song_artist(self):
method song_album (line 927) | def song_album(self):
method album_artist (line 931) | def album_artist(self):
method artwork (line 935) | def artwork(self):
method screen_height (line 943) | def screen_height(self):
method screen_width (line 947) | def screen_width(self):
method can_show_lyrics (line 951) | def can_show_lyrics(self):
method can_show_translated_lyrics (line 955) | def can_show_translated_lyrics(self):
method focus (line 961) | def focus(self):
method focus (line 965) | def focus(self, value):
method seek (line 970) | def seek(self, pos):
method scroll_forward (line 980) | def scroll_forward(self):
method scroll_backward (line 990) | def scroll_backward(self):
method snap_back (line 1000) | def snap_back(self):
method set_volume (line 1007) | def set_volume(self, v):
method quit (line 1011) | def quit(self):
method prompting_delete_char (line 1018) | def prompting_delete_char(self):
method update_screen (line 1027) | def update_screen(self):
method connect_to_discord (line 1030) | def connect_to_discord(self):
method update_discord_metadata (line 1042) | def update_discord_metadata(self):
method update_mac_now_playing_metadata (line 1108) | def update_mac_now_playing_metadata(self):
method update_icecast_metadata (line 1130) | def update_icecast_metadata(self):
method icecast_metadata_update_loop (line 1147) | def icecast_metadata_update_loop(self):
method threaded_update_icecast_metadata (line 1167) | def threaded_update_icecast_metadata(self):
method update_stream_metadata (line 1172) | def update_stream_metadata(self): # artwork + icecast metadata
method update_metadata (line 1193) | def update_metadata(self):
method initialize_discord (line 1201) | def initialize_discord(self):
method threaded_initialize_discord (line 1210) | def threaded_initialize_discord(self):
method output (line 1213) | def output(self, pos):
function init_curses (line 1902) | def init_curses(stdscr):
class SongParamType (line 1936) | class SongParamType(click.ParamType):
method convert (line 1939) | def convert(self, value, param, ctx) -> Song:
function yt_embed_artwork (line 1971) | def yt_embed_artwork(yt_dlp_info, crop):
function clip_editor (line 2040) | def clip_editor(stdscr, song: Song, name, start=None, end=None):
function clip_editor_output (line 2178) | def clip_editor_output(
function get_username (line 2371) | def get_username():
function get_password (line 2377) | def get_password():
function signup (line 2383) | def signup(username=None, password=None, login_=True):
function login (line 2430) | def login(username=None, password=None):
function format_seconds (line 2475) | def format_seconds(secs, show_decimal=False, digital=True, include_hours...
function search_song (line 2503) | def search_song(phrase):
function print_entry (line 2525) | def print_entry(
function multiprocessing_put_word (line 2615) | def multiprocessing_put_word(q, word):
function versiontuple (line 2621) | def versiontuple(v):
function pluralize (line 2625) | def pluralize(count, word, include_count=True):
function is_timed_lyrics (line 2629) | def is_timed_lyrics(lyrics):
function get_lyric (line 2635) | def get_lyric(lyric_obj):
function set_lyric (line 2641) | def set_lyric(lyrics, i, val):
function display_lyrics (line 2648) | def display_lyrics(lyrics, song, prefix: str = ""):
function filter_songs (line 2677) | def filter_songs(
FILE: maestro/jit_funcs.py
function lerp (line 22) | def lerp(start, stop, t):
function bin_average (line 27) | def bin_average(arr: np.ndarray, n, include_remainder=False, func=None):
function render (line 46) | def render(
FILE: maestro/mac_presence.py
class AppDelegate (line 32) | class AppDelegate(NSObject): # so Python doesn't bounce in the dock
method applicationDidFinishLaunching_ (line 33) | def applicationDidFinishLaunching_(self, _aNotification):
method sayHello_ (line 36) | def sayHello_(self, _sender):
function app_helper_loop (line 39) | def app_helper_loop():
class MockQueue (line 57) | class MockQueue: # enable testing this file
method put (line 58) | def put(self, *args, **kwargs):
method empty (line 61) | def empty(self):
class MockInt (line 65) | class MockInt:
method __init__ (line 66) | def __init__(self) -> None:
method value (line 70) | def value(self):
method value (line 74) | def value(self, value):
class MacNowPlaying (line 78) | class MacNowPlaying:
method __init__ (line 79) | def __init__(self):
method play_handler (line 131) | def play_handler(self, _event):
method pause_handler (line 140) | def pause_handler(self, _event):
method toggle_handler (line 149) | def toggle_handler(self, _event):
method next_handler (line 157) | def next_handler(self, _event):
method prev_handler (line 164) | def prev_handler(self, _event):
method seek_backward_handler (line 171) | def seek_backward_handler(self, _event):
method seek_forward_handler (line 178) | def seek_forward_handler(self, _event):
method change_position_handler (line 185) | def change_position_handler(self, event):
method stop (line 191) | def stop(self):
method pause (line 198) | def pause(self):
method resume (line 205) | def resume(self):
method update (line 212) | def update(self):
FILE: maestro/main.py
function _play (line 29) | def _play(
function cli (line 786) | def cli(ctx: click.Context):
function add (line 1004) | def add(
function remove (line 1369) | def remove(args, force, tag):
function tag_ (line 1431) | def tag_(songs, tags):
function untag (line 1456) | def untag(songs, tags, all_, force):
function play (line 1626) | def play(
function rename (line 1810) | def rename(original, new_name, renaming_tag):
function search (line 1850) | def search(phrase, searching_for_tags):
function list_ (line 2013) | def list_(
function entry (line 2288) | def entry(songs, year):
function recommend (line 2320) | def recommend(song, title):
function clips_ (line 2373) | def clips_(songs: tuple[helpers.Song]):
function clip_ (line 2425) | def clip_(song: helpers.Song, name, start, end, editor):
function unclip (line 2537) | def unclip(songs: tuple[helpers.Song], names, all_, force):
function set_clip (line 2621) | def set_clip(songs: tuple[helpers.Song], name, force):
function metadata (line 2668) | def metadata(songs: tuple[helpers.Song], pairs, artist, album, album_art...
function dir_ (line 2750) | def dir_(directory):
function version (line 2777) | def version():
function login (line 2787) | def login(username):
function logout (line 2806) | def logout(force):
function signup (line 2834) | def signup(username, login_):
function clear_logs (line 2849) | def clear_logs():
function download_ffmpeg (line 2865) | def download_ffmpeg():
function migrate (line 2876) | def migrate():
function format_data (line 2949) | def format_data(indent: int):
function lyrics_ (line 3021) | def lyrics_(
function transliterate (line 3269) | def transliterate(songs, lang, override, force):
function translate (line 3444) | def translate(songs, save_, from_langs, to_lang, force, remove_):
function user (line 3596) | def user():
FILE: scripts/custom_album_art.py
function yt_embed_artwork (line 22) | def yt_embed_artwork(path_, yt_dlp_info):
FILE: setup.py
function read_requirements (line 15) | def read_requirements(path: str) -> list[str]:
function main (line 44) | def main() -> None:
Condensed preview — 27 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (463K chars).
[
{
"path": ".github/workflows/cross-build.yml",
"chars": 4602,
"preview": "name: Cross-Platform Build\n\non:\n push:\n branches:\n - dev\n\njobs:\n pyinstaller-build-windows:\n runs-on: windo"
},
{
"path": ".gitignore",
"chars": 193,
"preview": ".vscode\nbuild\n*.egg-info\n**/*.log\n*.egg\n__pycache__\n*.code-workspace\n**/*.env\n.pylintrc\n\ntodo.md\ntest_songs/\nsong_info.t"
},
{
"path": "LICENSE",
"chars": 1058,
"preview": "Copyright © 2022 Prajwal Vandana\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this s"
},
{
"path": "install-scripts/mac",
"chars": 419,
"preview": "#!/bin/sh\n\nMAESTRO_BUNDLE_LOC=/usr/local/bin/maestro-bundle\nMAESTRO_SYMLINK_LOC=/usr/local/bin/maestro\n\n# Make maestro e"
},
{
"path": "install-scripts/ubuntu",
"chars": 1528,
"preview": "#!/bin/sh\n\nPREINSTALL_LOC=.\nMAESTRO_BUNDLE_LOC=/usr/local/bin/maestro-bundle\nMAESTRO_SYMLINK_LOC=/usr/local/bin/maestro\n"
},
{
"path": "install-scripts/windows.nsi",
"chars": 1275,
"preview": "!define PRODUCT_NAME \"maestro-cli\"\n\nOutFile \"maestro-installer.exe\"\n\nSection\n ; Set installation directory\n SetOut"
},
{
"path": "maestro/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "maestro/__main__.py",
"chars": 292,
"preview": "import sys\n\nfrom multiprocessing import freeze_support\n\nfrom maestro.main import cli\n\n\nif __name__ == \"__main__\":\n # "
},
{
"path": "maestro/__version__.py",
"chars": 17,
"preview": "VERSION = \"2.0.6\""
},
{
"path": "maestro/config.py",
"chars": 6159,
"preview": "import os\n\nfrom datetime import date, datetime\nfrom urllib.parse import urljoin\n\n\n# region constants\n\nDISCORD_ID = 10390"
},
{
"path": "maestro/helpers.py",
"chars": 88635,
"preview": "# region imports\n\nimport atexit\nimport curses\nimport logging\nimport os\nimport subprocess\nimport threading\n\nlogging.disab"
},
{
"path": "maestro/icon.py",
"chars": 146639,
"preview": "img = b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00\\x04\\x00\\x00\\x00\\x04\\x00\\x08\\x06\\x00\\x00\\x00\\x7f\\x1d+\\x83\\x00\\x00\\x00"
},
{
"path": "maestro/jit_funcs.py",
"chars": 3314,
"preview": "import warnings\n\nimport numpy as np\n\nfrom maestro import config\nfrom maestro.config import print_to_logfile\n\ntry:\n fr"
},
{
"path": "maestro/mac_presence.py",
"chars": 9369,
"preview": "# BIG thanks to @othalan on StackOverflow for this\n# adapted from https://stackoverflow.com/questions/69965175/pyobjc-ac"
},
{
"path": "maestro/main.py",
"chars": 129915,
"preview": "# pylint: disable=possibly-used-before-assignment\n\n# region imports\nimport curses\nimport multiprocessing\nimport os\nimpor"
},
{
"path": "readme.md",
"chars": 10511,
"preview": "# maestro\n[](https://pepy.tech/project/maestro-music) [!["
},
{
"path": "requirements.txt",
"chars": 1319,
"preview": "click # CLI handling\njust_playback # Audio playback\nmusic-tag # Metadata handling\npillow # Image processing (require"
},
{
"path": "scripts/add_album_art.py",
"chars": 1075,
"preview": "\"\"\"\nAdd album art to songs that don't have it, using the title of the song to search\nSpotify for the album art. Can fail"
},
{
"path": "scripts/custom_album_art.py",
"chars": 2394,
"preview": "\"\"\"\nUsage: python custom_album_art.py <path_to_songs_directory>\n\"\"\"\n\nimport json\nimport os\nimport subprocess\nimport sys\n"
},
{
"path": "scripts/rename_tracktitles.py",
"chars": 547,
"preview": "\"\"\"\nRename the 'tracktitle' metadata property of all songs to the name of the song\nfile. Sufficiently new versions of ma"
},
{
"path": "setup.py",
"chars": 2245,
"preview": "from os.path import normpath\nimport re\n\nfrom setuptools import setup, find_packages\n\n\nd = {}\nwith open(normpath(\"maestro"
},
{
"path": "specs/maestro-mac.spec",
"chars": 1458,
"preview": "# -*- mode: python ; coding: utf-8 -*-\nfrom PyInstaller.utils.hooks import collect_all\n\ndatas = []\nbinaries = []\nhiddeni"
},
{
"path": "specs/maestro-ubuntu.spec",
"chars": 1685,
"preview": "# -*- mode: python ; coding: utf-8 -*-\nfrom PyInstaller.utils.hooks import collect_all\n\ndatas = []\nbinaries = []\nhiddeni"
},
{
"path": "specs/maestro-windows.spec",
"chars": 1458,
"preview": "# -*- mode: python ; coding: utf-8 -*-\nfrom PyInstaller.utils.hooks import collect_all\n\ndatas = []\nbinaries = []\nhiddeni"
},
{
"path": "uninstall-scripts/unix",
"chars": 565,
"preview": "#!/bin/sh\n\nMAESTRO_BUNDLE_LOC=/usr/local/bin/maestro-bundle\nMAESTRO_SYMLINK_LOC=/usr/local/bin/maestro\n\n# Remove bundle "
}
]
// ... and 2 more files (download for full content)
About this extraction
This page contains the full source code of the PrajwalVandana/maestro-cli GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 27 files (406.9 KB), approximately 140.9k tokens, and a symbol index with 206 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.