Configure git to always use LF (#1013)
This commit is contained in:
parent
b0c435caf3
commit
6b859eb436
|
|
@ -1 +1 @@
|
||||||
docker_assets\run.sh eol=lf
|
* text=auto eol=lf
|
||||||
|
|
|
||||||
|
|
@ -1,352 +1,352 @@
|
||||||
"""
|
"""
|
||||||
Loose collection of helper functions
|
Loose collection of helper functions
|
||||||
- don't import AppConfig class here to avoid circular imports
|
- don't import AppConfig class here to avoid circular imports
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import subprocess
|
import subprocess
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from common.src.es_connect import IndexPaginate
|
from common.src.es_connect import IndexPaginate
|
||||||
|
|
||||||
|
|
||||||
def ignore_filelist(filelist: list[str]) -> list[str]:
|
def ignore_filelist(filelist: list[str]) -> list[str]:
|
||||||
"""ignore temp files for os.listdir sanitizer"""
|
"""ignore temp files for os.listdir sanitizer"""
|
||||||
to_ignore = [
|
to_ignore = [
|
||||||
"@eaDir",
|
"@eaDir",
|
||||||
"Icon\r\r",
|
"Icon\r\r",
|
||||||
"Network Trash Folder",
|
"Network Trash Folder",
|
||||||
"Temporary Items",
|
"Temporary Items",
|
||||||
]
|
]
|
||||||
cleaned: list[str] = []
|
cleaned: list[str] = []
|
||||||
for file_name in filelist:
|
for file_name in filelist:
|
||||||
if file_name.startswith(".") or file_name in to_ignore:
|
if file_name.startswith(".") or file_name in to_ignore:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
cleaned.append(file_name)
|
cleaned.append(file_name)
|
||||||
|
|
||||||
return cleaned
|
return cleaned
|
||||||
|
|
||||||
|
|
||||||
def randomizor(length: int) -> str:
|
def randomizor(length: int) -> str:
|
||||||
"""generate random alpha numeric string"""
|
"""generate random alpha numeric string"""
|
||||||
pool: str = string.digits + string.ascii_letters
|
pool: str = string.digits + string.ascii_letters
|
||||||
return "".join(random.choice(pool) for i in range(length))
|
return "".join(random.choice(pool) for i in range(length))
|
||||||
|
|
||||||
|
|
||||||
def rand_sleep(config) -> None:
|
def rand_sleep(config) -> None:
|
||||||
"""randomized sleep based on config"""
|
"""randomized sleep based on config"""
|
||||||
sleep_config = config["downloads"].get("sleep_interval")
|
sleep_config = config["downloads"].get("sleep_interval")
|
||||||
if not sleep_config:
|
if not sleep_config:
|
||||||
return
|
return
|
||||||
|
|
||||||
secs = random.randrange(int(sleep_config * 0.5), int(sleep_config * 1.5))
|
secs = random.randrange(int(sleep_config * 0.5), int(sleep_config * 1.5))
|
||||||
sleep(secs)
|
sleep(secs)
|
||||||
|
|
||||||
|
|
||||||
def requests_headers() -> dict[str, str]:
|
def requests_headers() -> dict[str, str]:
|
||||||
"""build header with random user agent for requests outside of yt-dlp"""
|
"""build header with random user agent for requests outside of yt-dlp"""
|
||||||
|
|
||||||
chrome_versions = (
|
chrome_versions = (
|
||||||
"90.0.4430.212",
|
"90.0.4430.212",
|
||||||
"90.0.4430.24",
|
"90.0.4430.24",
|
||||||
"90.0.4430.70",
|
"90.0.4430.70",
|
||||||
"90.0.4430.72",
|
"90.0.4430.72",
|
||||||
"90.0.4430.85",
|
"90.0.4430.85",
|
||||||
"90.0.4430.93",
|
"90.0.4430.93",
|
||||||
"91.0.4472.101",
|
"91.0.4472.101",
|
||||||
"91.0.4472.106",
|
"91.0.4472.106",
|
||||||
"91.0.4472.114",
|
"91.0.4472.114",
|
||||||
"91.0.4472.124",
|
"91.0.4472.124",
|
||||||
"91.0.4472.164",
|
"91.0.4472.164",
|
||||||
"91.0.4472.19",
|
"91.0.4472.19",
|
||||||
"91.0.4472.77",
|
"91.0.4472.77",
|
||||||
"92.0.4515.107",
|
"92.0.4515.107",
|
||||||
"92.0.4515.115",
|
"92.0.4515.115",
|
||||||
"92.0.4515.131",
|
"92.0.4515.131",
|
||||||
"92.0.4515.159",
|
"92.0.4515.159",
|
||||||
"92.0.4515.43",
|
"92.0.4515.43",
|
||||||
"93.0.4556.0",
|
"93.0.4556.0",
|
||||||
"93.0.4577.15",
|
"93.0.4577.15",
|
||||||
"93.0.4577.63",
|
"93.0.4577.63",
|
||||||
"93.0.4577.82",
|
"93.0.4577.82",
|
||||||
"94.0.4606.41",
|
"94.0.4606.41",
|
||||||
"94.0.4606.54",
|
"94.0.4606.54",
|
||||||
"94.0.4606.61",
|
"94.0.4606.61",
|
||||||
"94.0.4606.71",
|
"94.0.4606.71",
|
||||||
"94.0.4606.81",
|
"94.0.4606.81",
|
||||||
"94.0.4606.85",
|
"94.0.4606.85",
|
||||||
"95.0.4638.17",
|
"95.0.4638.17",
|
||||||
"95.0.4638.50",
|
"95.0.4638.50",
|
||||||
"95.0.4638.54",
|
"95.0.4638.54",
|
||||||
"95.0.4638.69",
|
"95.0.4638.69",
|
||||||
"95.0.4638.74",
|
"95.0.4638.74",
|
||||||
"96.0.4664.18",
|
"96.0.4664.18",
|
||||||
"96.0.4664.45",
|
"96.0.4664.45",
|
||||||
"96.0.4664.55",
|
"96.0.4664.55",
|
||||||
"96.0.4664.93",
|
"96.0.4664.93",
|
||||||
"97.0.4692.20",
|
"97.0.4692.20",
|
||||||
)
|
)
|
||||||
template = (
|
template = (
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||||
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
|
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||||
+ f"Chrome/{random.choice(chrome_versions)} Safari/537.36"
|
+ f"Chrome/{random.choice(chrome_versions)} Safari/537.36"
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"User-Agent": template}
|
return {"User-Agent": template}
|
||||||
|
|
||||||
|
|
||||||
def date_parser(timestamp: int | str | None) -> str | None:
|
def date_parser(timestamp: int | str | None) -> str | None:
|
||||||
"""return formatted date string"""
|
"""return formatted date string"""
|
||||||
if timestamp is None:
|
if timestamp is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if isinstance(timestamp, int):
|
if isinstance(timestamp, int):
|
||||||
date_obj = datetime.fromtimestamp(timestamp, tz=timezone.utc)
|
date_obj = datetime.fromtimestamp(timestamp, tz=timezone.utc)
|
||||||
elif isinstance(timestamp, str):
|
elif isinstance(timestamp, str):
|
||||||
date_obj = datetime.strptime(timestamp, "%Y-%m-%d")
|
date_obj = datetime.strptime(timestamp, "%Y-%m-%d")
|
||||||
date_obj = date_obj.replace(tzinfo=timezone.utc)
|
date_obj = date_obj.replace(tzinfo=timezone.utc)
|
||||||
else:
|
else:
|
||||||
raise TypeError(f"invalid timestamp: {timestamp}")
|
raise TypeError(f"invalid timestamp: {timestamp}")
|
||||||
|
|
||||||
return date_obj.isoformat()
|
return date_obj.isoformat()
|
||||||
|
|
||||||
|
|
||||||
def time_parser(timestamp: str) -> float:
|
def time_parser(timestamp: str) -> float:
|
||||||
"""return seconds from timestamp, false on empty"""
|
"""return seconds from timestamp, false on empty"""
|
||||||
if not timestamp:
|
if not timestamp:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if timestamp.isnumeric():
|
if timestamp.isnumeric():
|
||||||
return int(timestamp)
|
return int(timestamp)
|
||||||
|
|
||||||
hours, minutes, seconds = timestamp.split(":", maxsplit=3)
|
hours, minutes, seconds = timestamp.split(":", maxsplit=3)
|
||||||
return int(hours) * 60 * 60 + int(minutes) * 60 + float(seconds)
|
return int(hours) * 60 * 60 + int(minutes) * 60 + float(seconds)
|
||||||
|
|
||||||
|
|
||||||
def clear_dl_cache(cache_dir: str) -> int:
|
def clear_dl_cache(cache_dir: str) -> int:
|
||||||
"""clear leftover files from dl cache"""
|
"""clear leftover files from dl cache"""
|
||||||
print("clear download cache")
|
print("clear download cache")
|
||||||
download_cache_dir = os.path.join(cache_dir, "download")
|
download_cache_dir = os.path.join(cache_dir, "download")
|
||||||
leftover_files = ignore_filelist(os.listdir(download_cache_dir))
|
leftover_files = ignore_filelist(os.listdir(download_cache_dir))
|
||||||
for cached in leftover_files:
|
for cached in leftover_files:
|
||||||
to_delete = os.path.join(download_cache_dir, cached)
|
to_delete = os.path.join(download_cache_dir, cached)
|
||||||
os.remove(to_delete)
|
os.remove(to_delete)
|
||||||
|
|
||||||
return len(leftover_files)
|
return len(leftover_files)
|
||||||
|
|
||||||
|
|
||||||
def get_mapping() -> dict:
|
def get_mapping() -> dict:
|
||||||
"""read index_mapping.json and get expected mapping and settings"""
|
"""read index_mapping.json and get expected mapping and settings"""
|
||||||
with open("appsettings/index_mapping.json", "r", encoding="utf-8") as f:
|
with open("appsettings/index_mapping.json", "r", encoding="utf-8") as f:
|
||||||
index_config: dict = json.load(f).get("index_config")
|
index_config: dict = json.load(f).get("index_config")
|
||||||
|
|
||||||
return index_config
|
return index_config
|
||||||
|
|
||||||
|
|
||||||
def is_shorts(youtube_id: str) -> bool:
|
def is_shorts(youtube_id: str) -> bool:
|
||||||
"""check if youtube_id is a shorts video, bot not it it's not a shorts"""
|
"""check if youtube_id is a shorts video, bot not it it's not a shorts"""
|
||||||
shorts_url = f"https://www.youtube.com/shorts/{youtube_id}"
|
shorts_url = f"https://www.youtube.com/shorts/{youtube_id}"
|
||||||
cookies = {"SOCS": "CAI"}
|
cookies = {"SOCS": "CAI"}
|
||||||
try:
|
try:
|
||||||
response = requests.head(
|
response = requests.head(
|
||||||
shorts_url, cookies=cookies, headers=requests_headers(), timeout=10
|
shorts_url, cookies=cookies, headers=requests_headers(), timeout=10
|
||||||
)
|
)
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException:
|
||||||
# assume video on error
|
# assume video on error
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return response.status_code == 200
|
return response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def get_duration_sec(file_path: str) -> int:
|
def get_duration_sec(file_path: str) -> int:
|
||||||
"""get duration of media file from file path"""
|
"""get duration of media file from file path"""
|
||||||
|
|
||||||
duration = subprocess.run(
|
duration = subprocess.run(
|
||||||
[
|
[
|
||||||
"ffprobe",
|
"ffprobe",
|
||||||
"-v",
|
"-v",
|
||||||
"error",
|
"error",
|
||||||
"-show_entries",
|
"-show_entries",
|
||||||
"format=duration",
|
"format=duration",
|
||||||
"-of",
|
"-of",
|
||||||
"default=noprint_wrappers=1:nokey=1",
|
"default=noprint_wrappers=1:nokey=1",
|
||||||
file_path,
|
file_path,
|
||||||
],
|
],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
check=True,
|
check=True,
|
||||||
)
|
)
|
||||||
duration_raw = duration.stdout.decode().strip()
|
duration_raw = duration.stdout.decode().strip()
|
||||||
if duration_raw == "N/A":
|
if duration_raw == "N/A":
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
duration_sec = int(float(duration_raw))
|
duration_sec = int(float(duration_raw))
|
||||||
return duration_sec
|
return duration_sec
|
||||||
|
|
||||||
|
|
||||||
def get_duration_str(seconds: int | float) -> str:
|
def get_duration_str(seconds: int | float) -> str:
|
||||||
"""Return a human-readable duration string from seconds."""
|
"""Return a human-readable duration string from seconds."""
|
||||||
if not seconds:
|
if not seconds:
|
||||||
return "NA"
|
return "NA"
|
||||||
|
|
||||||
seconds = int(seconds)
|
seconds = int(seconds)
|
||||||
units = [("y", 31536000), ("d", 86400), ("h", 3600), ("m", 60), ("s", 1)]
|
units = [("y", 31536000), ("d", 86400), ("h", 3600), ("m", 60), ("s", 1)]
|
||||||
duration_parts = []
|
duration_parts = []
|
||||||
|
|
||||||
for unit_label, unit_seconds in units:
|
for unit_label, unit_seconds in units:
|
||||||
if seconds >= unit_seconds:
|
if seconds >= unit_seconds:
|
||||||
unit_count, seconds = divmod(seconds, unit_seconds)
|
unit_count, seconds = divmod(seconds, unit_seconds)
|
||||||
duration_parts.append(f"{unit_count:02}{unit_label}")
|
duration_parts.append(f"{unit_count:02}{unit_label}")
|
||||||
|
|
||||||
duration_parts[0] = duration_parts[0].lstrip("0")
|
duration_parts[0] = duration_parts[0].lstrip("0")
|
||||||
|
|
||||||
return " ".join(duration_parts)
|
return " ".join(duration_parts)
|
||||||
|
|
||||||
|
|
||||||
def ta_host_parser(ta_host: str) -> tuple[list[str], list[str]]:
|
def ta_host_parser(ta_host: str) -> tuple[list[str], list[str]]:
|
||||||
"""parse ta_host env var for ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS"""
|
"""parse ta_host env var for ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS"""
|
||||||
allowed_hosts: list[str] = [
|
allowed_hosts: list[str] = [
|
||||||
"localhost",
|
"localhost",
|
||||||
"tubearchivist",
|
"tubearchivist",
|
||||||
]
|
]
|
||||||
csrf_trusted_origins: list[str] = [
|
csrf_trusted_origins: list[str] = [
|
||||||
"http://localhost",
|
"http://localhost",
|
||||||
"http://tubearchivist",
|
"http://tubearchivist",
|
||||||
]
|
]
|
||||||
for host in ta_host.split():
|
for host in ta_host.split():
|
||||||
host_clean = host.strip()
|
host_clean = host.strip()
|
||||||
if not host_clean.startswith("http"):
|
if not host_clean.startswith("http"):
|
||||||
host_clean = f"http://{host_clean}"
|
host_clean = f"http://{host_clean}"
|
||||||
|
|
||||||
parsed = urlparse(host_clean)
|
parsed = urlparse(host_clean)
|
||||||
allowed_hosts.append(f"{parsed.hostname}")
|
allowed_hosts.append(f"{parsed.hostname}")
|
||||||
cors_url = f"{parsed.scheme}://{parsed.hostname}"
|
cors_url = f"{parsed.scheme}://{parsed.hostname}"
|
||||||
|
|
||||||
if parsed.port:
|
if parsed.port:
|
||||||
cors_url = f"{cors_url}:{parsed.port}"
|
cors_url = f"{cors_url}:{parsed.port}"
|
||||||
|
|
||||||
csrf_trusted_origins.append(cors_url)
|
csrf_trusted_origins.append(cors_url)
|
||||||
|
|
||||||
return allowed_hosts, csrf_trusted_origins
|
return allowed_hosts, csrf_trusted_origins
|
||||||
|
|
||||||
|
|
||||||
def get_stylesheets() -> list:
|
def get_stylesheets() -> list:
|
||||||
"""Get all valid stylesheets from /static/css"""
|
"""Get all valid stylesheets from /static/css"""
|
||||||
|
|
||||||
stylesheets = [
|
stylesheets = [
|
||||||
"dark.css",
|
"dark.css",
|
||||||
"light.css",
|
"light.css",
|
||||||
"matrix.css",
|
"matrix.css",
|
||||||
"midnight.css",
|
"midnight.css",
|
||||||
"custom.css",
|
"custom.css",
|
||||||
]
|
]
|
||||||
return stylesheets
|
return stylesheets
|
||||||
|
|
||||||
|
|
||||||
def check_stylesheet(stylesheet: str):
|
def check_stylesheet(stylesheet: str):
|
||||||
"""Check if a stylesheet exists. Return dark.css as a fallback"""
|
"""Check if a stylesheet exists. Return dark.css as a fallback"""
|
||||||
if stylesheet in get_stylesheets():
|
if stylesheet in get_stylesheets():
|
||||||
return stylesheet
|
return stylesheet
|
||||||
|
|
||||||
return "dark.css"
|
return "dark.css"
|
||||||
|
|
||||||
|
|
||||||
def is_missing(
|
def is_missing(
|
||||||
to_check: str | list[str],
|
to_check: str | list[str],
|
||||||
index_name: str = "ta_video,ta_download",
|
index_name: str = "ta_video,ta_download",
|
||||||
on_key: str = "youtube_id",
|
on_key: str = "youtube_id",
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
"""id or list of ids that are missing from index_name"""
|
"""id or list of ids that are missing from index_name"""
|
||||||
if isinstance(to_check, str):
|
if isinstance(to_check, str):
|
||||||
to_check = [to_check]
|
to_check = [to_check]
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"query": {"terms": {on_key: to_check}},
|
"query": {"terms": {on_key: to_check}},
|
||||||
"_source": [on_key],
|
"_source": [on_key],
|
||||||
}
|
}
|
||||||
result = IndexPaginate(index_name, data=data).get_results()
|
result = IndexPaginate(index_name, data=data).get_results()
|
||||||
existing_ids = [i[on_key] for i in result]
|
existing_ids = [i[on_key] for i in result]
|
||||||
dl = [i for i in to_check if i not in existing_ids]
|
dl = [i for i in to_check if i not in existing_ids]
|
||||||
|
|
||||||
return dl
|
return dl
|
||||||
|
|
||||||
|
|
||||||
def get_channel_overwrites() -> dict[str, dict[str, Any]]:
|
def get_channel_overwrites() -> dict[str, dict[str, Any]]:
|
||||||
"""get overwrites indexed my channel_id"""
|
"""get overwrites indexed my channel_id"""
|
||||||
data = {
|
data = {
|
||||||
"query": {
|
"query": {
|
||||||
"bool": {"must": [{"exists": {"field": "channel_overwrites"}}]}
|
"bool": {"must": [{"exists": {"field": "channel_overwrites"}}]}
|
||||||
},
|
},
|
||||||
"_source": ["channel_id", "channel_overwrites"],
|
"_source": ["channel_id", "channel_overwrites"],
|
||||||
}
|
}
|
||||||
result = IndexPaginate("ta_channel", data).get_results()
|
result = IndexPaginate("ta_channel", data).get_results()
|
||||||
overwrites = {i["channel_id"]: i["channel_overwrites"] for i in result}
|
overwrites = {i["channel_id"]: i["channel_overwrites"] for i in result}
|
||||||
|
|
||||||
return overwrites
|
return overwrites
|
||||||
|
|
||||||
|
|
||||||
def get_channels(
|
def get_channels(
|
||||||
subscribed_only: bool, source: list[str] | None = None
|
subscribed_only: bool, source: list[str] | None = None
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
"""get a list of all channels"""
|
"""get a list of all channels"""
|
||||||
data = {
|
data = {
|
||||||
"sort": [{"channel_name.keyword": {"order": "asc"}}],
|
"sort": [{"channel_name.keyword": {"order": "asc"}}],
|
||||||
}
|
}
|
||||||
if subscribed_only:
|
if subscribed_only:
|
||||||
query = {"term": {"channel_subscribed": {"value": True}}}
|
query = {"term": {"channel_subscribed": {"value": True}}}
|
||||||
else:
|
else:
|
||||||
query = {"match_all": {}}
|
query = {"match_all": {}}
|
||||||
|
|
||||||
data["query"] = query # type: ignore
|
data["query"] = query # type: ignore
|
||||||
|
|
||||||
if source:
|
if source:
|
||||||
data["_source"] = source # type: ignore
|
data["_source"] = source # type: ignore
|
||||||
|
|
||||||
all_channels = IndexPaginate("ta_channel", data).get_results()
|
all_channels = IndexPaginate("ta_channel", data).get_results()
|
||||||
|
|
||||||
return all_channels
|
return all_channels
|
||||||
|
|
||||||
|
|
||||||
def get_playlists(
|
def get_playlists(
|
||||||
subscribed_only: bool, source: list[str] | None = None
|
subscribed_only: bool, source: list[str] | None = None
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
"""get list of playlists"""
|
"""get list of playlists"""
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"sort": [{"playlist_channel.keyword": {"order": "desc"}}],
|
"sort": [{"playlist_channel.keyword": {"order": "desc"}}],
|
||||||
}
|
}
|
||||||
|
|
||||||
must_list = [{"term": {"playlist_active": {"value": True}}}]
|
must_list = [{"term": {"playlist_active": {"value": True}}}]
|
||||||
if subscribed_only:
|
if subscribed_only:
|
||||||
must_list.append({"term": {"playlist_subscribed": {"value": True}}})
|
must_list.append({"term": {"playlist_subscribed": {"value": True}}})
|
||||||
|
|
||||||
data = {"query": {"bool": {"must": must_list}}} # type: ignore
|
data = {"query": {"bool": {"must": must_list}}} # type: ignore
|
||||||
if source:
|
if source:
|
||||||
data["_source"] = source # type: ignore
|
data["_source"] = source # type: ignore
|
||||||
|
|
||||||
all_playlists = IndexPaginate("ta_playlist", data).get_results()
|
all_playlists = IndexPaginate("ta_playlist", data).get_results()
|
||||||
|
|
||||||
return all_playlists
|
return all_playlists
|
||||||
|
|
||||||
|
|
||||||
def calc_is_watched(duration: float, position: float) -> bool:
|
def calc_is_watched(duration: float, position: float) -> bool:
|
||||||
"""considered watched based on duration position"""
|
"""considered watched based on duration position"""
|
||||||
|
|
||||||
if not duration or duration <= 0:
|
if not duration or duration <= 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if duration < 60:
|
if duration < 60:
|
||||||
threshold = 0.5
|
threshold = 0.5
|
||||||
elif duration > 900:
|
elif duration > 900:
|
||||||
threshold = 1 - (180 / duration)
|
threshold = 1 - (180 / duration)
|
||||||
else:
|
else:
|
||||||
threshold = 0.9
|
threshold = 0.9
|
||||||
|
|
||||||
return position >= duration * threshold
|
return position >= duration * threshold
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
<svg version="1.0" width="960pt" height="960pt" viewBox="0 0 960 960" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">
|
<svg version="1.0" width="960pt" height="960pt" viewBox="0 0 960 960" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g transform="translate(0,960)scale(.075,.075)">
|
<g transform="translate(0,960)scale(.075,.075)">
|
||||||
<path id="path1" d="M 740 -10481 c -77 24 -152 94 -170 160 -6 23 -10 376 -10 946 l 0 910 24 50 c 28 59 66 97 121 122 38 17 268 18 5620 21 3923 1 5595 -1 5632 -8 70 -15 124 -56 157 -121 l 27 -54 0 -920 c 0 -637 -4 -932 -11 -958 -17 -58 -84 -122 -150 -141 -49 -15 -544 -16 -5635 -15 -3069 0 -5591 4 -5605 8 z M 9615 -7400 c -11 4 -31 20 -45 35 l -25 27 -3 889 c -3 978 -5 935 57 972 27 16 117 17 1253 17 1321 0 1252 3 1293 -54 20 -27 20 -43 20 -919 0 -860 -1 -893 -19 -920 -41 -60 37 -57 -1293 -56 -670 0 -1227 4 -1238 9 z M 3658 -7384 c -61 32 -58 -10 -58 959 0 973 -3 928 60 960 25 13 178 15 1250 15 1072 0 1225 -2 1250 -15 63 -32 60 13 60 -960 0 -973 3 -928 -60 -960 -25 -13 -178 -15 -1252 -15 -1062 1 -1227 3 -1250 16 z M 6551 -7374 c -17 14 -35 42 -41 62 -6 24 -10 339 -10 892 0 826 1 856 20 898 35 78 -60 73 1315 70 l 1225 -3 32 -33 33 -32 3 -895 c 2 -788 0 -899 -13 -925 -33 -64 48 -60 -1304 -60 l -1229 0 -31 26 z M 616 -7369 c -59 46 -56 0 -56 951 0 976 -4 924 70 960 33 17 113 18 1250 18 1187 0 1216 -1 1247 -20 66 -40 63 12 63 -955 0 -791 -2 -880 -16 -911 -33 -68 54 -64 -1302 -64 l -1229 0 -27 21 z M 9615 -4530 c -11 4 -31 20 -45 35 l -25 27 -3 901 c -2 891 -2 902 18 935 40 65 -32 62 1296 62 1335 0 1255 4 1292 -64 16 -29 17 -101 17 -921 0 -832 -1 -892 -18 -922 -36 -67 50 -63 -1294 -62 -670 0 -1227 4 -1238 9 z M 3664 -4514 c -68 33 -64 -26 -64 973 l 0 901 34 38 34 37 1242 0 1242 0 34 -37 34 -38 0 -900 c 0 -989 3 -942 -60 -975 -25 -13 -178 -15 -1247 -15 -1064 0 -1222 2 -1249 16 z M 6551 -4504 c -17 14 -35 42 -41 62 -14 51 -14 1733 0 1784 6 20 24 48 41 62 l 31 26 1237 0 c 1360 0 1263 5 1296 -60 23 -44 23 -1796 0 -1840 -33 -64 47 -60 -1304 -60 l -1229 0 -31 26 z M 615 -4498 c -57 44 -55 13 -55 963 0 844 1 877 19 913 11 21 31 43 46 50 20 9 323 12 1260 12 l 1233 0 31 -30 c 17 -17 33 -45 36 -63 3 -18 4 -429 3 -915 l -3 -884 -37 -34 -38 -34 -1234 0 -1233 0 -28 22 z " />
|
<path id="path1" d="M 740 -10481 c -77 24 -152 94 -170 160 -6 23 -10 376 -10 946 l 0 910 24 50 c 28 59 66 97 121 122 38 17 268 18 5620 21 3923 1 5595 -1 5632 -8 70 -15 124 -56 157 -121 l 27 -54 0 -920 c 0 -637 -4 -932 -11 -958 -17 -58 -84 -122 -150 -141 -49 -15 -544 -16 -5635 -15 -3069 0 -5591 4 -5605 8 z M 9615 -7400 c -11 4 -31 20 -45 35 l -25 27 -3 889 c -3 978 -5 935 57 972 27 16 117 17 1253 17 1321 0 1252 3 1293 -54 20 -27 20 -43 20 -919 0 -860 -1 -893 -19 -920 -41 -60 37 -57 -1293 -56 -670 0 -1227 4 -1238 9 z M 3658 -7384 c -61 32 -58 -10 -58 959 0 973 -3 928 60 960 25 13 178 15 1250 15 1072 0 1225 -2 1250 -15 63 -32 60 13 60 -960 0 -973 3 -928 -60 -960 -25 -13 -178 -15 -1252 -15 -1062 1 -1227 3 -1250 16 z M 6551 -7374 c -17 14 -35 42 -41 62 -6 24 -10 339 -10 892 0 826 1 856 20 898 35 78 -60 73 1315 70 l 1225 -3 32 -33 33 -32 3 -895 c 2 -788 0 -899 -13 -925 -33 -64 48 -60 -1304 -60 l -1229 0 -31 26 z M 616 -7369 c -59 46 -56 0 -56 951 0 976 -4 924 70 960 33 17 113 18 1250 18 1187 0 1216 -1 1247 -20 66 -40 63 12 63 -955 0 -791 -2 -880 -16 -911 -33 -68 54 -64 -1302 -64 l -1229 0 -27 21 z M 9615 -4530 c -11 4 -31 20 -45 35 l -25 27 -3 901 c -2 891 -2 902 18 935 40 65 -32 62 1296 62 1335 0 1255 4 1292 -64 16 -29 17 -101 17 -921 0 -832 -1 -892 -18 -922 -36 -67 50 -63 -1294 -62 -670 0 -1227 4 -1238 9 z M 3664 -4514 c -68 33 -64 -26 -64 973 l 0 901 34 38 34 37 1242 0 1242 0 34 -37 34 -38 0 -900 c 0 -989 3 -942 -60 -975 -25 -13 -178 -15 -1247 -15 -1064 0 -1222 2 -1249 16 z M 6551 -4504 c -17 14 -35 42 -41 62 -14 51 -14 1733 0 1784 6 20 24 48 41 62 l 31 26 1237 0 c 1360 0 1263 5 1296 -60 23 -44 23 -1796 0 -1840 -33 -64 47 -60 -1304 -60 l -1229 0 -31 26 z M 615 -4498 c -57 44 -55 13 -55 963 0 844 1 877 19 913 11 21 31 43 46 50 20 9 323 12 1260 12 l 1233 0 31 -30 c 17 -17 33 -45 36 -63 3 -18 4 -429 3 -915 l -3 -884 -37 -34 -38 -34 -1234 0 -1233 0 -28 22 z " />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
Loading…
Reference in New Issue