I built pagedMark to remove AI provenance from images and video you generated yourself

The important distinction is that AI provenance can exist in two forms.

First, there is metadata like C2PA, EXIF, XMP, IPTC and generator parameters. That part is easy to remove.

Second, there are invisible marks embedded directly into the pixels, such as SynthID style watermarks. A screenshot does not reliably remove those. pagedMark deals with them by regenerating the image.

The output is therefore not identical to the original. Faces, text and small details can change. The goal is to remove the provenance signal while keeping the image as close to the original as possible.

It currently supports invisible marks from ChatGPT, gpt-image API, Z-Image Turbo and Nano Banana, plus visible AI labels from several other generators. Video support covers visible marks and metadata from Sora, Veo, Seedance, Hailuo and Kling.

The other challenge was making this work properly on Apple Silicon. I tested it on M5 Macs with both 8 GB and 16 GB of memory, and added memory aware processing to prevent the system from silently falling into swap and turning a fast job into an extremely slow one.

And here is the really interesting part: after processing an image generated with GPT-Image, you can check it with OpenAI's verifier at openai.com/verify. In my testing, the processed image is reported with 0 AI detection.

uv tool install "pagedmark[diffusion]"
pagedmark invisible photo.png -o clean.png

GitHub: github.com/doofzoff/pagedMark

PyPI: PyPI: pagedmark

reddit.com
u/d0ofz — 1 day ago

I built pagedMark to remove AI provenance from images and video you generated yourself

The important distinction is that AI provenance can exist in two forms.

First, there is metadata like C2PA, EXIF, XMP, IPTC and generator parameters. That part is easy to remove.

Second, there are invisible marks embedded directly into the pixels, such as SynthID style watermarks. A screenshot does not reliably remove those. pagedMark deals with them by regenerating the image.

The output is therefore not identical to the original. Faces, text and small details can change. The goal is to remove the provenance signal while keeping the image as close to the original as possible.

It currently supports invisible marks from ChatGPT, gpt-image API, Z-Image Turbo and Nano Banana, plus visible AI labels from several other generators. Video support covers visible marks and metadata from Sora, Veo, Seedance, Hailuo and Kling.

The other challenge was making this work properly on Apple Silicon. I tested it on M5 Macs with both 8 GB and 16 GB of memory, and added memory aware processing to prevent the system from silently falling into swap and turning a fast job into an extremely slow one.

And here is the really interesting part: after processing an image generated with GPT-Image, you can check it with OpenAI's verifier at openai.com/verify. In my testing, the processed image is reported with 0 AI detection.

uv tool install "pagedmark[diffusion]"
pagedmark invisible photo.png -o clean.png

GitHub: github.com/doofzoff/pagedMark

PyPI: PyPI: pagedmark

reddit.com
u/d0ofz — 1 day ago

I built pagedMark to remove AI provenance from images and video you generated yourself

The important distinction is that AI provenance can exist in two forms.

First, there is metadata like C2PA, EXIF, XMP, IPTC and generator parameters. That part is easy to remove.

Second, there are invisible marks embedded directly into the pixels, such as SynthID style watermarks. A screenshot does not reliably remove those. pagedMark deals with them by regenerating the image.

The output is therefore not identical to the original. Faces, text and small details can change. The goal is to remove the provenance signal while keeping the image as close to the original as possible.

It currently supports invisible marks from ChatGPT, gpt-image API, Z-Image Turbo and Nano Banana, plus visible AI labels from several other generators. Video support covers visible marks and metadata from Sora, Veo, Seedance, Hailuo and Kling.

The other challenge was making this work properly on Apple Silicon. I tested it on M5 Macs with both 8 GB and 16 GB of memory, and added memory aware processing to prevent the system from silently falling into swap and turning a fast job into an extremely slow one.

And here is the really interesting part: after processing an image generated with GPT-Image, you can check it with OpenAI's verifier at openai.com/verify. In my testing, the processed image is reported with 0 AI detection.

uv tool install "pagedmark[diffusion]"
pagedmark invisible photo.png -o clean.png

GitHub: github.com/doofzoff/pagedMark

PyPI: PyPI: pagedmark

reddit.com
u/d0ofz — 1 day ago

I built pagedMark to remove AI provenance from images and video you generated yourself

The important distinction is that AI provenance can exist in two forms.

First, there is metadata like C2PA, EXIF, XMP, IPTC and generator parameters. That part is easy to remove.

Second, there are invisible marks embedded directly into the pixels, such as SynthID style watermarks. A screenshot does not reliably remove those. pagedMark deals with them by regenerating the image.

The output is therefore not identical to the original. Faces, text and small details can change. The goal is to remove the provenance signal while keeping the image as close to the original as possible.

It currently supports invisible marks from ChatGPT, gpt-image API, Z-Image Turbo and Nano Banana, plus visible AI labels from several other generators. Video support covers visible marks and metadata from Sora, Veo, Seedance, Hailuo and Kling.

The other challenge was making this work properly on Apple Silicon. I tested it on M5 Macs with both 8 GB and 16 GB of memory, and added memory aware processing to prevent the system from silently falling into swap and turning a fast job into an extremely slow one.

And here is the really interesting part: after processing an image generated with GPT-Image, you can check it with OpenAI's verifier at openai.com/verify. In my testing, the processed image is reported with 0 AI detection.

uv tool install "pagedmark[diffusion]"
pagedmark invisible photo.png -o clean.png

GitHub: github.com/doofzoff/pagedMark

PyPI: PyPI: pagedmark

reddit.com
u/d0ofz — 1 day ago

I built pagedMark to remove AI provenance from images and video you generated yourself

The important distinction is that AI provenance can exist in two forms.

First, there is metadata like C2PA, EXIF, XMP, IPTC and generator parameters. That part is easy to remove.

Second, there are invisible marks embedded directly into the pixels, such as SynthID style watermarks. A screenshot does not reliably remove those. pagedMark deals with them by regenerating the image.

The output is therefore not identical to the original. Faces, text and small details can change. The goal is to remove the provenance signal while keeping the image as close to the original as possible.

It currently supports invisible marks from ChatGPT, gpt-image API, Z-Image Turbo and Nano Banana, plus visible AI labels from several other generators. Video support covers visible marks and metadata from Sora, Veo, Seedance, Hailuo and Kling.

The other challenge was making this work properly on Apple Silicon. I tested it on M5 Macs with both 8 GB and 16 GB of memory, and added memory aware processing to prevent the system from silently falling into swap and turning a fast job into an extremely slow one.

And here is the really interesting part: after processing an image generated with GPT-Image, you can check it with OpenAI's verifier at openai.com/verify. In my testing, the processed image is reported with 0 AI detection.

uv tool install "pagedmark[diffusion]"
pagedmark invisible photo.png -o clean.png

GitHub: github.com/doofzoff/pagedMark

PyPI: PyPI: pagedmark

reddit.com
u/d0ofz — 2 days ago

I built pagedMark to remove AI provenance from images and video you generated yourself

The important distinction is that AI provenance can exist in two forms.

First, there is metadata like C2PA, EXIF, XMP, IPTC and generator parameters. That part is easy to remove.

Second, there are invisible marks embedded directly into the pixels, such as SynthID style watermarks. A screenshot does not reliably remove those. pagedMark deals with them by regenerating the image.

The output is therefore not identical to the original. Faces, text and small details can change. The goal is to remove the provenance signal while keeping the image as close to the original as possible.

It currently supports invisible marks from ChatGPT, gpt-image API, Z-Image Turbo and Nano Banana, plus visible AI labels from several other generators. Video support covers visible marks and metadata from Sora, Veo, Seedance, Hailuo and Kling.

The other challenge was making this work properly on Apple Silicon. I tested it on M5 Macs with both 8 GB and 16 GB of memory, and added memory aware processing to prevent the system from silently falling into swap and turning a fast job into an extremely slow one.

And here is the really interesting part: after processing an image generated with GPT-Image, you can check it with OpenAI's verifier at openai.com/verify. In my testing, the processed image is reported with 0 AI detection.

uv tool install "pagedmark[diffusion]"
pagedmark invisible photo.png -o clean.png

GitHub: github.com/doofzoff/pagedMark

PyPI: PyPI: pagedmark

reddit.com
u/d0ofz — 2 days ago
▲ 9 r/gptimage2prompts+5 crossposts

pagedMark: invisible SynthID-class watermark removal for AI images (ChatGPT, gpt-image, DALL·E, Sora, Gemini, Nano Banana), running on Metal

pagedMark removes AI provenance from content you generated yourself. Two different things, and it is worth separating them. The first is metadata: C2PA Content Credentials, EXIF, XMP, IPTC, the generator parameters. That part is easy and verifiable, and a screenshot does it too. The second is the invisible pixel watermark that a screenshot does not touch, the SynthID class of marks, which has to be disrupted by regenerating the image itself.

Coverage on the image side is ChatGPT, gpt-image, DALL·E, Sora, Gemini and Nano Banana for the invisible marks, plus a registry of visible vendor labels (Doubao, Jimeng, Qwen, Kling, Yuanbao, Baidu, LibLibAI, Samsung Galaxy AI). On the video side it handles the visible marks from Sora, Veo, Seedance, Dola, Hailuo and Kling, and the metadata that travels with them.

The reason this is worth a post rather than a link is that it is built for Apple Silicon instead of ported to it. I spent several days getting the pipeline to run correctly on an M5 with 16 GB, meaning predictable and measured rather than merely launching. Most of what I assumed turned out to be wrong, so the measurements are below.

The four-step distillation LoRA invents texture, and more steps make it worse

A low strength edit runs the tail of a long schedule: strength 0.15 executes the last four steps of twenty seven. A LoRA distilled for four timesteps spanning the entire noise range is off its distribution there. Wherever nothing conditions the model, and flat dark fabric gives a Canny ControlNet no edges at all, it fills the gap from its prior. On a night photograph that arrives as coloured camouflage across black clothing.

Global stage, 1448x1080, strength 0.15, seed 0 Invented texture PSNR Wall
Lightning, 4 steps 1.73x source 28.54 dB 41 s
Lightning, 8 steps 1.80x 28.19 dB 29 s
Lightning, 16 steps 1.84x 27.85 dB 62 s
Undistilled base, 16 steps 1.19x 29.25 dB 71 s
Undistilled base, 24 steps 1.20x 29.17 dB 132 s

Asking the distilled model for more steps made the artifact worse, which is what identified the distillation rather than the step count as the cause. Dropping the LoRA costs roughly three times the wall time and buys back both fidelity and correctness.

Three wrong theories I paid for first, in case they save someone else the time. Not the fp16 VAE: a bare encode and decode round trip of the same crop is clean in fp16 and in fp32, tiled or whole, at 34.6 dB. Not Metal's fp16 in general: bf16 measured marginally worse. Not Canny picking up sensor noise: the Canny map of that region is completely empty, which was the actual clue.

Metal pages instead of failing, so memory has to be measured

torch.mps.recommended_max_memory() reports 11.84 GiB on a 16 GB machine. Exceed it and nothing raises an error. The process starts swapping, and a run that should take 23 seconds takes an hour instead.

With VAE tiling disabled, a 1.57 MP frame peaks at 18.74 GiB and takes 59 seconds. With tiling it peaks at 10.92 GiB and takes 23 seconds. So tiling carries real weight on a small machine, but its boundaries leave a faint texture, which is why it is now decided per frame from the device budget rather than switched on globally.

Diffusion untiled at 2.5 MP went into swap and did not finish within twelve minutes. Tiled at 1024 px, a 5.07 MP frame holds 10.93 GiB, finishes in 88 seconds, and keeps its native geometry.

Sequential CPU offload works on MPS, and it is what makes 8 GB usable

The stack is 7.7 GiB of weights. An 8 GB Mac reports a working set of roughly 5.3 GiB, so it does not fit however the activations are handled. Streaming the weights one module at a time:

Same frame, same seed Peak device memory Wall
Weights resident 7.70 GiB 7.1 s
enable_sequential_cpu_offload(device="mps") 0.28 GiB 24.1 s

Twenty seven times less peak memory for 3.4 times the wall time. The plan is chosen from the measured budget and then printed, because a run three times slower than the fast path looks broken unless it says why.

Two Metal gaps worth knowing if you are porting anything

torch.float8_e4m3fn does not exist on MPS at all. The error is RuntimeError: Undefined type Float8_e4m3fn. Any pipeline that streams float8 weights, which several VRAM managed stacks do, cannot be loaded there under any configuration.

SAM's processor emits its box and point prompts as float64, which Metal also has no type for, so moving the batch to the device raises rather than degrading. A single cast fixes it, but nothing tells you that is the problem.

The expensive one: fp16 sampling on MPS returns zeros silently

I added a memory optimisation that encodes the two fixed prompts once and drops the text encoders, saving a measured 1.52 GiB of the 8.79 GiB the loaded stack holds. Two of four face crops then came back as all zero black rectangles. Deterministically, at the same seed, with nothing raised anywhere.

The embeddings were innocent. CPU fp16, MPS fp16 and fp32 encodings of that prompt agree to 0.0009 on tensors with a standard deviation of 3.06, and the same crop generated in isolation is correct either way. Freeing unrelated memory changed the allocation pattern the crops met after the global pass, and that alone was enough. I withdrew the optimisation and added a guard that drops any empty crop instead of compositing it.

If you run fp16 diffusion on Metal, check your output for degeneracy. It will not tell you.

What it does not claim

Regeneration is not payload deletion. The image changes: faces, text and fine detail move, and the numbers above are the measured size of that change rather than a reassurance.

No public local decoder exists for SynthID class marks, so identify reports unknown and never clean. Verification is the provider's verifier or nothing. The 0.15 operating point comes from the upstream project's record against openai.com/verify on CUDA. I have not re-run that check on Metal, and Metal is not bit identical to CUDA, so I am claiming the same operating point and not the same verdict.

It is for content you generated or own. The visible mark registry accepts AI generation labels only. Stock agency previews, marketplace and classifieds watermarks are deliberately out of scope, and that boundary is in the repository rather than only in this comment.

Because "how much did that cost my picture" is the whole question

pagedmark measure before.png after.png

PSNR over the frame, PSNR per detected face, and how much mid band structure appeared where the source was flat and dark. The third metric is the one that caught the camouflage, and it took two attempts. Per pixel chroma statistics rank the artifact below the source, because the source's own sensor grain carries more per pixel variance than the invented blotches do. A plain band ratio fails too, since any linear filter reports doubled grain and doubled blotches identically. Normalising mid band energy by fine detail energy, against the same ratio in the source, measures the shape of the spectrum instead of its size.

uv tool install "pagedmark[diffusion]"
pagedmark invisible photo.png -o clean.png
pagedmark invisible photo.png --preview      # 46.6 s instead of 112.6 s

Code: https://github.com/doofzoff/pagedMark
PyPI: https://pypi.org/project/pagedmark/

Happy to answer anything about the Metal specifics. That is the part I would have wanted written down before I started.

u/d0ofz — 10 hours ago
▲ 3 r/Qwen_AI+2 crossposts

pagedMark: invisible SynthID-class watermark removal for AI images (ChatGPT, gpt-image, Z Image Turbo, Qwen Image, Seedance), running on Metal

I spent a few days getting an SDXL-based provenance-removal pipeline (visible AI labels, C2PA metadata, SynthID-class pixel watermarks) to run properly on an M5 with 16 GB. Not "it launches" — actually correct and predictable. Almost everything I assumed was wrong, and the measurements are the interesting part, so here they are.

1. The four-step distillation LoRA invents texture, and more steps make it worse.

Low-strength img2img runs the tail of a long schedule (strength 0.15 → the last 4 of 27 steps). A LoRA distilled for four timesteps across the whole noise range is off-distribution there, and wherever nothing conditions it — flat dark fabric gives Canny no edges — it fills the gap from its prior. On a night photo that reads as coloured camouflage across black clothing.

Global stage, 1448×1080, strength 0.15, seed 0 Invented texture PSNR Wall
Lightning, 4 steps 1.73× source 28.54 dB 41 s
Lightning, 8 steps 1.80× 28.19 dB 29 s
Lightning, 16 steps 1.84× 27.85 dB 62 s
Undistilled base, 16 steps 1.19× 29.25 dB 71 s
Undistilled base, 24 steps 1.20× 29.17 dB 132 s

Asking the distilled model for more steps made it worse, which is what identified the distillation rather than the step count. Dropping the LoRA cost 3× the wall time and bought both fidelity and correctness.

Wrong theories I paid for first: the fp16 VAE (a bare round-trip is clean in fp16 and fp32, tiled or not, 34.6 dB), Metal's fp16 in general (bf16 measured marginally worse), and Canny picking up sensor noise (the Canny map of that region is empty — which was the actual clue).

2. Metal pages instead of failing, so memory has to be measured, not hoped for.

torch.mps.recommended_max_memory() reports 11.84 GiB on a 16 GB machine. Exceed it and nothing raises — the process just starts swapping and a run that should take 23 s takes an hour.

  • VAE tiling off, 1.57 MP frame: 18.74 GiB peak, 59 s. On: 10.92 GiB, 23 s. So tiling is load-bearing on small machines — but its boundaries leave a faint texture, so it's now decided per frame from the budget rather than switched on globally.
  • Diffusion untiled at 2.5 MP: went into swap and did not finish in twelve minutes. Tiled at 1024 px, 5.07 MP: 10.93 GiB, 88 s, native geometry preserved.

3. Sequential CPU offload works on MPS, and it's what makes 8 GB usable.

The stack is 7.7 GiB of weights; an 8 GB Mac gives you about 5.3 GiB. Streaming the weights module by module:

Same frame, same seed Peak device memory Wall
Resident 7.70 GiB 7.1 s
enable_sequential_cpu_offload(device="mps") 0.28 GiB 24.1 s

27× less peak for 3.4× the time. The plan is chosen from the measured budget and printed, because a run three times slower looks broken unless it says why.

4. Two Metal gaps worth knowing if you're porting anything.

  • torch.float8_e4m3fn doesn't exist on MPS at all (RuntimeError: Undefined type Float8_e4m3fn). Any pipeline that streams float8 weights — a lot of the VRAM-managed stacks do — cannot load, full stop.
  • SAM's processor emits its box/point prompts as float64, which Metal also has no type for, so moving the batch to the device raises instead of degrading. One cast fixes it.

5. The one that cost me the most: fp16 sampling on MPS silently returns zeros.

I added a memory optimisation — encode the fixed prompts once, drop the text encoders, save 1.52 GiB. Two of four face crops then came back as all-zero black rectangles. Deterministically, same seed, nothing raised.

The embeddings were innocent (CPU fp16, MPS fp16 and fp32 encodings of that prompt agree to 0.0009 on tensors with σ=3.06) and the same crop in isolation was fine. Freeing unrelated memory changed the allocation pattern the crops met after the global pass, and that was enough. I withdrew the optimisation and added a guard that drops any empty crop instead of compositing it.

If you're doing fp16 diffusion on Metal: check your output for degeneracy. It will not tell you.

What it doesn't claim. Regeneration is not payload deletion — faces, text and fine detail move, and the numbers above are the measured size of that. No public local decoder exists for SynthID-class marks, so identify reports unknown, never clean; verification is the provider's verifier or nothing. Metal isn't bit-identical to CUDA, so operating points transfer between backends but recorded verdicts don't. And it's for content you generated or own — the visible-mark registry takes AI-generation labels only, deliberately not stock or marketplace marks.

Because "how much did that cost my picture" is the whole question, it ships as a command:

pagedmark measure before.png after.png

PSNR over the frame, PSNR per detected face, and how much mid-band structure appeared where the source was flat and dark. That third metric is the one that caught the camouflage — per-pixel chroma statistics rank the artifact below the source, because the source's own sensor grain has more per-pixel variance than the invented blotches do.

uv tool install "pagedmark[diffusion]"
pagedmark invisible photo.png -o clean.png

Code: https://github.com/doofzoff/pagedMark · PyPI: https://pypi.org/project/pagedmark/

Happy to answer anything about the Metal specifics — that's the part I'd have wanted written down before I started.

u/d0ofz — 2 days ago

I ported an SDXL watermark-removal pipeline to Apple Silicon and measured everything. The four-step distillation LoRA was the problem.

I spent a few days getting an SDXL-based provenance-removal pipeline (visible AI labels, C2PA metadata, SynthID-class pixel watermarks) to run properly on an M5 with 16 GB. Not "it launches", actually correct and predictable. Almost everything I assumed was wrong, and the measurements are the interesting part, so here they are.

1. The four-step distillation LoRA invents texture, and more steps make it worse.

Low-strength img2img runs the tail of a long schedule (strength 0.15 → the last 4 of 27 steps). A LoRA distilled for four timesteps across the whole noise range is off-distribution there, and wherever nothing conditions it, flat dark fabric gives Canny no edges, it fills the gap from its prior. On a night photo that reads as coloured camouflage across black clothing.

Global stage, 1448×1080, strength 0.15, seed 0 Invented texture PSNR Wall
Lightning, 4 steps 1.73× source 28.54 dB 41 s
Lightning, 8 steps 1.80× 28.19 dB 29 s
Lightning, 16 steps 1.84× 27.85 dB 62 s
Undistilled base, 16 steps 1.19× 29.25 dB 71 s
Undistilled base, 24 steps 1.20× 29.17 dB 132 s

Asking the distilled model for more steps made it worse, which is what identified the distillation rather than the step count. Dropping the LoRA cost 3× the wall time and bought both fidelity and correctness.

Wrong theories I paid for first: the fp16 VAE (a bare round-trip is clean in fp16 and fp32, tiled or not, 34.6 dB), Metal's fp16 in general (bf16 measured marginally worse), and Canny picking up sensor noise (the Canny map of that region is empty — which was the actual clue).

2. Metal pages instead of failing, so memory has to be measured, not hoped for.

torch.mps.recommended_max_memory() reports 11.84 GiB on a 16 GB machine. Exceed it and nothing raises — the process just starts swapping and a run that should take 23 s takes an hour.

  • VAE tiling off, 1.57 MP frame: 18.74 GiB peak, 59 s. On: 10.92 GiB, 23 s. So tiling is load-bearing on small machines — but its boundaries leave a faint texture, so it's now decided per frame from the budget rather than switched on globally.
  • Diffusion untiled at 2.5 MP: went into swap and did not finish in twelve minutes. Tiled at 1024 px, 5.07 MP: 10.93 GiB, 88 s, native geometry preserved.

3. Sequential CPU offload works on MPS, and it's what makes 8 GB usable.

The stack is 7.7 GiB of weights; an 8 GB Mac gives you about 5.3 GiB. Streaming the weights module by module:

Same frame, same seed Peak device memory Wall
Resident 7.70 GiB 7.1 s
enable_sequential_cpu_offload(device="mps") 0.28 GiB 24.1 s

27× less peak for 3.4× the time. The plan is chosen from the measured budget and printed, because a run three times slower looks broken unless it says why.

4. Two Metal gaps worth knowing if you're porting anything.

  • torch.float8_e4m3fn doesn't exist on MPS at all (RuntimeError: Undefined type Float8_e4m3fn). Any pipeline that streams float8 weights — a lot of the VRAM-managed stacks do — cannot load, full stop.
  • SAM's processor emits its box/point prompts as float64, which Metal also has no type for, so moving the batch to the device raises instead of degrading. One cast fixes it.

5. The one that cost me the most: fp16 sampling on MPS silently returns zeros.

I added a memory optimisation — encode the fixed prompts once, drop the text encoders, save 1.52 GiB. Two of four face crops then came back as all-zero black rectangles. Deterministically, same seed, nothing raised.

The embeddings were innocent (CPU fp16, MPS fp16 and fp32 encodings of that prompt agree to 0.0009 on tensors with σ=3.06) and the same crop in isolation was fine. Freeing unrelated memory changed the allocation pattern the crops met after the global pass, and that was enough. I withdrew the optimisation and added a guard that drops any empty crop instead of compositing it.

If you're doing fp16 diffusion on Metal: check your output for degeneracy. It will not tell you.

What it doesn't claim. Regeneration is not payload deletion — faces, text and fine detail move, and the numbers above are the measured size of that. No public local decoder exists for SynthID-class marks, so identify reports unknown, never clean; verification is the provider's verifier or nothing. Metal isn't bit-identical to CUDA, so operating points transfer between backends but recorded verdicts don't. And it's for content you generated or own — the visible-mark registry takes AI-generation labels only, deliberately not stock or marketplace marks.

Because "how much did that cost my picture" is the whole question, it ships as a command:

pagedmark measure before.png after.png

PSNR over the frame, PSNR per detected face, and how much mid-band structure appeared where the source was flat and dark. That third metric is the one that caught the camouflage — per-pixel chroma statistics rank the artifact below the source, because the source's own sensor grain has more per-pixel variance than the invented blotches do.

uv tool install "pagedmark[diffusion]"
pagedmark invisible photo.png -o clean.png

Code: https://github.com/doofzoff/pagedMark · PyPI: https://pypi.org/project/pagedmark/

Happy to answer anything about the Metal specifics, that's the part I'd have wanted written down before I started.

reddit.com
u/d0ofz — 3 days ago