Migrate to Pages: public/meme assets, gallery and scripts; remove root meme/; tooling defaults

Made-with: Cursor
This commit is contained in:
2026-04-08 10:01:44 +08:00
parent 8f111823a4
commit eff9cea804
5535 changed files with 8883 additions and 10075 deletions

212
animated_webp_to_gif.py Normal file
View File

@@ -0,0 +1,212 @@
#!/usr/bin/env python3
"""
Recursively find *.webp under 原图/ (by default). Animated WebP -> same-stem .gif
(then remove the .webp). Static WebP unchanged. Default 10 worker threads.
"""
from __future__ import annotations
import argparse
import io
import re
import sys
import tempfile
import threading
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
try:
from PIL import Image, ImageSequence
except ImportError as exc: # pragma: no cover
raise SystemExit(
"This script requires Pillow. Install it with: python3 -m pip install pillow"
) from exc
# 只处理已编号的成品动图(避免误处理未编号的素材)
OUTPUT_NAME_RE = re.compile(r"^(\d+)\.webp$", re.IGNORECASE)
DEFAULT_JOBS = 10
_log_lock = threading.Lock()
def _log(*args, file=sys.stdout, **kwargs) -> None:
with _log_lock:
print(*args, file=file, **kwargs)
def convert_one(path: Path, root: Path, dry_run: bool) -> str:
try:
if not is_animated_webp(path):
return "skip_static"
except OSError as e:
_log(f"[skip] {path}: {e}", file=sys.stderr)
return "error"
gif_path = path.with_suffix(".gif")
rel = path.relative_to(root)
if dry_run:
_log(f"would convert: {rel} -> {gif_path.name}")
return "ok"
try:
webp_to_gif(path, gif_path)
path.unlink()
_log(f"{rel} -> {gif_path.name} (animated)")
return "ok"
except Exception as e:
_log(f"[error] {path}: {e}", file=sys.stderr)
if gif_path.exists():
try:
gif_path.unlink()
except OSError:
pass
return "error"
def is_animated_webp_image(im: Image.Image) -> bool:
if im.format != "WEBP":
return False
return bool(
getattr(im, "is_animated", False) or getattr(im, "n_frames", 1) > 1
)
def is_animated_webp(path: Path) -> bool:
with Image.open(path) as im:
return is_animated_webp_image(im)
def save_animated_webp_image_to_gif(im: Image.Image, dst: Path) -> None:
if im.format != "WEBP":
raise ValueError("expected WebP image")
loop = im.info.get("loop", 0)
frames: list[Image.Image] = []
durations: list[int] = []
for frame in ImageSequence.Iterator(im):
rgba = frame.convert("RGBA")
frames.append(rgba)
d = frame.info.get(
"duration", im.info.get("duration", im.info.get("gif_duration", 100))
)
try:
durations.append(int(d) if d is not None else 100)
except (TypeError, ValueError):
durations.append(100)
if len(frames) == 0:
raise ValueError("no frames")
if len(durations) < len(frames):
durations.extend([durations[-1]] * (len(frames) - len(durations)))
dst.parent.mkdir(parents=True, exist_ok=True)
with tempfile.NamedTemporaryFile(
suffix=".gif", delete=False, dir=dst.parent
) as tmp:
tmp_path = Path(tmp.name)
try:
if len(frames) == 1:
frames[0].save(
tmp_path,
format="GIF",
duration=durations[0],
loop=loop,
)
else:
frames[0].save(
tmp_path,
format="GIF",
save_all=True,
append_images=frames[1:],
duration=durations,
loop=loop,
disposal=2,
)
tmp_path.replace(dst)
finally:
if tmp_path.exists():
try:
tmp_path.unlink()
except OSError:
pass
def webp_bytes_to_gif(data: bytes, dst: Path) -> None:
with Image.open(io.BytesIO(data)) as im:
save_animated_webp_image_to_gif(im, dst)
def webp_to_gif(src: Path, dst: Path) -> None:
with Image.open(src) as im:
save_animated_webp_image_to_gif(im, dst)
def main(argv: list[str]) -> int:
parser = argparse.ArgumentParser(
description=(
"Convert animated WebP under 原图/ to GIF (in place); "
"static WebP unchanged. Only numbered N.webp files are considered."
)
)
parser.add_argument(
"root",
nargs="?",
default="原图",
type=Path,
help="Root directory to scan (default: 原图).",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Only print what would be converted.",
)
parser.add_argument(
"--all-webp",
action="store_true",
help="Process any *.webp, not only numbered N.webp.",
)
parser.add_argument(
"-j",
"--jobs",
type=int,
default=DEFAULT_JOBS,
metavar="N",
help=f"Parallel worker threads (default: {DEFAULT_JOBS}).",
)
args = parser.parse_args(argv)
root = args.root.expanduser().resolve()
if not root.is_dir():
print(f"Not a directory: {root}", file=sys.stderr)
return 1
webp_files = sorted(root.rglob("*.webp"))
converted = 0
skipped_static = 0
skipped_name = 0
errors = 0
candidates: list[Path] = []
for path in webp_files:
if not args.all_webp and not OUTPUT_NAME_RE.match(path.name):
skipped_name += 1
continue
candidates.append(path)
workers = max(1, args.jobs)
with ThreadPoolExecutor(max_workers=workers) as pool:
for outcome in pool.map(
lambda p: convert_one(p, root, args.dry_run), candidates
):
if outcome == "ok":
converted += 1
elif outcome == "skip_static":
skipped_static += 1
else:
errors += 1
print(
f"Done. converted={converted}, skipped_static_webp={skipped_static}, "
f"skipped_unnumbered={skipped_name}, errors={errors} ({workers} workers)"
)
return 0 if errors == 0 else 1
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import argparse import argparse
import io import io
import os import re
import sys import sys
import zipfile import zipfile
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
@@ -18,6 +18,7 @@ except ImportError as exc: # pragma: no cover
"This script requires Pillow. Install it with: python3 -m pip install pillow" "This script requires Pillow. Install it with: python3 -m pip install pillow"
) from exc ) from exc
from animated_webp_to_gif import webp_bytes_to_gif
IMAGE_EXTENSIONS = { IMAGE_EXTENSIONS = {
".avif", ".avif",
@@ -30,20 +31,46 @@ IMAGE_EXTENSIONS = {
".png", ".png",
".tif", ".tif",
".tiff", ".tiff",
".webp",
} }
# 已编号的成品1.webp、42.gif不参与转换用于各子文件夹内续号
OUTPUT_NAME_RE = re.compile(r"^(\d+)\.(webp|gif)$", re.IGNORECASE)
DEFAULT_SOURCE_DIR = Path("原图")
@dataclass(frozen=True) @dataclass(frozen=True)
class Job: class Job:
source_label: str source_label: str
target_path: Path target_dir: Path
index: int
loader: Callable[[], bytes] loader: Callable[[], bytes]
# 磁盘上的源文件转换成功后删除zip 成员为 None。与输出路径相同时不会删。
source_file: Path | None = None
def is_image_path(path: Path) -> bool: def is_image_path(path: Path) -> bool:
return path.is_file() and path.suffix.lower() in IMAGE_EXTENSIONS return path.is_file() and path.suffix.lower() in IMAGE_EXTENSIONS
def is_numbered_asset_file(path: Path) -> bool:
return path.is_file() and bool(OUTPUT_NAME_RE.match(path.name))
def max_index_in_directory(directory: Path) -> int:
highest = 0
if not directory.is_dir():
return 0
for p in directory.iterdir():
if not p.is_file():
continue
m = OUTPUT_NAME_RE.match(p.name)
if m:
highest = max(highest, int(m.group(1)))
return highest
def convert_to_webp(data: bytes) -> tuple[bytes, bool]: def convert_to_webp(data: bytes) -> tuple[bytes, bool]:
with Image.open(io.BytesIO(data)) as im: with Image.open(io.BytesIO(data)) as im:
is_animated = bool( is_animated = bool(
@@ -76,137 +103,237 @@ def convert_to_webp(data: bytes) -> tuple[bytes, bool]:
return output.getvalue(), False return output.getvalue(), False
def is_animated_gif(data: bytes) -> bool:
with Image.open(io.BytesIO(data)) as im:
if im.format != "GIF":
return False
n_frames = getattr(im, "n_frames", 1)
return bool(getattr(im, "is_animated", False) or n_frames > 1)
def is_animated_webp_bytes(data: bytes) -> bool:
with Image.open(io.BytesIO(data)) as im:
if im.format != "WEBP":
return False
return bool(
getattr(im, "is_animated", False) or getattr(im, "n_frames", 1) > 1
)
def _read_zip_member(zip_path: Path, member_name: str) -> bytes: def _read_zip_member(zip_path: Path, member_name: str) -> bytes:
with zipfile.ZipFile(zip_path) as zf: with zipfile.ZipFile(zip_path) as zf:
return zf.read(member_name) return zf.read(member_name)
def collect_directory_jobs(root: Path, output_base: Path) -> list[Job]: def collect_directory_jobs(
source_root: Path, output_root: Path, *, delete_sources: bool
) -> list[Job]:
grouped: dict[Path, list[Path]] = {} grouped: dict[Path, list[Path]] = {}
for path in sorted(root.rglob("*")): for path in sorted(source_root.rglob("*")):
if not is_image_path(path): if not is_image_path(path):
continue continue
if is_numbered_asset_file(path):
continue
grouped.setdefault(path.parent, []).append(path) grouped.setdefault(path.parent, []).append(path)
jobs: list[Job] = [] jobs: list[Job] = []
for directory in sorted(grouped): for directory in sorted(grouped, key=lambda p: str(p)):
files = sorted(grouped[directory], key=lambda p: p.name) files = sorted(grouped[directory], key=lambda p: p.name)
relative_dir = directory.relative_to(root) target_dir = output_root / directory.relative_to(source_root)
target_dir = output_base / relative_dir next_i = max_index_in_directory(target_dir) + 1
for index, path in enumerate(files, start=1): for path in files:
jobs.append( jobs.append(
Job( Job(
source_label=path.relative_to(root).as_posix(), source_label=path.relative_to(source_root).as_posix(),
target_path=target_dir / f"{index}.webp", target_dir=target_dir,
index=next_i,
loader=lambda p=path: p.read_bytes(), loader=lambda p=path: p.read_bytes(),
source_file=path if delete_sources else None,
) )
) )
next_i += 1
return jobs return jobs
def collect_zip_jobs(zip_path: Path, output_base: Path) -> list[Job]: def collect_zip_jobs(zip_path: Path, output_root: Path) -> list[Job]:
grouped: dict[PurePosixPath, list[PurePosixPath]] = {} grouped: dict[PurePosixPath, list[PurePosixPath]] = {}
with zipfile.ZipFile(zip_path) as zf: with zipfile.ZipFile(zip_path) as zf:
for info in zf.infolist(): for info in zf.infolist():
if info.is_dir(): if info.is_dir():
continue continue
member_path = PurePosixPath(info.filename) member_path = PurePosixPath(info.filename)
if member_path.suffix.lower() not in IMAGE_EXTENSIONS: suf = member_path.suffix.lower()
if suf not in IMAGE_EXTENSIONS:
continue
if OUTPUT_NAME_RE.match(member_path.name):
continue continue
grouped.setdefault(member_path.parent, []).append(member_path) grouped.setdefault(member_path.parent, []).append(member_path)
jobs: list[Job] = [] jobs: list[Job] = []
for directory in sorted(grouped, key=lambda p: str(p)): for directory in sorted(grouped, key=lambda p: str(p)):
files = sorted(grouped[directory], key=lambda p: str(p.name)) files = sorted(grouped[directory], key=lambda p: str(p.name))
for index, member_path in enumerate(files, start=1): target_dir = output_root / Path(str(directory))
target_dir = output_base / Path(str(directory)) next_i = max_index_in_directory(target_dir) + 1
for member_path in files:
jobs.append( jobs.append(
Job( Job(
source_label=member_path.as_posix(), source_label=member_path.as_posix(),
target_path=target_dir / f"{index}.webp", target_dir=target_dir,
index=next_i,
loader=lambda name=member_path.as_posix(), zp=zip_path: _read_zip_member( loader=lambda name=member_path.as_posix(), zp=zip_path: _read_zip_member(
zp, name zp, name
), ),
) )
) )
next_i += 1
return jobs return jobs
def collect_jobs(source: Path, output_base: Path | None) -> list[Job]: def _remove_source_if_any(job: Job, out: Path, kind: str) -> str:
if source.is_dir(): if job.source_file is None:
base = output_base if output_base is not None else source.parent / f"{source.name}-out" return kind
base.mkdir(parents=True, exist_ok=True) try:
return collect_directory_jobs(source, base) src = job.source_file.resolve()
if src == out.resolve():
if source.is_file() and source.suffix.lower() == ".zip": return kind
base = output_base if output_base is not None else source.parent / source.stem src.unlink()
base.mkdir(parents=True, exist_ok=True) return f"{kind}; removed-source"
return collect_zip_jobs(source, base) except OSError as exc:
print(
if is_image_path(source): f"[warn] ok: {out} ({kind}) but could not delete {job.source_file}: {exc}",
return [ file=sys.stderr,
Job( )
source_label=source.name, return kind
target_path=source.parent / "1.webp",
loader=lambda p=source: p.read_bytes(),
)
]
return []
def run_job(job: Job) -> tuple[bool, str, Path, bool | None, str | None]: def run_job(job: Job) -> tuple[bool, str, Path, str | None, str | None]:
try: try:
data = job.loader() data = job.loader()
job.target_dir.mkdir(parents=True, exist_ok=True)
if is_animated_gif(data):
out = job.target_dir / f"{job.index}.gif"
out.write_bytes(data)
kind = _remove_source_if_any(job, out, "gif-animated")
return True, job.source_label, out, kind, None
if is_animated_webp_bytes(data):
out = job.target_dir / f"{job.index}.gif"
webp_bytes_to_gif(data, out)
kind = _remove_source_if_any(job, out, "animated-webp->gif")
return True, job.source_label, out, kind, None
webp_data, animated = convert_to_webp(data) webp_data, animated = convert_to_webp(data)
job.target_path.parent.mkdir(parents=True, exist_ok=True) out = job.target_dir / f"{job.index}.webp"
job.target_path.write_bytes(webp_data) out.write_bytes(webp_data)
return True, job.source_label, job.target_path, animated, None base = "animated-webp" if animated else "static-webp"
kind = _remove_source_if_any(job, out, base)
return True, job.source_label, out, kind, None
except Exception as exc: except Exception as exc:
return False, job.source_label, job.target_path, None, str(exc) return False, job.source_label, job.target_dir / f"{job.index}.?", None, str(exc)
def main(argv: list[str]) -> int: def main(argv: list[str]) -> int:
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Convert images to WebP in place, with independent numbering per folder." description=(
"Read unnumbered images from 原图/ (by default), write numbered *.webp / *.gif "
"into the same folder tree (in-place). Subfolders mirror 原图. "
"Animated GIFs and animated WebPs become .gif; static images become lossless .webp. "
"Next index per folder = max existing N in that folder + 1. "
"By default unnumbered source files are deleted after a successful conversion."
)
) )
parser.add_argument( parser.add_argument(
"source", "source",
nargs="?", nargs="?",
default=".", default=None,
help="Source folder, image file, or .zip archive. Default: current directory.", type=Path,
help=f"Source root folder, single image, or .zip (default: {DEFAULT_SOURCE_DIR.as_posix()}/).",
) )
parser.add_argument( parser.add_argument(
"--output-base", "-o",
"--output",
default=None, default=None,
help="Output root. Default for folders: <source>-out next to the source. Default for zip: a folder next to the zip with the same name.", dest="output_root",
type=Path,
help="Output root (default: same as source for folders/single file; for .zip default: <zip-stem>/).",
) )
parser.add_argument( parser.add_argument(
"-j", "-j",
"--jobs", "--jobs",
type=int, type=int,
default=max(1, min(32, os.cpu_count() or 4)), default=10,
help="Number of worker threads. Default: number of CPU cores.", help="Number of worker threads (default: 10).",
)
parser.add_argument(
"--keep-sources",
action="store_true",
help="Keep unnumbered source files after conversion (default: delete them).",
) )
args = parser.parse_args(argv) args = parser.parse_args(argv)
source = Path(args.source).expanduser().resolve() cwd = Path.cwd()
output_base = Path(args.output_base).expanduser().resolve() if args.output_base else None source = (
args.source.expanduser().resolve()
if args.source is not None
else (cwd / DEFAULT_SOURCE_DIR).resolve()
)
jobs: list[Job] = []
jobs = collect_jobs(source, output_base) if source.is_dir():
jobs.sort(key=lambda job: str(job.target_path)) out_root = (
args.output_root.expanduser().resolve()
if args.output_root is not None
else source
)
out_root.mkdir(parents=True, exist_ok=True)
jobs = collect_directory_jobs(
source, out_root, delete_sources=not args.keep_sources
)
elif source.is_file() and source.suffix.lower() == ".zip":
out_root = (
args.output_root.expanduser().resolve()
if args.output_root is not None
else (source.parent / source.stem).resolve()
)
out_root.mkdir(parents=True, exist_ok=True)
jobs = collect_zip_jobs(source, out_root)
elif is_image_path(source):
if is_numbered_asset_file(source):
print(f"Skip already-numbered file: {source}", file=sys.stderr)
return 0
target_dir = (
args.output_root.expanduser().resolve()
if args.output_root is not None
else source.parent.resolve()
)
target_dir.mkdir(parents=True, exist_ok=True)
idx = max_index_in_directory(target_dir) + 1
jobs = [
Job(
source_label=source.name,
target_dir=target_dir,
index=idx,
loader=lambda p=source: p.read_bytes(),
source_file=source if not args.keep_sources else None,
)
]
else:
print(f"Not a folder, image, or zip: {source}", file=sys.stderr)
return 1
jobs.sort(key=lambda job: (str(job.target_dir), job.index))
if not jobs: if not jobs:
print(f"No supported images found in: {source}") print(f"No new images to convert under: {source}")
return 1 return 0
converted = 0 converted = 0
skipped = 0 skipped = 0
with ThreadPoolExecutor(max_workers=max(1, args.jobs)) as pool: with ThreadPoolExecutor(max_workers=max(1, args.jobs)) as pool:
for ok, label, target, animated, error in pool.map(run_job, jobs): for ok, label, target, kind, error in pool.map(run_job, jobs):
if ok: if ok:
kind = "animated" if animated else "static"
print(f"{label} -> {target} ({kind})") print(f"{label} -> {target} ({kind})")
converted += 1 converted += 1
else: else:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 507 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 KiB

Some files were not shown because too many files have changed in this diff Show More