Complete seven reviewed original ship HD remasters
This commit is contained in:
1 parent
11505721c3
commit
126d28ba49
963 files changed
+1771020
-34
No files matched your search
@@ -0,0 +1,398 @@
|
||||
"""Restore ALL SHIP09G2 Source-cyan owners using genuine bounded cyan only.
|
||||
|
||||
The protected noncyan hull is the literal existing G2 native4 palette projection.
|
||||
Source supplies every cyan owner/support (wake, halo, interior paint) and limits,
|
||||
never replacement RGB. No gains, synthetic grain, new generation or cascading
|
||||
fills. Reproduction is not independent artwork or publication approval.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path, PurePosixPath
|
||||
import stat
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image, __version__ as PILLOW_VERSION
|
||||
|
||||
METHOD = 'derived-SHIP09G2-complete2-all-Source-cyan-noncyan-hull-v2'
|
||||
DECODER_SHA = '66184a4758027dadbadb5e20fe90b2add2b9fc218b7810304b1a1ea2de74f58e'
|
||||
G2_HELPER_SHA = '0338a925ccf26f856c9d5cc2230a3878ecafe6cd3a3d25903742d9e819ac8d12'
|
||||
G2_LIBRARY_SHA = '5b6bbd03874da298dae953dc1c0f472d24f27031df4f46616368faefb0801676'
|
||||
ASSETS = ['2009f', '200a0']
|
||||
MAX_BYTES = 32 * 1024 * 1024
|
||||
DISTANCE_CAP = 5.5
|
||||
MISSING_CAP = .90
|
||||
AGGREGATE_CAP = .85
|
||||
COLOR_TOLERANCE = 4
|
||||
MIN_DONORS = 128
|
||||
MIN_DONOR_COLORS = 32
|
||||
|
||||
|
||||
def sha(payload):
|
||||
return hashlib.sha256(payload).hexdigest()
|
||||
|
||||
|
||||
def keys(value, fields, label):
|
||||
if type(value) is not dict or set(value) != set(fields):
|
||||
raise ValueError(f'{label}: exact fields required')
|
||||
|
||||
|
||||
def hash_string(value):
|
||||
return type(value) is str and len(value) == 64 and all(c in '0123456789abcdef' for c in value)
|
||||
|
||||
|
||||
def regular_bytes(path):
|
||||
"""Bounded nonblocking no-follow regular-descriptor read, consumed once."""
|
||||
path = Path(path)
|
||||
if any(p.is_symlink() for p in [path, *path.parents]):
|
||||
raise ValueError('symlink input path')
|
||||
before = path.lstat()
|
||||
if not stat.S_ISREG(before.st_mode) or before.st_size > MAX_BYTES:
|
||||
raise ValueError('bounded regular input required')
|
||||
flags = os.O_RDONLY | getattr(os, 'O_NOFOLLOW', 0) | getattr(os, 'O_NONBLOCK', 0)
|
||||
with os.fdopen(os.open(path, flags), 'rb') as stream:
|
||||
opened = os.fstat(stream.fileno())
|
||||
if not stat.S_ISREG(opened.st_mode) or opened.st_size > MAX_BYTES:
|
||||
raise ValueError('bounded regular input required')
|
||||
data = stream.read(MAX_BYTES + 1)
|
||||
after = os.fstat(stream.fileno())
|
||||
def identity(v):
|
||||
return v.st_dev, v.st_ino, v.st_size, v.st_mtime_ns, v.st_mode
|
||||
if (len(data) > MAX_BYTES or identity(before) != identity(opened)
|
||||
or identity(opened) != identity(after) or identity(path.lstat()) != identity(before)):
|
||||
raise ValueError('input changed during bounded read')
|
||||
return data
|
||||
|
||||
|
||||
def checked(binding, root):
|
||||
keys(binding, {'path', 'sha256'}, 'binding')
|
||||
name = binding['path']
|
||||
if type(name) is not str or not name or '\\' in name or ':' in name:
|
||||
raise ValueError('portable relative path required')
|
||||
path = PurePosixPath(name)
|
||||
if path.is_absolute() or any(p in ('', '.', '..') for p in name.split('/')):
|
||||
raise ValueError('unsafe relative path')
|
||||
if not hash_string(binding['sha256']):
|
||||
raise ValueError('sha256 required')
|
||||
data = regular_bytes(Path(root).joinpath(*path.parts))
|
||||
if sha(data) != binding['sha256']:
|
||||
raise ValueError('input hash mismatch: ' + name)
|
||||
return data
|
||||
|
||||
|
||||
def verified_decoder(project):
|
||||
data = checked(dict(path='tools/hd_frame_policy.py', sha256=DECODER_SHA), project)
|
||||
scope = dict(__name__='_verified_source_decoder', __file__='<verified-source-decoder-buffer>')
|
||||
exec(compile(data, '<verified-source-decoder-buffer>', 'exec'), scope)
|
||||
return scope['_indices']
|
||||
|
||||
|
||||
def scale2(source):
|
||||
b = np.concatenate([source[:1], source[:-1]], axis=0)
|
||||
d = np.concatenate([source[:, :1], source[:, :-1]], axis=1)
|
||||
f = np.concatenate([source[:, 1:], source[:, -1:]], axis=1)
|
||||
g = np.concatenate([source[1:], source[-1:]], axis=0)
|
||||
condition = (b != g) & (d != f)
|
||||
out = np.empty((source.shape[0]*2, source.shape[1]*2), np.uint16)
|
||||
out[0::2, 0::2] = np.where(condition & (d == b), d, source)
|
||||
out[0::2, 1::2] = np.where(condition & (b == f), f, source)
|
||||
out[1::2, 0::2] = np.where(condition & (d == g), d, source)
|
||||
out[1::2, 1::2] = np.where(condition & (g == f), f, source)
|
||||
return out
|
||||
|
||||
|
||||
def water(source):
|
||||
return (source >= 144) & (source <= 159)
|
||||
|
||||
|
||||
def hull_guard(source):
|
||||
"""Source noncyan row envelope plus exactly one logical-cell halo."""
|
||||
solid = (source < 256) & ~water(source)
|
||||
core = np.zeros(source.shape, bool)
|
||||
for y in range(40):
|
||||
xs = np.where(solid[y])[0]
|
||||
if len(xs):
|
||||
core[y, xs.min():xs.max()+1] = True
|
||||
if not core.any():
|
||||
raise ValueError('missing Source hull')
|
||||
guard = core.copy()
|
||||
yy, xx = np.where(core)
|
||||
for dy in (-1, 0, 1):
|
||||
for dx in (-1, 0, 1):
|
||||
guard[np.clip(yy+dy, 0, 39), np.clip(xx+dx, 0, 71)] = True
|
||||
return guard
|
||||
|
||||
|
||||
def source_caps(sources, palette):
|
||||
colors = np.concatenate([palette[s[water(s)]] for s in sources]).astype(np.int32)
|
||||
if not len(colors):
|
||||
raise ValueError('missing authentic Source cyan')
|
||||
chroma = colors[:, 2]-colors[:, 0]
|
||||
if np.any(chroma <= 0) or np.any(colors[:, 1] <= colors[:, 0]) or np.any(colors[:, 1] > colors[:, 2]):
|
||||
raise ValueError('Source cyan hue domain required')
|
||||
fractions = [(int(c[1]-c[0]), int(c[2]-c[0])) for c in colors]
|
||||
minimum = min(fractions, key=lambda q:q[0]/q[1])
|
||||
maximum = max(fractions, key=lambda q:q[0]/q[1])
|
||||
light = colors @ np.array([54, 183, 19], np.int32)
|
||||
return dict(rgb_min=np.maximum(0, colors.min(axis=0)-COLOR_TOLERANCE),
|
||||
rgb_max=np.minimum(255, colors.max(axis=0)+COLOR_TOLERANCE),
|
||||
hue_min_fraction=list(minimum), hue_max_fraction=list(maximum),
|
||||
chroma_min=max(1, int(chroma.min())-COLOR_TOLERANCE),
|
||||
luminance_min=int(light.min())-COLOR_TOLERANCE*256,
|
||||
luminance_max=int(light.max())+COLOR_TOLERANCE*256)
|
||||
|
||||
|
||||
def cyan_donors(rgb, caps):
|
||||
c = rgb.astype(np.int32)
|
||||
green = c[:, :, 1]-c[:, :, 0]
|
||||
blue = c[:, :, 2]-c[:, :, 0]
|
||||
low, high = caps['hue_min_fraction'], caps['hue_max_fraction']
|
||||
light = c @ np.array([54, 183, 19], np.int32)
|
||||
return (np.all((c >= caps['rgb_min']) & (c <= caps['rgb_max']), axis=2)
|
||||
& (blue >= caps['chroma_min']) & (green > 0) & (green <= blue)
|
||||
& (green*low[1] >= blue*low[0])
|
||||
& (green*high[1] <= blue*high[0])
|
||||
& (light >= caps['luminance_min']) & (light <= caps['luminance_max']))
|
||||
|
||||
|
||||
def index_array(value, width, height, label):
|
||||
if (type(value) is not dict or type(value.get('width')) is not int or type(value.get('height')) is not int
|
||||
or value.get('width') != width or value.get('height') != height
|
||||
or type(value.get('indices')) is not list or len(value['indices']) != width*height
|
||||
or any(type(v) is not int or not 0 <= v <= 256 for v in value['indices'])):
|
||||
raise ValueError(label + ': invalid indexed canvas')
|
||||
return np.array(value['indices'], np.uint16).reshape(height, width)
|
||||
|
||||
|
||||
def render_frame(source, palette, raw, crop, baseline_indices, caps):
|
||||
"""Pure buffers; same-phase raw donors only, never derived output donors."""
|
||||
source4 = scale2(scale2(source))
|
||||
guard4 = (source4 < 256) & ~water(source4)
|
||||
wake4 = water(source4)
|
||||
if not wake4.any():
|
||||
raise ValueError('missing Source cyan owners')
|
||||
lut = np.zeros((257, 4), np.uint8)
|
||||
lut[:256, :3] = palette
|
||||
lut[:256, 3] = 255
|
||||
baseline = lut[baseline_indices]
|
||||
# All noncyan Source structural owners retain exact prior G2 RGBA.
|
||||
# Source-cyan owners ALL receive eligible genuine RGB, including interior
|
||||
# paint and the old halo. Source-transparent owners are never fabricated.
|
||||
result = np.zeros(baseline.shape, np.uint8)
|
||||
result[guard4] = baseline[guard4]
|
||||
l, t, w, h = crop
|
||||
sx, sy = w/72, h/40
|
||||
region = raw[t:t+h, l:l+w]
|
||||
eligible = ((region[:, :, 3] >= 128)
|
||||
& cyan_donors(region[:, :, :3], caps))
|
||||
donor_count = int(eligible.sum())
|
||||
donor_colors = len(np.unique(region[:, :, :3][eligible], axis=0))
|
||||
if donor_count < MIN_DONORS or donor_colors < MIN_DONOR_COLORS:
|
||||
raise ValueError('insufficient genuine same-phase cyan texture')
|
||||
out_y, out_x = np.where(wake4)
|
||||
u = ((out_x+.5)/4)*sx
|
||||
v = ((out_y+.5)/4)*sy
|
||||
px, py = np.floor(u).astype(int), np.floor(v).astype(int)
|
||||
direct = eligible[py, px]
|
||||
fraction = float(np.mean(~direct))
|
||||
if fraction > MISSING_CAP + 1e-12:
|
||||
raise ValueError('fixed missing fraction exceeded')
|
||||
maximum = 0.0
|
||||
for point in np.where(~direct)[0]:
|
||||
x0 = max(0, int(math.floor(u[point]-DISTANCE_CAP*sx-1)))
|
||||
x1 = min(w, int(math.ceil(u[point]+DISTANCE_CAP*sx+1)))
|
||||
y0 = max(0, int(math.floor(v[point]-DISTANCE_CAP*sy-1)))
|
||||
y1 = min(h, int(math.ceil(v[point]+DISTANCE_CAP*sy+1)))
|
||||
dy, dx = np.where(eligible[y0:y1, x0:x1])
|
||||
if not len(dx):
|
||||
raise ValueError('no bounded same-phase cyan donor')
|
||||
dx, dy = dx+x0, dy+y0
|
||||
distance2 = ((dx+.5-u[point])/sx)**2 + ((dy+.5-v[point])/sy)**2
|
||||
nearest = int(np.argmin(distance2)) # row-major y/x tie order is fixed
|
||||
distance = math.sqrt(float(distance2[nearest]))
|
||||
if distance > DISTANCE_CAP + 1e-12:
|
||||
raise ValueError('fixed donor distance exceeded')
|
||||
px[point], py[point] = dx[nearest], dy[nearest]
|
||||
maximum = max(maximum, distance)
|
||||
chosen = region[py, px, :3]
|
||||
result[out_y, out_x, :3] = chosen
|
||||
result[out_y, out_x, 3] = 255
|
||||
if (not np.array_equal(result[guard4], baseline[guard4])
|
||||
or not np.array_equal(result[:, :, 3][~guard4], np.where(wake4, 255, 0)[~guard4])):
|
||||
raise ValueError('noncyan hull or Source-cyan support changed')
|
||||
return result, dict(samples=int(len(out_x)), repaired=int((~direct).sum()),
|
||||
missing_fraction=fraction, maximum_donor_logical_distance=maximum,
|
||||
raw_donors=donor_count, raw_donor_RGB_colors=donor_colors,
|
||||
used_donor_RGB_colors=len(np.unique(chosen, axis=0)),
|
||||
noncyan_structural_hull_RGBA_sha256=sha(result[guard4].tobytes()),
|
||||
noncyan_structural_hull_exact_to_existing_G2_projection=True,
|
||||
all_Source_cyan_alpha_exact_to_SourceScale4=True,
|
||||
Original_RGB_samples_painted=0,
|
||||
Source_cyan_RGB_outside_original_raw_donor_tuples=0)
|
||||
|
||||
|
||||
def run_recipe(recipe_path, output, project_root=None):
|
||||
recipe_path, output = Path(recipe_path).absolute(), Path(output).absolute()
|
||||
if output.exists() or any(p.is_symlink() for p in [output, *output.parents]):
|
||||
raise ValueError('fresh nonsymlink output required')
|
||||
if not output.parent.is_dir():
|
||||
raise ValueError('existing output parent required')
|
||||
own_sha = sha(regular_bytes(Path(__file__)))
|
||||
rb = regular_bytes(recipe_path)
|
||||
recipe = json.loads(rb)
|
||||
keys(recipe, {'format_version', 'method', 'raw_png', 'palette', 'candidate',
|
||||
'runtime_qa', 'indexed_bank', 'actual_invocation', 'frames'}, 'recipe')
|
||||
if type(recipe['format_version']) is not int or recipe['format_version'] != 1 or recipe['method'] != METHOD:
|
||||
raise ValueError('fixed method/version required')
|
||||
frames = recipe['frames']
|
||||
if type(frames) is not list or len(frames) != 2 or [f.get('asset') for f in frames if type(f) is dict] != ASSETS:
|
||||
raise ValueError('complete ordered SHIP09G2 pair required')
|
||||
project = Path(project_root) if project_root else Path(__file__).resolve().parents[1]
|
||||
decode = verified_decoder(project)
|
||||
raw_payload = checked(recipe['raw_png'], recipe_path.parent)
|
||||
with Image.open(io.BytesIO(raw_payload)) as im:
|
||||
if im.format != 'PNG' or im.mode != 'RGBA' or im.size != (1536, 1024):
|
||||
raise ValueError('exact1536x1024 RGBA raw atlas required')
|
||||
raw = np.asarray(im).copy()
|
||||
pb = checked(recipe['palette'], recipe_path.parent)
|
||||
if len(pb) != 768 or max(pb) > 63:
|
||||
raise ValueError('genuine VGA palette required')
|
||||
p6 = np.frombuffer(pb, np.uint8).reshape(256, 3).astype(np.uint16)
|
||||
palette = ((p6 << 2) | (p6 >> 4)).astype(np.uint8)
|
||||
candidate_bytes = checked(recipe['candidate'], recipe_path.parent)
|
||||
candidate = json.loads(candidate_bytes)
|
||||
seqs = candidate.get('sequences')
|
||||
if (type(seqs) is not list or len(seqs) != 1 or seqs[0].get('base_asset') != 0x2009f
|
||||
or type(seqs[0]['base_asset']) is not int or seqs[0].get('frame_count') != 2
|
||||
or type(seqs[0].get('frame_count')) is not int
|
||||
or candidate.get('complete_family') is not True):
|
||||
raise ValueError('literal complete candidate required')
|
||||
cf = seqs[0].get('frames')
|
||||
if type(cf) is not list or len(cf) != 2:
|
||||
raise ValueError('candidate complete2 frame order required')
|
||||
qa = json.loads(checked(recipe['runtime_qa'], recipe_path.parent))
|
||||
rows = {asset: f for asset, f in zip(ASSETS, cf)}
|
||||
binding = sha(json.dumps(rows, sort_keys=True, separators=(',', ':')).encode())
|
||||
files = {f.get('file') for f in cf}
|
||||
if (len(files) != 1 or any(type(f) is not str for f in files)
|
||||
or qa.get('candidate_sha256') != sha(candidate_bytes)
|
||||
or qa.get('frame_bindings_sha256') != binding
|
||||
or type(qa.get('validated_frames')) is not int or qa.get('validated_frames') != 2 or qa.get('diagnostics') != []
|
||||
or qa.get('actual_sourceG2_helper_sha256') != G2_HELPER_SHA
|
||||
or qa.get('library_sha256') != G2_LIBRARY_SHA
|
||||
or qa.get('source_newer_than_library') is not False
|
||||
or qa.get('any_game_source_newer_than_library') is not False
|
||||
or qa.get('artwork_sha256') != {next(iter(files)): sha(raw_payload)}):
|
||||
raise ValueError('literal currentG2 QA/candidate/raw binding mismatch')
|
||||
invocation = json.loads(checked(recipe['actual_invocation'], recipe_path.parent))
|
||||
if (type(invocation.get('exit_code')) is not int or invocation.get('exit_code') != 0
|
||||
or invocation.get('runtime_QA_sha256') != recipe['runtime_qa']['sha256']
|
||||
or invocation.get('indexed_bank_sha256') != recipe['indexed_bank']['sha256']):
|
||||
raise ValueError('actual invocation does not bind literal QA/indexed bank')
|
||||
bank = json.loads(checked(recipe['indexed_bank'], recipe_path.parent))
|
||||
if type(bank) is not list or len(bank) != 2 or [r.get('asset') for r in bank if type(r) is dict] != ASSETS:
|
||||
raise ValueError('complete ordered actual bank required')
|
||||
qr = qa.get('frames')
|
||||
if type(qr) is not list or len(qr) != 2 or [r.get('asset') for r in qr if type(r) is dict] != ASSETS:
|
||||
raise ValueError('complete ordered QA Source frames required')
|
||||
sources, baselines, crops = [], [], []
|
||||
for i, (frame, c, row) in enumerate(zip(frames, cf, bank)):
|
||||
keys(frame, {'asset', 'source', 'crop'}, 'frame')
|
||||
keys(c, {'frame', 'file', 'logical_width', 'logical_height', 'origin', 'source_sha256', 'crop'}, 'literal frame')
|
||||
crop = frame['crop']
|
||||
name = c['file']
|
||||
if (type(name) is not str or not name or '\\' in name or ':' in name
|
||||
or PurePosixPath(name).is_absolute() or any(p in ('', '.', '..') for p in name.split('/'))):
|
||||
raise ValueError('portable literal artwork path required')
|
||||
if (type(crop) is not list or len(crop) != 4 or any(type(v) is not int for v in crop)
|
||||
or not 0 <= crop[0] < crop[0]+crop[2] <= 1536
|
||||
or not 0 <= crop[1] < crop[1]+crop[3] <= 1024
|
||||
or crop[2]*40 != crop[3]*72 or crop[2] < 288
|
||||
or c['frame'] != i or type(c['frame']) is not int or c['crop'] != crop
|
||||
or type(c['logical_width']) is not int or type(c['logical_height']) is not int
|
||||
or type(c['origin']) is not list or any(type(v) is not int for v in c['origin'])
|
||||
or c['logical_width'] != 72 or c['logical_height'] != 40 or c['origin'] != [0, 0]
|
||||
or c['source_sha256'] != frame['source']['sha256']):
|
||||
raise ValueError('exact canvas/crop/source binding required')
|
||||
if (qr[i].get('source_sha256') != frame['source']['sha256'] or qr[i].get('logical_canvas') != [72, 40]):
|
||||
raise ValueError('QA Source identity differs')
|
||||
excluded = raw[:, i*768:(i+1)*768, 3].copy()
|
||||
x, y, cw, ch = crop
|
||||
excluded[y:y+ch, max(x, i*768)-i*768:min(x+cw, (i+1)*768)-i*768] = 0
|
||||
if np.any(excluded >= 128):
|
||||
raise ValueError('crop clips meaningful own-phase alpha')
|
||||
if i == 1 and x < 768 and np.any(raw[y:y+ch, x:768, 3] >= 128):
|
||||
raise ValueError('crop includes foreign phase alpha')
|
||||
if not (i*768 <= crop[0] < crop[0]+crop[2] <= (i+1)*768):
|
||||
# The authentic selected second crop starts5px before midpoint;
|
||||
# neither crop may include the OTHER phase's alpha128 content.
|
||||
if i != 1 or not 760 <= crop[0] < 768:
|
||||
raise ValueError('crop escapes its own phase cell')
|
||||
w, h, idx = decode(checked(frame['source'], recipe_path.parent))
|
||||
if (w, h) != (72, 40) or any(v != 256 and not 0 <= v < 240 for v in idx):
|
||||
raise ValueError('original72x40 ordinary Source required')
|
||||
source = np.array(idx, np.uint16).reshape(40, 72)
|
||||
if not np.array_equal(index_array(row.get('source'), 72, 40, 'bank Source'), source):
|
||||
raise ValueError('bank Source differs from authenticated Source')
|
||||
scales = row.get('scales')
|
||||
if type(scales) is not list or len(scales) != 4 or [s.get('scale') for s in scales if type(s) is dict] != [1, 2, 3, 4]:
|
||||
raise ValueError('ordered actual native1..4 bank required')
|
||||
for k, s in enumerate(scales, 1):
|
||||
if type(s.get('scale')) is not int:
|
||||
raise ValueError('integer native scale required')
|
||||
indexed = index_array(s, 72*k, 40*k, 'bank scale')
|
||||
if np.any((indexed >= 240) & (indexed < 256)):
|
||||
raise ValueError('ordinary Source bank gained reserved labels')
|
||||
sources.append(source); baselines.append(indexed); crops.append(crop)
|
||||
a = np.where(water(sources[0]), 256, sources[0])
|
||||
b = np.where(water(sources[1]), 256, sources[1])
|
||||
if not np.array_equal(a, b):
|
||||
raise ValueError('authentic shared hull material differs between phases')
|
||||
if not np.array_equal(hull_guard(sources[0]), hull_guard(sources[1])):
|
||||
raise ValueError('shared hull envelope differs')
|
||||
caps = source_caps(sources, palette)
|
||||
payloads, reports = [], []
|
||||
for frame, source, baseline, crop in zip(frames, sources, baselines, crops):
|
||||
rgba, report = render_frame(source, palette, raw, crop, baseline, caps)
|
||||
stream = io.BytesIO();Image.fromarray(rgba).save(stream, format='PNG', compress_level=9)
|
||||
payloads.append((frame['asset'], stream.getvalue()))
|
||||
reports.append(dict(asset=frame['asset'], RGBA_sha256=sha(rgba.tobytes()), **report))
|
||||
if sum(r['repaired'] for r in reports)/sum(r['samples'] for r in reports) > AGGREGATE_CAP + 1e-12:
|
||||
raise ValueError('complete2 aggregate repair cap exceeded')
|
||||
if sha(regular_bytes(Path(__file__))) != own_sha:
|
||||
raise ValueError('executing helper changed')
|
||||
receipt = dict(execution=dict(status='success', method=METHOD, recipe_sha256=sha(rb),
|
||||
tool_sha256=own_sha, source_sha256={f['asset']: f['source']['sha256'] for f in frames},
|
||||
parent_raw_sha256=[sha(raw_payload)], output_sha256={a: sha(p) for a, p in payloads}),
|
||||
decoder_sha256=DECODER_SHA, candidate_sha256=sha(candidate_bytes),
|
||||
literal_QA_sha256=recipe['runtime_qa']['sha256'], indexed_bank_sha256=recipe['indexed_bank']['sha256'],
|
||||
caps={k:v.tolist() if isinstance(v, np.ndarray) else v for k,v in caps.items()}, reports=reports,
|
||||
Python=sys.version.split()[0], NumPy=np.__version__, Pillow=PILLOW_VERSION,
|
||||
limits=['Every noncyan structural Source4 owner keeps exact existing G2 native4 palette RGBA of genuine raw, not an unquantized raw RGB identity claim.',
|
||||
'All Source-cyan support/partition and Source-derived caps get zero generated-HD credit; every restored cyan RGB tuple comes unchanged from this phase raw.',
|
||||
'Final alpha is exactly Source4cyan support plus baseline alpha at noncyan Source4 owners; Source4transparent owners are clear. Whole Original-alpha identity is not promised. Fresh G2allscale review required.',
|
||||
'No generator invocation, grain or RGB gain; numerical reproduction does not confer independent art approval.'])
|
||||
payloads.append(('execution-receipt', (json.dumps(receipt, indent=2, sort_keys=True)+'\n').encode()))
|
||||
output.mkdir() # exclusive; every input/frame/cap already validated in memory
|
||||
for name, payload in payloads:
|
||||
suffix = '.json' if name == 'execution-receipt' else '.png'
|
||||
with (output/(name+suffix)).open('xb') as stream:
|
||||
stream.write(payload)
|
||||
return receipt
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('recipe', type=Path);p.add_argument('output', type=Path)
|
||||
p.add_argument('--project-root', type=Path)
|
||||
a = p.parse_args()
|
||||
print(json.dumps(run_recipe(a.recipe, a.output, a.project_root)['execution'], sort_keys=True))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user