"""Generic identities, validators, and limits for authenticated Bilibili jobs."""
|
|
from __future__ import annotations
|
|
import hashlib
|
import re
|
|
BVID_RE = re.compile(r"BV1[1-9A-HJ-NP-Za-km-z]{9}\Z")
|
CREATOR_UID_RE = re.compile(r"[1-9][0-9]{0,19}\Z")
|
JOB_ID_RE = re.compile(r"[0-9a-f]{64}\Z")
|
MESSAGE_ID_RE = re.compile(r"msg_[0-9]{17}_[0-9a-f]{8}\Z")
|
HANDOFF_ID_RE = re.compile(r"HANDOFF-[A-Z0-9-]{8,240}\Z")
|
AUDIT_ID_RE = re.compile(r"DEV-AUDIT-[A-Z0-9-]{8,240}\Z")
|
ERROR_CODE_RE = re.compile(r"E_[A-Z0-9_]{1,48}\Z")
|
UPPER_SHA256_RE = re.compile(r"[0-9A-F]{64}\Z")
|
|
# Lineage-only marker for an already COMPLETE job whose exact published pair
|
# still needs the governed formal-manifest and processing-handoff closure. It
|
# is never a durable runtime terminal code.
|
COMPLETION_CLOSURE_REQUIRED = "E_COMPLETION_CLOSURE_REQUIRED"
|
|
|
def validate_bvid(value: object) -> str:
|
if not isinstance(value, str) or not BVID_RE.fullmatch(value):
|
raise ValueError("invalid BVID")
|
return value
|
|
|
def validate_creator_uid(value: object) -> str:
|
if not isinstance(value, str) or not CREATOR_UID_RE.fullmatch(value):
|
raise ValueError("invalid creator UID")
|
return value
|
|
|
def canonical_url(bvid: str) -> str:
|
return f"https://www.bilibili.com/video/{validate_bvid(bvid)}"
|
|
|
def target_path(bvid: str) -> str:
|
return f"/video/{validate_bvid(bvid)}"
|
|
|
def stable_job_id(creator_uid: str, bvid: str) -> str:
|
creator = validate_creator_uid(creator_uid)
|
target = validate_bvid(bvid)
|
return hashlib.sha256(f"bili-auth-job-v1\0{creator}\0{target}".encode("ascii")).hexdigest()
|
|
|
def stable_successor_job_id(
|
creator_uid: str,
|
bvid: str,
|
predecessor_job_id: str,
|
retry_generation: int,
|
predecessor_terminal_error_code: str,
|
authorization_message_id: str,
|
authorization_handoff_id: str,
|
authorization_sha256: str,
|
repair_review_result_message_id: str,
|
repair_audit_id: str,
|
repair_audit_bytes: int,
|
repair_audit_sha256: str,
|
) -> str:
|
"""Derive the exact schema-2 successor identity frozen by V001."""
|
creator = validate_creator_uid(creator_uid)
|
target = validate_bvid(bvid)
|
fields: tuple[tuple[object, re.Pattern[str] | None], ...] = (
|
(predecessor_job_id, JOB_ID_RE),
|
(retry_generation, None),
|
(predecessor_terminal_error_code, ERROR_CODE_RE),
|
(authorization_message_id, MESSAGE_ID_RE),
|
(authorization_handoff_id, HANDOFF_ID_RE),
|
(authorization_sha256, UPPER_SHA256_RE),
|
(repair_review_result_message_id, MESSAGE_ID_RE),
|
(repair_audit_id, AUDIT_ID_RE),
|
(repair_audit_bytes, None),
|
(repair_audit_sha256, UPPER_SHA256_RE),
|
)
|
for value, pattern in fields:
|
if pattern is not None and (not isinstance(value, str) or not pattern.fullmatch(value)):
|
raise ValueError("invalid successor identity")
|
if (
|
isinstance(retry_generation, bool) or not isinstance(retry_generation, int)
|
or not 1 <= retry_generation <= 1_000_000
|
or isinstance(repair_audit_bytes, bool) or not isinstance(repair_audit_bytes, int)
|
or repair_audit_bytes <= 0
|
):
|
raise ValueError("invalid successor identity")
|
payload = "\0".join((
|
"bili-auth-job-v2",
|
creator,
|
target,
|
predecessor_job_id,
|
str(retry_generation),
|
predecessor_terminal_error_code,
|
authorization_message_id,
|
authorization_handoff_id,
|
authorization_sha256,
|
repair_review_result_message_id,
|
repair_audit_id,
|
str(repair_audit_bytes),
|
repair_audit_sha256,
|
))
|
return hashlib.sha256(payload.encode("ascii")).hexdigest()
|
|
|
def duration_tolerance_ms(expected_duration_ms: int) -> int:
|
if isinstance(expected_duration_ms, bool) or not isinstance(expected_duration_ms, int):
|
raise ValueError("invalid duration")
|
if not 1_000 <= expected_duration_ms <= 86_400_000:
|
raise ValueError("invalid duration")
|
return max(1_500, min(10_000, (expected_duration_ms + 999) // 1_000))
|
|
|
SCHEMA_VERSION = 3
|
QUEUE_SCHEMA_VERSION = 1
|
SUCCESSOR_QUEUE_SCHEMA_VERSION = 2
|
HOST_NAME = "com.project_info.bili_auth_ingress"
|
EXTENSION_BUILD = "project-info-bili-auth-ingress/1.2.25+20260829.generic.v027"
|
HOST_BUILD = "project-info-bili-auth-native-host/1.2.25+20260829.generic.v027"
|
RELOAD_GENERATION = "bili-auth-generic-v027"
|
EXPECTED_EXTENSION_ID = "oidmclckpdmpabbfedplkbdplmfcenbb"
|
EXPECTED_ORIGIN = f"chrome-extension://{EXPECTED_EXTENSION_ID}/"
|
PUBLIC_KEY_DER_SHA256 = (
|
"E83C2B2AF3CF011543FBA13FBC524D1122EEA68548F9F27B9F7A82B5D594666C"
|
)
|
MANIFEST_PUBLIC_KEY = (
|
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt2dT1HGYaI0DXM7zZwOTNBWX"
|
"TKlMBJMpyDVjRUc+v6bUotmLyoraC+ay2scy9UQluSZVYq0tS8qvQNNvuZOlc5w2bOEx"
|
"m4TH2IIKvaVO8nVthHBnNz2kXdiM8ItN0vPZEmS+8gpTCI1+6wPTuUglMoXpqYBYhii8"
|
"fJ5RkENRF3PRJBBigGt8soqdBFRY1QZUmpQv9dYw4dRq4L2C4QtBgClUg4bQpuCppiVZ"
|
"9LHbePi9IAjc9r9R93KLzpaBuXdJpfVRE5w/6YHnxP8ovXxBdl7XktmrdH3xj7mWT7Q7"
|
"ZBxkDNwn2RkruD45XgDD3yuNxOYSkLFMkseN+Ua69gSS4wIDAQAB"
|
)
|
|
MAX_INPUT_FRAME = 131_072
|
MAX_OUTPUT_FRAME = 16_384
|
MAX_COOKIE_COUNT = 128
|
MAX_SAFE_INTEGER = (1 << 53) - 1
|
QUEUE_LEASE_SECONDS = 300
|
MAX_CLAIM_ATTEMPTS = 2
|
PRESTART_ABORT_CODES = frozenset({
|
"E_COOKIE_ACCESS",
|
})
|
COOKIE_ACCESS_REASONS = frozenset({
|
"COOKIE_API_ERROR",
|
"COOKIE_API_SHAPE",
|
"COOKIE_AUTH_SET_EMPTY",
|
"COOKIE_EQUAL_PRECEDENCE_CONFLICT",
|
"COOKIE_RELEVANCE_AMBIGUOUS",
|
"COOKIE_SELECTED_INVALID",
|
"COOKIE_SELECTION_EXCEPTION",
|
"COOKIE_TRANSFER_INVALID",
|
"COOKIE_TRANSFER_OVERFLOW",
|
})
|
COOKIE_ACCESS_TERMINAL_CODES = {
|
reason: f"E_{reason}" for reason in COOKIE_ACCESS_REASONS
|
}
|
RUNTIME_DIAGNOSTIC_STATES = frozenset({
|
"BRIDGE_EXIT",
|
"BRIDGE_FFPROBE",
|
"BRIDGE_INVOCATION",
|
"BRIDGE_METADATA_BINDING",
|
"BRIDGE_OUTPUT_SCHEMA",
|
"BRIDGE_SOURCE_STABILITY",
|
"CREATE_NEW_PUBLISH",
|
"DIMENSIONS_PENDING",
|
"DURATION_SHA_VERIFICATION",
|
"IDENTITY_PENDING",
|
"MAPPING_READBACK",
|
"MEDIA_MAPPING_PRESENCE",
|
"METADATA_NOT_READY",
|
"OWNER_PENDING",
|
"PAGE_REJECTED",
|
"READY_UNSTABLE",
|
"SCRIPT_TIMEOUT",
|
"TAB_IDENTITY",
|
"VIDEO_ABSENT",
|
})
|
RUNTIME_DIAGNOSTIC_REASONS = frozenset({
|
"BATCH_RECEIPT_CREATE_FAILED",
|
"BRIDGE_REPORTED_PUBLISH_FAILURE",
|
"DEADLINE_EXHAUSTED",
|
"DURATION_OR_SHA_MISMATCH",
|
"MEDIA_OR_MAPPING_MISSING",
|
"METADATA_FIELDS_PENDING",
|
"INITIAL_STATE_ASSIGNMENT_ABSENT",
|
"INITIAL_STATE_JSON_INVALID",
|
"INITIAL_STATE_VIDEO_DATA_ABSENT",
|
"NONZERO_EXIT",
|
"EXPECTED_METADATA_MISMATCH",
|
"LOCAL_MEDIA_PROBE_FAILED",
|
"OUTPUT_SCHEMA_INVALID",
|
"OWNER_ANCHOR_ABSENT",
|
"OWNER_ANCHOR_AMBIGUOUS",
|
"OWNER_ANCHOR_MISMATCH",
|
"PAGE_DRM_REJECTED",
|
"PAGE_DURATION_REJECTED",
|
"PAGE_ACCESS_REJECTED",
|
"PAGE_BVID_REJECTED",
|
"PAGE_DOM_IDENTITY_PENDING",
|
"PAGE_IDENTITY_REJECTED",
|
"PAGE_MULTIPART_REJECTED",
|
"PAGE_NAVIGATION_PENDING",
|
"PAGE_ORIGIN_REJECTED",
|
"PAGE_OWNER_REJECTED",
|
"PAGE_SCRIPTING_PENDING",
|
"PAGE_TAB_ACTIVATION_PENDING",
|
"PAGE_URL_PENDING",
|
"PAGE_URL_REJECTED",
|
"PAGE_VISIBILITY_PENDING",
|
"PAGE_WINDOW_FOCUS_PENDING",
|
"PAGE_WINDOW_MINIMIZED_PENDING",
|
"PAGE_WINDOW_STATE_PENDING",
|
"PERSISTED_MAPPING_INVALID",
|
"SOURCE_FILE_INVALID",
|
"SCRIPT_INVOCATION_FAILED",
|
"STABLE_SNAPSHOTS_PENDING",
|
"SUBPROCESS_INVOCATION_FAILED",
|
"SUBPROCESS_TIMEOUT",
|
"TAB_IDENTITY_DRIFT",
|
"VIDEO_DIMENSIONS_PENDING",
|
"VIDEO_ELEMENT_ABSENT",
|
})
|
|
PREPARELESS_TERMINAL_DIAGNOSTIC_CONTRACT = {
|
"E_DRM": {
|
"PAGE_REJECTED": frozenset({"PAGE_DRM_REJECTED"}),
|
},
|
"E_DURATION": {
|
"PAGE_REJECTED": frozenset({"PAGE_DURATION_REJECTED"}),
|
},
|
"E_MULTI_PART": {
|
"PAGE_REJECTED": frozenset({"PAGE_MULTIPART_REJECTED"}),
|
},
|
"E_OWNER": {
|
"PAGE_REJECTED": frozenset({"PAGE_IDENTITY_REJECTED", "PAGE_OWNER_REJECTED"}),
|
},
|
"E_PAGE_ACCESS_CONTROL": {
|
"PAGE_REJECTED": frozenset({"PAGE_ACCESS_REJECTED"}),
|
},
|
"E_PAGE_DUPLICATE_TAB": {
|
"TAB_IDENTITY": frozenset({"TAB_IDENTITY_DRIFT"}),
|
},
|
"E_PAGE_DIMENSIONS_UNAVAILABLE": {
|
"DIMENSIONS_PENDING": frozenset({"VIDEO_DIMENSIONS_PENDING"}),
|
},
|
"E_PAGE_METADATA_UNAVAILABLE": {
|
"METADATA_NOT_READY": frozenset({
|
"INITIAL_STATE_ASSIGNMENT_ABSENT",
|
"INITIAL_STATE_JSON_INVALID",
|
"INITIAL_STATE_VIDEO_DATA_ABSENT",
|
}),
|
},
|
"E_PAGE_IDENTITY_UNAVAILABLE": {
|
"IDENTITY_PENDING": frozenset({
|
"PAGE_DOM_IDENTITY_PENDING",
|
"PAGE_NAVIGATION_PENDING",
|
"PAGE_SCRIPTING_PENDING",
|
"PAGE_TAB_ACTIVATION_PENDING",
|
"PAGE_URL_PENDING",
|
"PAGE_VISIBILITY_PENDING",
|
"PAGE_WINDOW_FOCUS_PENDING",
|
"PAGE_WINDOW_MINIMIZED_PENDING",
|
"PAGE_WINDOW_STATE_PENDING",
|
}),
|
},
|
"E_PAGE_OWNER_ANCHOR_UNAVAILABLE": {
|
"OWNER_PENDING": frozenset({
|
"OWNER_ANCHOR_ABSENT",
|
"OWNER_ANCHOR_AMBIGUOUS",
|
"OWNER_ANCHOR_MISMATCH",
|
}),
|
},
|
"E_PAGE_PROOF": {
|
"PAGE_REJECTED": frozenset({
|
"PAGE_BVID_REJECTED",
|
"PAGE_IDENTITY_REJECTED",
|
"PAGE_ORIGIN_REJECTED",
|
"PAGE_URL_REJECTED",
|
}),
|
},
|
"E_PAGE_SCRIPT_TIMEOUT": {
|
"SCRIPT_TIMEOUT": frozenset({"SCRIPT_INVOCATION_FAILED"}),
|
},
|
"E_PAGE_STABILITY_TIMEOUT": {
|
"READY_UNSTABLE": frozenset({"STABLE_SNAPSHOTS_PENDING"}),
|
},
|
"E_PAGE_TAB_DRIFT": {
|
"TAB_IDENTITY": frozenset({"TAB_IDENTITY_DRIFT"}),
|
},
|
"E_PAGE_VIDEO_ABSENT": {
|
"VIDEO_ABSENT": frozenset({"VIDEO_ELEMENT_ABSENT"}),
|
},
|
}
|
PREPARELESS_REJECT_CODES = frozenset(PREPARELESS_TERMINAL_DIAGNOSTIC_CONTRACT)
|
INTERNAL_ONLY_PAGE_PENDING_CODES = frozenset({"E_PAGE_METADATA_NOT_READY"})
|
|
# One bounded compatibility contract for the immutable pre-V017 journal. The
|
# legacy code remains forbidden on every live write/wire boundary; only these
|
# exact raw lines inside the exact governed prefix may be decoded during
|
# append-only state replay.
|
LEGACY_PAGE_METADATA_REPLAY_STATE_SUFFIX = (
|
"project-info", "bili-auth-generic-runtime", "queue-state.jsonl",
|
)
|
LEGACY_PAGE_METADATA_REPLAY_PREFIX_BYTES = 27_899
|
LEGACY_PAGE_METADATA_REPLAY_PREFIX_SHA256 = (
|
"43CB0D7994AC349038026D202DBC85B897E8BF322A3B0EEFD536997FFCBE32DB"
|
)
|
LEGACY_PAGE_METADATA_REPLAY_LINES = (
|
(68, 307, "4F0AF7297B700E045C66E91812C470335447833A9248898C7A746521D6DB688D"),
|
(70, 307, "10B4B3A77CA2F11D5DA2E2A1503006182C9B2EA616E7E265AC504B88329179C8"),
|
(75, 415, "C3CA763D02CE4A45E23131759CE4F2B8449885975E0848AABBD0C5D290076C76"),
|
(80, 415, "F62B30976883F027D1F9AF4BA883BE7DAA9E055328DB345E73B71CFA98249CE1"),
|
(82, 415, "B889763C7B9C65140CA8403B26628A58E8F9BF0FA58FFE7D64637017CF15A2DF"),
|
(86, 415, "AE93EDC05478CCC0699ACA31BA957FE9537A067E9409F71FF88768F08561E5BA"),
|
(88, 415, "A4D748BEF18B15A75A65DA7F9D88D40772421CEA4041AC604579D40EF822DFC5"),
|
(93, 415, "DA83626D0C7D45D29CD55A5556F8214E71C2781D52A91FACCCB695EF37AF11A1"),
|
)
|
LEGACY_MISSING_DIAGNOSTIC_REPLAY_LINES = (
|
(2, 294, "474B56CD9D57ABCD11F663D1FECE38087CE5CC6F18A4C02837F867548EF42242", "E_PAGE_PROOF"),
|
(4, 294, "C9C38D1559A519661E9C00EC2B2C8DD56073F117726CB477343283860D52324F", "E_PAGE_PROOF"),
|
(6, 294, "A4DB42C3223611CD14B4EF25F4A028964DBABF93903BA65931398166E1936424", "E_PAGE_PROOF"),
|
(14, 294, "F69E2BFF4A2EF51AA614E5C510FE7E9A965B47BCC97ED671E572D62AD2FB875C", "E_PAGE_PROOF"),
|
(16, 294, "40D03BF0F736B8207A61DFFAF807E75BDED2F645F8CED9C586B2C5257AD86E43", "E_PAGE_PROOF"),
|
(22, 294, "8D07285C1364D147DFD4060CB3B6E6BCADD057D686F64C5BC3D2A7AD097635EE", "E_PAGE_PROOF"),
|
(24, 294, "42D3A599A00D37F5C65B2DC86CEFDC2E971C32FFFA163C7C2407D5E0CA95E20D", "E_PAGE_PROOF"),
|
(26, 298, "1477BA661C0F060149D6725D2E44D1F1A419714C9350AD266FFDF6344C02EEAF", "E_PAGE_TAB_DRIFT"),
|
(30, 298, "DA135304B6F29B4A3937C9DF9C8E6861C179F71675414ED49931EEF461E2890E", "E_PAGE_TAB_DRIFT"),
|
)
|
|
|
def validate_runtime_diagnostic(value: object) -> dict[str, object]:
|
"""Validate the only durable, non-secret runtime diagnostic shape."""
|
|
if not isinstance(value, dict) or set(value) != {"attempts", "elapsed_ms", "state", "reason"}:
|
raise ValueError("invalid runtime diagnostic")
|
attempts = value["attempts"]
|
elapsed_ms = value["elapsed_ms"]
|
if (
|
isinstance(attempts, bool) or not isinstance(attempts, int) or not 0 <= attempts <= 10_000
|
or isinstance(elapsed_ms, bool) or not isinstance(elapsed_ms, int)
|
or not 0 <= elapsed_ms <= 7_200_000
|
or value["state"] not in RUNTIME_DIAGNOSTIC_STATES
|
or value["reason"] not in RUNTIME_DIAGNOSTIC_REASONS
|
):
|
raise ValueError("invalid runtime diagnostic")
|
return value
|
|
|
POSTPROCESS_TERMINAL_DIAGNOSTIC_CONTRACT = {
|
"E_BRIDGE_OUTPUT_SCHEMA": {
|
"BRIDGE_OUTPUT_SCHEMA": frozenset({"OUTPUT_SCHEMA_INVALID"}),
|
},
|
}
|
|
|
def validate_postprocess_terminal(
|
error_code: object, diagnostic: object,
|
) -> dict[str, object] | None:
|
"""Validate the canonical diagnostic matrix for a post-media terminal."""
|
|
if not isinstance(error_code, str) or ERROR_CODE_RE.fullmatch(error_code) is None:
|
raise ValueError("invalid postprocess terminal")
|
if (
|
error_code == COMPLETION_CLOSURE_REQUIRED
|
or error_code in INTERNAL_ONLY_PAGE_PENDING_CODES
|
or error_code in PREPARELESS_REJECT_CODES
|
):
|
raise ValueError("invalid postprocess terminal")
|
contract = POSTPROCESS_TERMINAL_DIAGNOSTIC_CONTRACT.get(error_code)
|
if diagnostic is None:
|
if contract is not None:
|
raise ValueError("invalid postprocess terminal")
|
return None
|
if contract is None:
|
raise ValueError("invalid postprocess terminal")
|
validated = validate_runtime_diagnostic(diagnostic)
|
reasons = contract.get(validated["state"])
|
if reasons is None or validated["reason"] not in reasons:
|
raise ValueError("invalid postprocess terminal")
|
return validated
|
|
|
def validate_prepareless_terminal(
|
error_code: object, diagnostic: object,
|
) -> dict[str, object]:
|
"""Validate one canonical durable pre-secret terminal and its typed diagnostic."""
|
|
if not isinstance(error_code, str):
|
raise ValueError("invalid prepareless terminal")
|
contract = PREPARELESS_TERMINAL_DIAGNOSTIC_CONTRACT.get(error_code)
|
if contract is None:
|
raise ValueError("invalid prepareless terminal")
|
validated = validate_runtime_diagnostic(diagnostic)
|
allowed_reasons = contract.get(validated["state"])
|
if allowed_reasons is None or validated["reason"] not in allowed_reasons:
|
raise ValueError("invalid prepareless terminal")
|
return validated
|
RELOAD_BACKOFF_SECONDS = 3_600
|
|
SOCKET_TIMEOUT_SECONDS = 20
|
EXTRACTOR_RETRIES = 1
|
HTTP_RETRIES = 2
|
FRAGMENT_RETRIES = 2
|
FILE_ACCESS_RETRIES = 1
|
METADATA_TIMEOUT_SECONDS = 120
|
DOWNLOAD_TIMEOUT_SECONDS = 7_200
|
BRIDGE_TIMEOUT_SECONDS = 900
|
GRACEFUL_CANCEL_SECONDS = 3
|
JOB_WAIT_SECONDS = 5
|
THREAD_JOIN_SECONDS = 5
|
|
YTDLP_VERSION = "2026.07.04"
|
YTDLP_MODULE_SHA256 = {
|
"version.py": "3848D30C4990355374A1198AD6DAAAB212CD3EF2C92BEA32FA970D913B5EB79F",
|
"__init__.py": "AA2C9CF5A6F2E1D97EFB6EE13DDCE83CACCE4BEF7D6BDC15E5B5F53AF90BB902",
|
"YoutubeDL.py": "41E83B95FCE4A59F09746D0804D80CCFA8489C62C81D2CDFA20CE0E835611A17",
|
"plugins.py": "106991D3275A6A136CA46AE0B334CD5F8F988C77BDDD638E85CC3581FE8F652B",
|
"globals.py": "CD5AFD8A68685DC5C7A02B55D1502B4F9FBE1069A33B571F3B120ABB8C42CD23",
|
"cookies.py": "BAF3B9AF355B436D4DA1C03D88A93349D88833E6D452B255DD4DB93D953B2D21",
|
"extractor/common.py": "539BB835D6FBFBD32BCC2B4E743F2E99E61B8860DC1962B0D714A9DAEC569352",
|
"extractor/bilibili.py": "B99AD995A016B11097CDADFB64BC6DA55CD64129B76E7914EAFBC7E45685EC33",
|
"downloader/__init__.py": "0342E193CB315D6B89384645501761C47A0C5A70D289FE60FF43D67D591E17F7",
|
"downloader/common.py": "EE2E5CD7959FF3C19247E4CED7579037963E1A372875BC67A097B887A60D453B",
|
"downloader/http.py": "E111FFFDC894F68253CE3C5155FA1AA7390E58CAA9A33EEB28D9EF78134E2CD0",
|
"downloader/dash.py": "92B6C7C0944AF2620A1F9581DD6E1EF177059519B480581B990047B53F0AE4A9",
|
"downloader/hls.py": "EFE86E1183B652C1B96254462A79C31924551E5ABC990BBC765C43E307FA460A",
|
"downloader/external.py": "3577991E61D3E4055EB534AF7B9B7AE00E2A2D308A3944FE8CAF0C2E4391E11C",
|
"networking/__init__.py": "B3CA5D8084AEDA1349AF610F532D492225B3990245EB4DB88D084ECA2667CB4F",
|
"networking/common.py": "2647275C8A8C49DC61E36B371BE1D1794D639C015CA73BD9837D710045FF7BF9",
|
"networking/_helper.py": "F7C064184C9CBFBEB3C22BF41218004B30E602D1686599FBAA82FEF1E0C60799",
|
"networking/_urllib.py": "3454CE495D524833D8D978B86B5437B8C390E89B31E5E153CAF2D57C8DF557DE",
|
"utils/_utils.py": "9200BE7E89F04B51243FE20643BC0FAC16186A91FF6B588456E95BC2670F7EB8",
|
"postprocessor/__init__.py": "3960A70BCBE4E01B5E08020156B9F0316AF5D98EB3CB53E20479849C067DB505",
|
"postprocessor/common.py": "4C6BF888FED859FFFF3E649215686BBEC873670DFECA5F14AF3A21D1DA12CBEB",
|
"postprocessor/ffmpeg.py": "3FB12E0FDA8271D33EC3284F42D87A0141A1C3415B9B78B2790B12D536A670F5",
|
}
|
|
SAFE_PHASES = {
|
"IDLE", "READY", "CHECKING", "DOWNLOADING", "MERGING", "VALIDATING",
|
"PUBLISHING", "MEDIA_COMPLETE", "POSTPROCESS_PENDING", "POSTPROCESS_FAILED",
|
"COMPLETE", "FAILED", "CANCELED", "RELOAD_REQUIRED",
|
}
|