deploy.py auto-commit

This commit is contained in:
jaseg 2023-07-26 18:35:47 +02:00
commit 74d7f5c965
313 changed files with 4951 additions and 5747 deletions

View file

@ -0,0 +1,650 @@
<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<title>Gerbolyze | Home</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="color-scheme" content="dark light">
<link rel="stylesheet" href="/style.css">
</head>
<body><nav>
<div class="internal">
<a href="/" title="Home">Home</a>
<a href="/blog/" title="Blog">Blog</a>
<a href="/projects/" title="Projects">Projects</a>
<a href="/about/" title="About">About</a>
</div>
<div class="external">
<a href="https://git.jaseg.de/" title="cgit">cgit</a>
<a href="https://github.com/jaseg" title="Github">Github</a>
<a href="https://gitlab.com/neinseg" title="Gitlab">Gitlab</a>
<a href="https://chaos.social/jaseg" title="Mastodon">Mastodon</a>
</span>
</nav>
<header>
<h1>Gerbolyze</h1>
<ul class="breadcrumbs">
<li><a href="/">jaseg.de</a></li>
<li><a href="/projects/">Projects</a></li><li><a href="/projects/gerbolyze/">Gerbolyze</a></li>
</ul>
</header>
<main>
<div class="links">
<a href="https://git.jaseg.de/gerbolyze.git">Sources</a>
<a href="https://github.com/jaseg/gerbolyze/issues">Issues</a>
<a href="https://gerbolyze.gitlab.io/gerbolyze">Docs</a>
<a href="https://pypi.org/project/gerbolyze">PyPI</a>
</div>
<div class="document">
<p>Gerbolyze renders SVG vector and PNG/JPG raster images into existing gerber PCB manufacturing files.
Vector data from SVG files is rendered losslessly <em>without</em> an intermediate rasterization/revectorization step.
Still, gerbolyze supports (almost) the full SVG 1.1 spec including complex, self-intersecting paths with holes,
patterns, dashes and transformations.</p>
<p>Raster images can either be vectorized through contour tracing (like gerbolyze v1.0 did) or they can be embedded using
high-resolution grayscale emulation while (mostly) guaranteeing trace/space design rules.</p>
<p>Try gerbolyze online at <a class="reference external" href="https://dyna.kokoroyukuma.de/gerboweb">https://dyna.kokoroyukuma.de/gerboweb</a></p>
<div class="figure">
<img alt="pics/pcbway_sample_02_small.jpg" src="pics/pcbway_sample_02_small.jpg" style="width: 800px;" />
<p class="caption">Drawing by <a class="reference external" href="https://twitter.com/fluffy2038/status/1317231121269104640">トーコ Toko</a> converted using Gerbolyze and printed at PCBWay.</p>
</div>
<p>Tooling for PCB art is quite limited in both open source and closed source ecosystems. Something as simple as putting a
pretty picture on a PCB can be an extremely tedious task. Depending on the PCB tool used, various arcane incantations
may be necessary and even modestly complex images will slow down most PCB tools to a crawl.</p>
<p>Gerbolyze solves this problem in a toolchain-agnostic way by directly vectorizing SVG vector and PNG or JPG bitmap files
onto existing gerber layers. Gerbolyze processes any spec-compliant SVG and &quot;gerbolyzes&quot; SVG vector data into a Gerber
spec-compliant form. Gerbolyze has been tested against both the leading open-source KiCAD toolchain and the
industry-standard Altium Designer. Gerbolyze is written with performance in mind and will happily vectorize tens of
thousands of primitives, generating tens of megabytes of gerber code without crapping itself. With gerbolyze you can
finally be confident that your PCB fab's toolchain will fall over before yours does if you overdo it with the high-poly
anime silkscreen.</p>
<p>Gerbolyze is based on <a class="reference external" href="https://gitlab.com/gerbolyze/gerbonara">gerbonara</a>.</p>
<img alt="pics/process-overview.png" src="pics/process-overview.png" style="width: 800px;" />
<div class="contents topic" id="contents">
<p class="topic-title">Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#tl-dr-produce-high-quality-artistic-pcbs-in-three-easy-steps" id="toc-entry-1">Tl;dr: Produce high-quality artistic PCBs in three easy steps!</a></li>
<li><a class="reference internal" href="#quick-start-installation-any-platform" id="toc-entry-2">Quick Start Installation (Any Platform)</a></li>
<li><a class="reference internal" href="#speeding-up-gerbolyze-using-natively-built-binaries" id="toc-entry-3">Speeding up gerbolyze using natively-built binaries</a><ul>
<li><a class="reference internal" href="#build-from-source-any-distro" id="toc-entry-4">Build from source (any distro)</a></li>
</ul>
</li>
<li><a class="reference internal" href="#features" id="toc-entry-5">Features</a></li>
<li><a class="reference internal" href="#algorithm-overview" id="toc-entry-6">Algorithm Overview</a></li>
<li><a class="reference internal" href="#web-interface" id="toc-entry-7">Web interface</a></li>
<li><a class="reference internal" href="#command-line-usage" id="toc-entry-8">Command-line usage</a><ul>
<li><a class="reference internal" href="#gerbolyze-template" id="toc-entry-9"><tt class="docutils literal">gerbolyze template</tt></a><ul>
<li><a class="reference internal" href="#options" id="toc-entry-10">Options:</a></li>
</ul>
</li>
<li><a class="reference internal" href="#gerbolyze-paste" id="toc-entry-11"><tt class="docutils literal">gerbolyze paste</tt></a><ul>
<li><a class="reference internal" href="#options-1" id="toc-entry-12">Options:</a></li>
<li><a class="reference internal" href="#outline-layers-1" id="toc-entry-13">Outline layers</a></li>
<li><a class="reference internal" href="#subtraction-scripts" id="toc-entry-14">Subtraction scripts</a></li>
</ul>
</li>
<li><a class="reference internal" href="#svg-flatten-1" id="toc-entry-15"><tt class="docutils literal"><span class="pre">svg-flatten</span></tt></a><ul>
<li><a class="reference internal" href="#options-2" id="toc-entry-16">Options:</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#gerbolyze-image-vectorization" id="toc-entry-17">Gerbolyze image vectorization</a><ul>
<li><a class="reference internal" href="#vectorizer-poisson-disc-the-default" id="toc-entry-18"><tt class="docutils literal"><span class="pre">--vectorizer</span> <span class="pre">poisson-disc</span></tt> (the default)</a></li>
<li><a class="reference internal" href="#vectorizer-hex-grid" id="toc-entry-19"><tt class="docutils literal"><span class="pre">--vectorizer</span> <span class="pre">hex-grid</span></tt></a></li>
<li><a class="reference internal" href="#vectorizer-square-grid" id="toc-entry-20"><tt class="docutils literal"><span class="pre">--vectorizer</span> <span class="pre">square-grid</span></tt></a></li>
<li><a class="reference internal" href="#vectorizer-binary-contours" id="toc-entry-21"><tt class="docutils literal"><span class="pre">--vectorizer</span> <span class="pre">binary-contours</span></tt></a></li>
</ul>
</li>
<li><a class="reference internal" href="#gimp-halftone-preprocessing-guide" id="toc-entry-22">GIMP halftone preprocessing guide</a><ul>
<li><a class="reference internal" href="#import-your-desired-artwork" id="toc-entry-23">1 Import your desired artwork</a></li>
<li><a class="reference internal" href="#convert-the-image-to-grayscale" id="toc-entry-24">2 Convert the image to grayscale</a></li>
<li><a class="reference internal" href="#fine-tune-the-image-s-contrast" id="toc-entry-25">3 Fine-tune the image's contrast</a></li>
<li><a class="reference internal" href="#retouch-details" id="toc-entry-26">4 Retouch details</a></li>
<li><a class="reference internal" href="#run-the-newsprint-filter" id="toc-entry-27">5 Run the newsprint filter</a></li>
<li><a class="reference internal" href="#export-the-image-for-use-with-gerbolyze-vectorize" id="toc-entry-28">6 Export the image for use with <tt class="docutils literal">gerbolyze vectorize</tt></a></li>
</ul>
</li>
<li><a class="reference internal" href="#manufacturing-considerations" id="toc-entry-29">Manufacturing Considerations</a></li>
<li><a class="reference internal" href="#limitations" id="toc-entry-30">Limitations</a><ul>
<li><a class="reference internal" href="#svg-raster-features" id="toc-entry-31">SVG raster features</a></li>
<li><a class="reference internal" href="#gerber-pass-through" id="toc-entry-32">Gerber pass-through</a></li>
<li><a class="reference internal" href="#trace-space-design-rule-adherence" id="toc-entry-33">Trace/Space design rule adherence</a></li>
</ul>
</li>
<li><a class="reference internal" href="#gallery" id="toc-entry-34">Gallery</a></li>
<li><a class="reference internal" href="#licensing" id="toc-entry-35">Licensing</a></li>
</ul>
</div>
<div class="section" id="tl-dr-produce-high-quality-artistic-pcbs-in-three-easy-steps">
<h2><a class="toc-backref" href="#toc-entry-1">Tl;dr: Produce high-quality artistic PCBs in three easy steps!</a></h2>
<p>Gerbolyze works in three steps.</p>
<ol class="arabic">
<li><p class="first">Generate a scale-accurate template of the finished PCB from your CAD tool's gerber output:</p>
<pre class="code literal-block">
<span class="lineno"></span><span class="line">$ gerbolyze template --top template_top.svg [--bottom template_bottom.svg] my_gerber_dir
</span></pre>
</li>
<li><p class="first">Load the resulting template image <a class="reference external" href="https://inkscape.org/">Inkscape</a> or another SVG editing program. Put your artwork on the appropriate SVG
layer. Dark colors become filled gerber primitives, bright colors become unfilled primitives. You can directly put
raster images (PNG/JPG) into this SVG as well, just position and scale them like everything else. SVG clips work for
images, too. Masks are not supported.</p>
</li>
<li><p class="first">Vectorize the edited SVG template image drectly into the PCB's gerber files:</p>
<pre class="code literal-block">
<span class="lineno"></span><span class="line">$ gerbolyze paste --top template_top_edited.svg [--bottom ...] my_gerber_dir output_gerber_dir
</span></pre>
</li>
</ol>
</div>
<div class="section" id="quick-start-installation-any-platform">
<h2><a class="toc-backref" href="#toc-entry-2">Quick Start Installation (Any Platform)</a></h2>
<pre class="code shell literal-block">
<span class="lineno"></span><span class="line">python<span class="w"> </span>-m<span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>--user<span class="w"> </span>gerbolyze
</span></pre>
<p>To uninstall, run</p>
<pre class="code shell literal-block">
<span class="lineno"></span><span class="line">python<span class="w"> </span>-m<span class="w"> </span>pip<span class="w"> </span>uninstall<span class="w"> </span>gerbolyze<span class="w"> </span>gerbonara<span class="w"> </span>resvg-wasi<span class="w"> </span>svg-flatten-wasi
</span></pre>
<p>To update, run</p>
<pre class="code shell literal-block">
<span class="lineno"></span><span class="line">python<span class="w"> </span>-m<span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>--user<span class="w"> </span>--upgrade<span class="w"> </span>--upgrade-strategy<span class="w"> </span>eager<span class="w"> </span>gerbolyze
</span></pre>
</div>
<div class="section" id="speeding-up-gerbolyze-using-natively-built-binaries">
<h2><a class="toc-backref" href="#toc-entry-3">Speeding up gerbolyze using natively-built binaries</a></h2>
<p>This will install gerbolyze's binary dependency resvg and gerbolyze's svg-flatten utility as pre-built cross-platform
WASM binaries. When you first run gerbolyze, it will take some time (~30s) to link these binaries for your system. The
output is cached, so any future run is going to be fast.</p>
<p>WASM is slower than natively-built binaries. To speed up gerbolyze, you can natively build its two binary dependencies:</p>
<ol class="arabic simple">
<li>Install resvg natively using rust's cargo package manager: <tt class="docutils literal">cargo install resvg</tt></li>
<li>Install gerbolyze's svg-flatten utility natively. You can get pre-built binaries from gerbolyze's gitlab CI jobs <a class="reference external" href="https://gitlab.com/gerbolyze/gerbolyze/-/pipelines?scope=tags&amp;page=1">at
this link</a> by clicking the three dots on the
right next to the version you want. These pre-built binaries should work on any x86_64 linux since they are
statically linked. You can also build svg-flatten yourself by running <tt class="docutils literal">make</tt> inside the <tt class="docutils literal"><span class="pre">svg-flatten</span></tt> folder from
a gerbolyze checkout.</li>
</ol>
<p>Gerbolyze will pick up these binaries when installed in your <tt class="docutils literal">$PATH</tt>. resvg is also picked up when it is installed by
cargo in your home's <tt class="docutils literal"><span class="pre">~/.cargo</span></tt>, even if it's not in your <tt class="docutils literal">$PATH</tt>. You can override the resvg, usvg or svg-flatten
binary that gerbolyze uses by giving it the absoulute path to a binary in the <tt class="docutils literal">$RESVG</tt>, <tt class="docutils literal">$USVG</tt> and <tt class="docutils literal">$SVG_FLATTEN</tt>
environment variables.</p>
<div class="section" id="build-from-source-any-distro">
<h3><a class="toc-backref" href="#toc-entry-4">Build from source (any distro)</a></h3>
<pre class="code sh literal-block">
<span class="lineno"></span><span class="line">git<span class="w"> </span>clone<span class="w"> </span>--recurse-submodules<span class="w"> </span>https://git.jaseg.de/gerbolyze.git<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="nb">cd</span><span class="w"> </span>gerbolyze<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>python3<span class="w"> </span>-m<span class="w"> </span>venv<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="nb">source</span><span class="w"> </span>venv/bin/activate<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>python3<span class="w"> </span>setup.py<span class="w"> </span>install
</span></pre>
</div>
</div>
<div class="section" id="features">
<h2><a class="toc-backref" href="#toc-entry-5">Features</a></h2>
<p>Input on the left, output on the right.</p>
<img alt="pics/test_svg_readme_composited.png" src="pics/test_svg_readme_composited.png" style="width: 800px;" />
<ul class="simple">
<li>Almost full SVG 1.1 static spec coverage (!)<ul>
<li>Paths with beziers, self-intersections and holes</li>
<li>Strokes, even with dashes and markers</li>
<li>Pattern fills and strokes</li>
<li>Transformations and nested groups</li>
<li>Proper text rendering with support for complex text layout (e.g. Arabic)</li>
<li>&lt;image&gt; elements via either built-in vectorizer or built-in halftone processor</li>
<li>(some) CSS</li>
</ul>
</li>
<li>Writes Gerber, SVG or KiCAD S-Expression (<tt class="docutils literal">.kicad_mod</tt>) formats</li>
<li>Can export from top/bottom SVGs to a whole gerber layer stack at once with filename autodetection</li>
<li>Can export SVGs to <tt class="docutils literal">.kicad_mod</tt> files like svg2mod (but with full SVG support)</li>
<li>Beziers flattening with configurable tolerance using actual math!</li>
<li>Polygon intersection removal</li>
<li>Polygon hole removal (!)</li>
<li>Optionally vector-compositing of output: convert black/white/transparent image to black/transparent image</li>
<li>Renders SVG templates from input gerbers for accurate and easy scaling and positioning of artwork</li>
<li>layer masking with offset (e.g. all silk within 1mm of soldermask)</li>
<li>Can read gerbers from zip files</li>
<li>Limited SVG support for board outline layers (no fill/region support)</li>
<li>Dashed lines supported on board outline layers</li>
</ul>
<p>Gerbolyze is the end-to-end &quot;paste this svg into these gerbers&quot; command that handles all layers on both board sides at
once. The heavy-duty computer geometry logic of gerbolyze is handled by the svg-flatten utility (<tt class="docutils literal"><span class="pre">svg-flatten</span></tt>
directory). svg-flatten reads an SVG file and renders it into a variety of output formats. svg-flatten can be used like
a variant of the popular svg2mod that supports all of SVG and handles arbitrary input <tt class="docutils literal">&lt;path&gt;</tt> elements.</p>
</div>
<div class="section" id="algorithm-overview">
<h2><a class="toc-backref" href="#toc-entry-6">Algorithm Overview</a></h2>
<p>This is the algorithm gerbolyze uses to process a stack of gerbers.</p>
<ul class="simple">
<li>Map input files to semantic layers by their filenames</li>
<li>For each layer:<ul>
<li>load input gerber</li>
<li>Pass mask layers through <tt class="docutils literal">gerbv</tt> for conversion to SVG</li>
<li>Pass mask layers SVG through <tt class="docutils literal"><span class="pre">svg-flatten</span> <span class="pre">--dilate</span></tt></li>
<li>Pass input SVG through <tt class="docutils literal"><span class="pre">svg-flatten</span> <span class="pre">--only-groups</span> [layer]</tt></li>
<li>Overlay input gerber, mask and input svg</li>
<li>Write result to output gerber</li>
</ul>
</li>
</ul>
<p>This is the algorithm svg-flatten uses to process an SVG.</p>
<ul class="simple">
<li>pass input SVG through <a class="reference external" href="https://github.com/RazrFalcon/resvg">usvg</a></li>
<li>iterate depth-first through resulting SVG.<ul>
<li>for groups: apply transforms and clip and recurse</li>
<li>for images: Vectorize using selected vectorizer</li>
<li>for paths:<ul>
<li>flatten path using Cairo</li>
<li>remove self-intersections using Clipper</li>
<li>if stroke is set: process dash, then offset using Clipper</li>
<li>apply pattern fills</li>
<li>clip to clip-path</li>
<li>remove holes using Clipper</li>
</ul>
</li>
</ul>
</li>
<li>for KiCAD S-Expression export: vector-composite results using CavalierContours: subtract each clear output primitive
from all previous dark output primitives</li>
</ul>
</div>
<div class="section" id="web-interface">
<h2><a class="toc-backref" href="#toc-entry-7">Web interface</a></h2>
<p>You can try gerbolyze online at <a class="reference external" href="https://dyna.kokoroyukuma.de/gerboweb">https://dyna.kokoroyukuma.de/gerboweb</a></p>
<p>The web interface does not expose all of gerbolyze's bells and whistles, but it allows you to simply paste a single SVG
file on a board to try out gerbolyze. Upload your design on the web interface, then download the template for either the
top or bottom side, and put your artwork on the appropriate layer of that template using <a class="reference external" href="https://inkscape.org/">Inkscape</a>. Finally, upload the
modified template and let gerbolyze process your design.</p>
</div>
<div class="section" id="command-line-usage">
<h2><a class="toc-backref" href="#toc-entry-8">Command-line usage</a></h2>
<p id="command-line-usage-1">Generate SVG template from Gerber files:</p>
<pre class="code shell literal-block">
<span class="lineno"></span><span class="line">gerbolyze<span class="w"> </span>template<span class="w"> </span><span class="o">[</span>options<span class="o">]</span><span class="w"> </span><span class="o">[</span>--top<span class="p">|</span>--bottom<span class="o">]</span><span class="w"> </span>input_dir_or.zip<span class="w"> </span>output.svg
</span></pre>
<p>Render design from an SVG made with the template above into a set of gerber files:</p>
<pre class="code shell literal-block">
<span class="lineno"></span><span class="line">gerbolyze<span class="w"> </span>paste<span class="w"> </span><span class="o">[</span>options<span class="o">]</span><span class="w"> </span>artwork.svg<span class="w"> </span>input_dir_or.zip<span class="w"> </span>output_dir_or.zip
</span></pre>
<p>Use svg-flatten to convert an SVG file into Gerber or flattened SVG:</p>
<pre class="code shell literal-block">
<span class="lineno"></span><span class="line">svg-flatten<span class="w"> </span><span class="o">[</span>options<span class="o">]</span><span class="w"> </span>--format<span class="w"> </span><span class="o">[</span>gerber<span class="p">|</span>svg<span class="o">]</span><span class="w"> </span><span class="o">[</span>input_file.svg<span class="o">]</span><span class="w"> </span><span class="o">[</span>output_file<span class="o">]</span>
</span></pre>
<p>Use svg-flatten to convert an SVG file into the given layer of a KiCAD S-Expression (<tt class="docutils literal">.kicad_mod</tt>) file:</p>
<pre class="code shell literal-block">
<span class="lineno"></span><span class="line">svg-flatten<span class="w"> </span><span class="o">[</span>options<span class="o">]</span><span class="w"> </span>--format<span class="w"> </span>kicad<span class="w"> </span>--sexp-layer<span class="w"> </span>F.SilkS<span class="w"> </span>--sexp-mod-name<span class="w"> </span>My_Module<span class="w"> </span><span class="o">[</span>input_file.svg<span class="o">]</span><span class="w"> </span><span class="o">[</span>output_file<span class="o">]</span>
</span></pre>
<p>Use svg-flatten to convert an SVG file into a <tt class="docutils literal">.kicad_mod</tt> with SVG layers fed into separate KiCAD layers based on
their IDs like the popular <tt class="docutils literal">svg2mod</tt> is doing:</p>
<dl class="docutils">
<dt>Note:</dt>
<dd><p class="first">Right now, the input SVG's layers must have <em>ids</em> that match up KiCAD's s-exp layer names. Note that when you name
a layer in Inkscape that only sets a <tt class="docutils literal">name</tt> attribute, but does not change the ID. In order to change the ID in
Inkscape, you have to use Inkscape's &quot;object properties&quot; context menu function.</p>
<p>Also note that svg-flatten expects the layer names KiCAD uses in their S-Expression format. These are <em>different</em> to
the layer names KiCAD exposes in the UI (even though most of them match up!).</p>
<p class="last">For your convenience, there is an SVG template with all the right layer names and IDs located next to this README.</p>
</dd>
</dl>
<pre class="code shell literal-block">
<span class="lineno"></span><span class="line">svg-flatten<span class="w"> </span><span class="o">[</span>options<span class="o">]</span><span class="w"> </span>--format<span class="w"> </span>kicad<span class="w"> </span>--sexp-mod-name<span class="w"> </span>My_Module<span class="w"> </span><span class="o">[</span>input_file.svg<span class="o">]</span><span class="w"> </span><span class="o">[</span>output_file<span class="o">]</span>
</span></pre>
<div class="section" id="gerbolyze-template">
<h3><a class="toc-backref" href="#toc-entry-9"><tt class="docutils literal">gerbolyze template</tt></a></h3>
<p>Usage: <tt class="docutils literal">gerbolyze template [OPTIONS] INPUT</tt></p>
<p>Generate SVG template for gerbolyze paste from gerber files.</p>
<p>INPUT may be a gerber file, directory of gerber files or zip file with gerber files. The output file contains a preview
image of the input gerbers to allow you to position your artwork, as well as prepared Inkscape layers corresponding to
each gerber layer. Simply place your artwork in this SVG template using Inkscape. Starting in v3.0, gerbolyze
automatically keeps track of which board side (top or bottom) is contained in an SVG template.</p>
<div class="section" id="options">
<h4><a class="toc-backref" href="#toc-entry-10">Options:</a></h4>
<dl class="docutils">
<dt><tt class="docutils literal"><span class="pre">--top</span> | <span class="pre">--bottom</span></tt></dt>
<dd>Output top or bottom side template. This affects both the preview image and the prepared Inkscape layers.</dd>
<dt><tt class="docutils literal"><span class="pre">--vector</span> | <span class="pre">--raster</span></tt></dt>
<dd>Embed preview renders into output file as SVG vector graphics instead of rendering them to PNG bitmaps. The
resulting preview may slow down your SVG editor.</dd>
<dt><tt class="docutils literal"><span class="pre">--raster-dpi</span> FLOAT</tt></dt>
<dd>DPI for rastering preview</dd>
<dt><tt class="docutils literal"><span class="pre">--bbox</span> TEXT</tt></dt>
<dd>Output file bounding box. Format: &quot;w,h&quot; to force [w] mm by [h] mm output canvas OR &quot;x,y,w,h&quot; to force [w] mm by [h]
mm output canvas with its bottom left corner at the given input gerber coördinates.</dd>
</dl>
</div>
</div>
<div class="section" id="gerbolyze-paste">
<h3><a class="toc-backref" href="#toc-entry-11"><tt class="docutils literal">gerbolyze paste</tt></a></h3>
<p>(see <a class="reference internal" href="#vectorization">below</a>)</p>
<p>Usage: <tt class="docutils literal">gerbolyze paste [OPTIONS] INPUT_GERBERS OVERLAY_SVG OUTPUT_GERBERS</tt></p>
<p>Render vector data and raster images from SVG file into gerbers. The SVG input file can be generated using <tt class="docutils literal">gerbolyze
template</tt> and contains the name and board side of each layer. Note that for board outline layers, handling slightly
differs from other layers as PCB fabs do not support filled Gerber regions on these layers.</p>
<div class="section" id="options-1">
<h4><a class="toc-backref" href="#toc-entry-12">Options:</a></h4>
<dl class="docutils">
<dt><tt class="docutils literal"><span class="pre">--bbox</span> TEXT</tt></dt>
<dd>Output file bounding box. Format: &quot;w,h&quot; to force [w] mm by [h] mm output canvas OR &quot;x,y,w,h&quot; to force [w] mm by [h]
mm output canvas with its bottom left corner at the given input gerber coördinates. This <strong>must match the ``--bbox`` value given to
template</strong>!</dd>
<dt><tt class="docutils literal"><span class="pre">--subtract</span> TEXT</tt></dt>
<dd>Use user subtraction script from argument (see <a class="reference internal" href="#subtraction-script">below</a>)</dd>
<dt><tt class="docutils literal"><span class="pre">--no-subtract</span></tt></dt>
<dd>Disable subtraction (see <a class="reference internal" href="#subtraction-script">below</a>)</dd>
<dt><tt class="docutils literal"><span class="pre">--dilate</span> FLOAT</tt></dt>
<dd>Default dilation for subtraction operations in mm (see <a class="reference internal" href="#subtraction-script">below</a>)</dd>
<dt><tt class="docutils literal"><span class="pre">--trace-space</span> FLOAT</tt></dt>
<dd>Passed through to svg-flatten, see <a class="reference internal" href="#svg-flatten">below</a>.</dd>
<dt><tt class="docutils literal"><span class="pre">--vectorizer</span> TEXT</tt></dt>
<dd>Passed through to svg-flatten, see <a class="reference internal" href="#svg-flatten">its description below</a>. Also have a look at <a class="reference internal" href="#vectorization">the examples below</a>.</dd>
<dt><tt class="docutils literal"><span class="pre">--vectorizer-map</span> TEXT</tt></dt>
<dd>Passed through to svg-flatten, see <a class="reference internal" href="#svg-flatten">below</a>.</dd>
<dt><tt class="docutils literal"><span class="pre">--exclude-groups</span> TEXT</tt></dt>
<dd>Passed through to svg-flatten, see <a class="reference internal" href="#svg-flatten">below</a>.</dd>
</dl>
</div>
<div class="section" id="outline-layers-1">
<span id="outline-layers"></span><h4><a class="toc-backref" href="#toc-entry-13">Outline layers</a></h4>
<p>Outline layers require special handling since PCB fabs do not support filled G36/G37 polygons on these layers. The main
difference between normal layers and outline layers is how strokes are handled. On outline layers, strokes are
translated to normal Gerber draw commands (D01, D02 etc.) with an aperture set to the stroke's width instead of tracing
them to G36/G37 filled regions. This means that on outline layers, SVG end caps and line join types do not work: All
lines are redered with round joins and end caps.</p>
<p>One exception from this are patterns, which work as expected for both fills and strokes with full support for joins and
end caps.</p>
<p>Dashed strokes are supported on outline layers and can be used to make easy mouse bites.</p>
</div>
<div class="section" id="subtraction-scripts">
<span id="subtraction-script"></span><h4><a class="toc-backref" href="#toc-entry-14">Subtraction scripts</a></h4>
<img alt="pics/subtract_example.png" src="pics/subtract_example.png" style="width: 800px;" />
<p>Subtraction scripts tell <tt class="docutils literal">gerbolyze paste</tt> to remove an area around certain input layers to from an overlay layer.
When a input layer is given in the subtraction script, gerbolyze will dilate (extend outwards) everything on this input
layer and remove it from the target overlay layer. By default, Gerbolyze subtracts the mask layer from the silk layer to
make sure there are no silk primitives that overlap bare copper, and subtracts each input layer from its corresponding
overlay to make sure the two do not overlap. In the picture above you can see both at work: The overlay contains
halftone primitives all over the place. The subtraction script has cut out an area around all pads (mask layer) and all
existing silkscreen. You can turn off this behavior by passing <tt class="docutils literal"><span class="pre">--no-subtract</span></tt> or pass your own &quot;script&quot;.</p>
<p>The syntax of these scripts is:</p>
<pre class="code literal-block">
<span class="lineno"></span><span class="line">{target layer} -= {source layer} {dilation} [; ...]
</span></pre>
<p>The target layer must be <tt class="docutils literal"><span class="pre">out.{layer</span> name}</tt> and the source layer <tt class="docutils literal"><span class="pre">in.{layer</span> name}</tt>. The layer names are gerbolyze's
internal layer names, i.e.: <tt class="docutils literal">paste, silk, mask, copper, outline, drill</tt></p>
<p>The dilation value is optional, but can be a float with a leading <tt class="docutils literal">+</tt> or <tt class="docutils literal">-</tt>. If given, before subtraction the
source layer's features will be extended by that many mm. If not given, the dilation defaults to the value given by
<tt class="docutils literal"><span class="pre">--dilate</span></tt> if given or 0.1 mm otherwise. To disable dilation, simply pass <tt class="docutils literal">+0</tt> here.</p>
<p>Multiple commands can be separated by semicolons <tt class="docutils literal">;</tt> or line breaks.</p>
<p>The default subtraction script is:</p>
<pre class="code literal-block">
<span class="lineno"></span><span class="line">out.silk -= in.mask</span>
<span class="lineno"></span><span class="line">out.silk -= in.silk+0.5</span>
<span class="lineno"></span><span class="line">out.mask -= in.mask+0.5</span>
<span class="lineno"></span><span class="line">out.copper -= in.copper+0.5
</span></pre>
</div>
</div>
<div class="section" id="svg-flatten-1">
<span id="svg-flatten"></span><h3><a class="toc-backref" href="#toc-entry-15"><tt class="docutils literal"><span class="pre">svg-flatten</span></tt></a></h3>
<p>Usage: <tt class="docutils literal"><span class="pre">svg-flatten</span> <span class="pre">[OPTIONS]...</span> [INPUT_FILE] [OUTPUT_FILE]</tt></p>
<p>Specify <tt class="docutils literal">-</tt> for stdin/stdout.</p>
<div class="section" id="options-2">
<h4><a class="toc-backref" href="#toc-entry-16">Options:</a></h4>
<dl class="docutils">
<dt><tt class="docutils literal"><span class="pre">-h,</span> <span class="pre">--help</span></tt></dt>
<dd>Print help and exit</dd>
<dt><tt class="docutils literal"><span class="pre">-v,</span> <span class="pre">--version</span></tt></dt>
<dd>Print version and exit</dd>
<dt><tt class="docutils literal"><span class="pre">-o,</span> <span class="pre">--format</span></tt></dt>
<dd>Output format. Supported: gerber, gerber-outline (for board outline layers), svg, s-exp (KiCAD S-Expression)</dd>
<dt><tt class="docutils literal"><span class="pre">-p,</span> <span class="pre">--precision</span></tt></dt>
<dd>Number of decimal places use for exported coordinates (gerber: 1-9, SVG: &gt;=0). Note that not all gerber viewers are
happy with too many digits. 5 or 6 is a reasonable choice.</dd>
<dt><tt class="docutils literal"><span class="pre">--clear-color</span></tt></dt>
<dd>SVG color to use in SVG output for &quot;clear&quot; areas (default: white)</dd>
<dt><tt class="docutils literal"><span class="pre">--dark-color</span></tt></dt>
<dd>SVG color to use in SVG output for &quot;dark&quot; areas (default: black)</dd>
<dt><tt class="docutils literal"><span class="pre">-f,</span> <span class="pre">--flip-gerber-polarity</span></tt></dt>
<dd>Flip polarity of all output gerber primitives for --format gerber.</dd>
<dt><tt class="docutils literal"><span class="pre">-d,</span> <span class="pre">--trace-space</span></tt></dt>
<dd>Minimum feature size of elements in vectorized graphics (trace/space) in mm. Default: 0.1mm.</dd>
<dt><tt class="docutils literal"><span class="pre">--no-header</span></tt></dt>
<dd>Do not export output format header/footer, only export the primitives themselves</dd>
<dt><tt class="docutils literal"><span class="pre">--flatten</span></tt></dt>
<dd>Flatten output so it only consists of non-overlapping white polygons. This perform composition at the vector level.
Potentially slow. This defaults to on when using KiCAD S-Exp export because KiCAD does not know polarity or colors.</dd>
<dt><tt class="docutils literal"><span class="pre">--no-flatten</span></tt></dt>
<dd>Disable automatic flattening for KiCAD S-Exp export</dd>
<dt><tt class="docutils literal"><span class="pre">--dilate</span></tt></dt>
<dd>Dilate output gerber primitives by this amount in mm. Used for masking out other layers.</dd>
<dt><tt class="docutils literal"><span class="pre">-g,</span> <span class="pre">--only-groups</span></tt></dt>
<dd>Comma-separated list of group IDs to export.</dd>
<dt><tt class="docutils literal"><span class="pre">-b,</span> <span class="pre">--vectorizer</span></tt></dt>
<dd>Vectorizer to use for bitmap images. One of poisson-disc (default), hex-grid, square-grid, binary-contours,
dev-null. Have a look at <a class="reference internal" href="#vectorization">the examples below</a>.</dd>
<dt><tt class="docutils literal"><span class="pre">--vectorizer-map</span></tt></dt>
<dd><p class="first">Map from image element id to vectorizer. Overrides --vectorizer. Format: id1=vectorizer,id2=vectorizer,...</p>
<p class="last">You can use this to set a certain vectorizer for specific images, e.g. if you want to use both halftone
vectorization and contour tracing in the same SVG. Note that you can set an <tt class="docutils literal">&lt;image&gt;</tt> element's SVG ID from within
Inkscape though the context menu's Object Properties tool.</p>
</dd>
<dt><tt class="docutils literal"><span class="pre">--force-svg</span></tt></dt>
<dd>Force SVG input irrespective of file name</dd>
<dt><tt class="docutils literal"><span class="pre">--force-png</span></tt></dt>
<dd>Force bitmap graphics input irrespective of file name</dd>
<dt><tt class="docutils literal"><span class="pre">-s,</span> <span class="pre">--size</span></tt></dt>
<dd>Bitmap mode only: Physical size of output image in mm. Format: 12.34x56.78</dd>
<dt><tt class="docutils literal"><span class="pre">--sexp-mod-name</span></tt></dt>
<dd>Module name for KiCAD S-Exp output. This is a mandatory argument if using S-Exp output.</dd>
<dt><tt class="docutils literal"><span class="pre">--sexp-layer</span></tt></dt>
<dd>Layer for KiCAD S-Exp output. Defaults to auto-detect layers from SVG layer/top-level group IDs. If given, SVG
groups and layers are completely ignored and everything is simply vectorized into this layer, though you cna still
use <tt class="docutils literal"><span class="pre">-g</span></tt> for group selection.</dd>
<dt><tt class="docutils literal"><span class="pre">-a,</span> <span class="pre">--preserve-aspect-ratio</span></tt></dt>
<dd>Bitmap mode only: Preserve aspect ratio of image. Allowed values are meet, slice. Can also parse full SVG
preserveAspectRatio syntax.</dd>
<dt><tt class="docutils literal"><span class="pre">--no-usvg</span></tt></dt>
<dd>Do not preprocess input using usvg (do not use unless you know <em>exactly</em> what you're doing)</dd>
<dt><tt class="docutils literal"><span class="pre">--usvg-dpi</span></tt></dt>
<dd>Passed through to usvg's --dpi, in case the input file has different ideas of DPI than usvg has.</dd>
<dt><tt class="docutils literal"><span class="pre">--scale</span></tt></dt>
<dd>Scale input svg lengths by this factor (-o gerber only).</dd>
<dt><tt class="docutils literal"><span class="pre">-e,</span> <span class="pre">--exclude-groups</span></tt></dt>
<dd>Comma-separated list of group IDs to exclude from export. Takes precedence over --only-groups.</dd>
</dl>
</div>
</div>
</div>
<div class="section" id="gerbolyze-image-vectorization">
<span id="vectorization"></span><h2><a class="toc-backref" href="#toc-entry-17">Gerbolyze image vectorization</a></h2>
<p>Gerbolyze has two built-in strategies to translate pixel images into vector images. One is its built-in halftone
processor that tries to approximate grayscale. The other is its built-in binary vectorizer that traces contours in
black-and-white images. Below are examples for the four options.</p>
<p>The vectorizers can be used in isolation through <tt class="docutils literal"><span class="pre">svg-flatten</span></tt> with either an SVG input that contains an image or a
PNG/JPG input.</p>
<p>The vectorizer can be controlled globally using the <tt class="docutils literal"><span class="pre">--vectorizer</span></tt> flag in both <tt class="docutils literal">gerbolyze</tt> and <tt class="docutils literal"><span class="pre">svg-flatten</span></tt>. It
can also be set on a per-image basis in both using <tt class="docutils literal"><span class="pre">--vectorizer-map</span> [image svg <span class="pre">id]=[option][&quot;,&quot;</span> <span class="pre">...]</span></tt>.</p>
<!-- for f in vec_*.png; convert -background white -gravity center $f -resize 500x500 -extent 500x500 (basename -s .png $f)-square.png; end -->
<!-- for vec in hexgrid square poisson contours; convert vec_"$vec"_whole-square.png vec_"$vec"_detail-square.png -background transparent -splice 25x0+0+0 +append -chop 25x0+0+0 vec_"$vec"_composited.png; end -->
<div class="section" id="vectorizer-poisson-disc-the-default">
<h3><a class="toc-backref" href="#toc-entry-18"><tt class="docutils literal"><span class="pre">--vectorizer</span> <span class="pre">poisson-disc</span></tt> (the default)</a></h3>
<img alt="pics/vec_poisson_composited.png" src="pics/vec_poisson_composited.png" style="width: 800px;" />
</div>
<div class="section" id="vectorizer-hex-grid">
<h3><a class="toc-backref" href="#toc-entry-19"><tt class="docutils literal"><span class="pre">--vectorizer</span> <span class="pre">hex-grid</span></tt></a></h3>
<img alt="pics/vec_hexgrid_composited.png" src="pics/vec_hexgrid_composited.png" style="width: 800px;" />
</div>
<div class="section" id="vectorizer-square-grid">
<h3><a class="toc-backref" href="#toc-entry-20"><tt class="docutils literal"><span class="pre">--vectorizer</span> <span class="pre">square-grid</span></tt></a></h3>
<img alt="pics/vec_square_composited.png" src="pics/vec_square_composited.png" style="width: 800px;" />
</div>
<div class="section" id="vectorizer-binary-contours">
<h3><a class="toc-backref" href="#toc-entry-21"><tt class="docutils literal"><span class="pre">--vectorizer</span> <span class="pre">binary-contours</span></tt></a></h3>
<img alt="pics/vec_contours_composited.png" src="pics/vec_contours_composited.png" style="width: 800px;" />
<p>The binary contours vectorizer requires a black-and-white binary input image. As you can see, like every bitmap tracer
it will produce some artifacts. For artistic input this is usually not too bad as long as the input data is
high-resolution. Antialiased edges in the input image are not only OK, they may even help with an accurate
vectorization.</p>
</div>
</div>
<div class="section" id="gimp-halftone-preprocessing-guide">
<h2><a class="toc-backref" href="#toc-entry-22">GIMP halftone preprocessing guide</a></h2>
<p>Gerbolyze has its own built-in halftone processor, but you can also use the high-quality &quot;newsprint&quot; filter built into
<a class="reference external" href="https://gimp.org/">GIMP</a> instead if you like. This section will guide you through this. The PNG you get out of this can then be fed into
gerbolyze using <tt class="docutils literal"><span class="pre">--vectorizer</span> <span class="pre">binary-contours</span></tt>.</p>
<div class="section" id="import-your-desired-artwork">
<h3><a class="toc-backref" href="#toc-entry-23">1 Import your desired artwork</a></h3>
<p>Though anime or manga pictures are highly recommended, you can use any image including photographs. Be careful to select
a picture with comparatively low detail that remains recognizable at very low resolution. While working on a screen this
is hard to vizualize, but the grain resulting from the low resolution of a PCB's silkscreen is quite coarse.</p>
<img alt="screenshots/02import02.png" src="screenshots/02import02.png" style="width: 800px;" />
</div>
<div class="section" id="convert-the-image-to-grayscale">
<h3><a class="toc-backref" href="#toc-entry-24">2 Convert the image to grayscale</a></h3>
<img alt="screenshots/06grayscale.png" src="screenshots/06grayscale.png" style="width: 800px;" />
</div>
<div class="section" id="fine-tune-the-image-s-contrast">
<h3><a class="toc-backref" href="#toc-entry-25">3 Fine-tune the image's contrast</a></h3>
<p>To look well on the PCB, contrast is critical. If your source image is in color, you may have lost some contrast during
grayscale conversion. Now is the time to retouch that using the GIMP's color curve tool.</p>
<p>When using the GIMP's newsprint filter, bright grays close to white and dark grays close to black will cause very small
dots that might be beyond your PCB manufacturer's maximum resolution. To control this case, add small steps at the ends
of the grayscale value curve as shown (exaggerated) in the picture below. These steps saturate very bright grays to
white and very dark grays to black while preserving the values in the middle.</p>
<img alt="screenshots/08curve_cut.png" src="screenshots/08curve_cut.png" style="width: 800px;" />
</div>
<div class="section" id="retouch-details">
<h3><a class="toc-backref" href="#toc-entry-26">4 Retouch details</a></h3>
<p>Therer might be small details that don't look right yet, such as the image's background color or small highlights that
merge into the background now. You can manually change the color of any detail now using the GIMP's flood-fill tool.</p>
<p>If you don't want the image's background to show up on the final PCB at all, just make it black.</p>
<p>Particularly on low-resolution source images it may make sense to apply a blur with a radius similar to the following
newsprint filter's cell size (10px) to smooth out the dot pattern generated by the newsprint filter.</p>
<img alt="screenshots/09retouch.png" src="screenshots/09retouch.png" style="width: 800px;" />
<p>In the following example, I retouched the highlights in the hair of the character in the picture to make them completely
white instead of light-gray, so they still stand out nicely in the finished picture.</p>
<img alt="screenshots/10retouched.png" src="screenshots/10retouched.png" style="width: 800px;" />
</div>
<div class="section" id="run-the-newsprint-filter">
<h3><a class="toc-backref" href="#toc-entry-27">5 Run the newsprint filter</a></h3>
<p>Now, run the GIMP's newsprint filter, under filters, distorts, newsprint.</p>
<p>The first important settings is the spot size, which should be larger than your PCB's minimum detail size (about 10px
with <tt class="docutils literal">gerbolyze render</tt> default settings for good-quality silkscreen). In general the cheap and fast standard option of chinese PCB houses will require a larger detail size, but when you order specialty options like large size, 4-layer or non-green color along with a longer turnaround time you'll get much better-quality silk screen.</p>
<p>The second important setting is oversampling, which should be set to four or slightly higher. This improves the result
of the edge reconstruction of <tt class="docutils literal">gerbolyze vectorize</tt>.</p>
<img alt="screenshots/11newsprint.png" src="screenshots/11newsprint.png" style="width: 800px;" />
<p>The following are examples on the detail resulting from the newsprint filter.</p>
<img alt="screenshots/12newsprint.png" src="screenshots/12newsprint.png" style="width: 800px;" />
</div>
<div class="section" id="export-the-image-for-use-with-gerbolyze-vectorize">
<h3><a class="toc-backref" href="#toc-entry-28">6 Export the image for use with <tt class="docutils literal">gerbolyze vectorize</tt></a></h3>
<p>Simply export the image as a PNG file. Below are some pictures of the output <tt class="docutils literal">gerbolyze vectorize</tt> produced for this
example.</p>
<img alt="screenshots/14result_cut.png" src="screenshots/14result_cut.png" style="width: 800px;" />
<img alt="screenshots/15result_cut.png" src="screenshots/15result_cut.png" style="width: 800px;" />
</div>
</div>
<div class="section" id="manufacturing-considerations">
<h2><a class="toc-backref" href="#toc-entry-29">Manufacturing Considerations</a></h2>
<p>The main consideration when designing artwork for PCB processes is the processes' trace/space design rule. The two
things you can do here is one, to be creative with graphical parts of the design and avoid extremely narrow lines,
wedges or other thin features that will not come out well. Number two is to keep detail in raster images several times
larger than the manufacturing processes native capability. For example, to target a trace/space design rule of 100 µm,
the smallest detail in embedded raster graphics should not be much below 1mm.</p>
<p>Gerbolyze's halftone vectorizers have built-in support for trace/space design rules. While they can still produce small
artifacts that violate these rules, their output should be close enough to satifsy board houses and close enough for the
result to look good. The way gerbolyze does this is to clip the halftone cell's values to zero whenevery they get too
small, and to forcefully split or merge two neighboring cells when they get too close. While this process introduces
slight steps at the top and bottom of grayscale response, for most inputs these are not noticeable.</p>
<p>On the other hand, for SVG vector elements as well as for traced raster images, Gerbolyze cannot help with these design
rules. There is no heuristic that would allow Gerbolyze to non-destructively &quot;fix&quot; a design here, so all that's on the
roadmap here is to eventually include a gerber-level design rule checker.</p>
<p>As far as board houses go, I have made good experiences with the popular Chinese board houses. In my experience, JLC
will just produce whatever you send them with little fucks being given about design rule adherence or validity of the
input gerbers. This is great if you just want artistic circuit boards without much of a hassle, and you don't care if
they come out exactly as you imagined. The worst I've had happen was when an older version of gerbolyze generated
polygons with holes assuming standard fill-rule processing. The in the board house's online gerber viewer things looked
fine, and neither did they complain during file review. However, the resulting boards looked completely wrong because
all the dark halftones were missing.</p>
<p>PCBWay on the other hand has a much more rigurous file review process. They &lt;em&gt;will&lt;/em&gt; complain when you throw
illegal garbage gerbers at them, and they will helpfully guide you through your design rule violations. In this way you
get much more of a professional service from them and for designs that have to be functional their higher level of
scrutiny definitely is a good thing. For the design you saw in the first picture in this article, I ended up begging
them to just plot my files if it doesn't physically break their machines and to their credit, while they seemed unhappy
about it they did it and the result looks absolutely stunning.</p>
<p>PCBWay is a bit more expensive on their lowest-end offering than JLC, but I found that for anything else (large boards,
multi-layer, gold plating etc.) their prices match. PCBWay offers a much broader range of manufacturing options such as
flexible circuit boards, multi-layer boards, thick or thin substrates and high-temperature substrates.</p>
<p>When in doubt about how your design is going to come out on the board, do not hesitate to contact your board house. Most
of the end customer-facing online PCB services have a number of different factories that do a number of different
fabrication processes for them depending on order parameters. Places like PCBWay have exceptional quality control and
good customer service, but that is mostly focused on the technical aspects of the PCB. If you rely on visual aspects
like silkscreen uniformity or solder mask color that is a strong no concern to everyone else in the electronics
industry, you may find significant variations between manufacturers or even between orders with the same manufacturer
and you may encounter challenges communicating your requirements.</p>
</div>
<div class="section" id="limitations">
<h2><a class="toc-backref" href="#toc-entry-30">Limitations</a></h2>
<div class="section" id="svg-raster-features">
<h3><a class="toc-backref" href="#toc-entry-31">SVG raster features</a></h3>
<p>Currently, SVG masks and filters are not supported. Though SVG is marketed as a &quot;vector graphics format&quot;, these two
features are really raster primitives that all SVG viewers perform at the pixel level after rasterization. Since
supporting these would likely not end up looking like what you want, it is not a planned feature. If you need masks or
filters, simply export the relevant parts of the SVG as a PNG then include that in your template.</p>
</div>
<div class="section" id="gerber-pass-through">
<h3><a class="toc-backref" href="#toc-entry-32">Gerber pass-through</a></h3>
<p>Since gerbolyze has to composite your input gerbers with its own output, it has to fully parse and re-serialize them.
gerbolyze <a class="reference external" href="https://gitlab.com/gerbolyze/gerbonara">gerbonara</a> for all its gerber parsing needs. Thus, gerbonara will interpret your gerbers and output will be in
gerbonara's gerber &quot;dialect&quot;. If you find a corner case where this does not work and the output looks wrong, please file
a bug report with an example file on the <a class="reference external" href="https://gitlab.com/gerbolyze/gerbonara">gerbonara</a> bug tracker. <em>Always</em> check the output files for errors before
submitting them to production.</p>
<p>Gerbolyze is provided without any warranty, but still please open an issue or <a class="reference external" href="mailto:gerbolyze&#64;jaseg.de">send me an email</a> if you find any errors or inconsistencies.</p>
</div>
<div class="section" id="trace-space-design-rule-adherence">
<h3><a class="toc-backref" href="#toc-entry-33">Trace/Space design rule adherence</a></h3>
<p>While the grayscale halftone vectorizers do a reasonable job adhering to a given trace/space design rule, they can still
produce small parts of output that violate it. For the contour vectorizer as well as for all SVG primitives, you are
responsible for adhering to design rules yourself as there is no algorithm that gerboyze could use to &quot;fix&quot; its input.</p>
<p>A design rule checker is planned as a future addition to gerbolyze, but is not yet part of it. If in doubt, talk to your
fab and consider doing a test run of your design before ordering assembled boards ;)</p>
</div>
</div>
<div class="section" id="gallery">
<h2><a class="toc-backref" href="#toc-entry-34">Gallery</a></h2>
<img alt="pics/sample3.jpg" src="pics/sample3.jpg" style="width: 400px;" />
<p>For a demonstration of <tt class="docutils literal">gerbolyze convert</tt>, check out the <a class="reference external" href="https://dyna.kokoroyukuma.de/protos/">Gerbolyze Protoboard Index</a>, where you can download gerber
files for over 7.000 SMD and THT protoboard layouts.</p>
</div>
<div class="section" id="licensing">
<h2><a class="toc-backref" href="#toc-entry-35">Licensing</a></h2>
<p>This tool is licensed under the rather radical AGPLv3 license. Briefly, this means that you have to provide users of a
webapp using this tool in the backend with this tool's source.</p>
<p>I get that some people have issues with the AGPL. In case this license prevents you from using this software, please
send me <a class="reference external" href="mailto:agpl.sucks&#64;jaseg.de">an email</a> and I can grant you an exception. I want this software to be useful to as
many people as possible and I wouldn't want the license to be a hurdle to anyone. OTOH I see a danger of some cheap
board house just integrating a fork into their webpage without providing their changes back upstream, and I want to
avoid that so the default license is still AGPL.</p>
</div>
</div>
</main><footer>
Copyright © 2023 Jan Sebastian Götte
/ <a href="/about/">About</a>
/ <a href="/imprint/">Imprint</a>
</footer>
<script>
if(navigator.getEnvironmentIntegrity!==undefined)document.querySelector('body').innerHTML=`<h1>Your browser
contains Google DRM</h1>"Web Environment Integrity" is a Google euphemism for a DRM that is designed to
prevent ad-blocking, and which Google has forced into their browsers against widespread public opposition.
In support of an open web, this website does not function with this DRM. Please install a browser such
as <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> that respects your freedom and supports
ad blockers.`;
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,165 @@
<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<title>Gerbonara | Home</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="color-scheme" content="dark light">
<link rel="stylesheet" href="/style.css">
</head>
<body><nav>
<div class="internal">
<a href="/" title="Home">Home</a>
<a href="/blog/" title="Blog">Blog</a>
<a href="/projects/" title="Projects">Projects</a>
<a href="/about/" title="About">About</a>
</div>
<div class="external">
<a href="https://git.jaseg.de/" title="cgit">cgit</a>
<a href="https://github.com/jaseg" title="Github">Github</a>
<a href="https://gitlab.com/neinseg" title="Gitlab">Gitlab</a>
<a href="https://chaos.social/jaseg" title="Mastodon">Mastodon</a>
</span>
</nav>
<header>
<h1>Gerbonara</h1>
<ul class="breadcrumbs">
<li><a href="/">jaseg.de</a></li>
<li><a href="/projects/">Projects</a></li><li><a href="/projects/gerbonara/">Gerbonara</a></li>
</ul>
</header>
<main>
<div class="links">
<a href="https://git.jaseg.de/gerbonara.git">Sources</a>
<a href="https://gitlab.com/gerbolyze/gerbonara/issues">Issues</a>
<a href="https://gerbolyze.gitlab.io/gerbonara">Docs</a>
<a href="https://pypi.org/project/gerbonara">PyPI</a>
</div>
<div class="document">
<p>Gerbonara is a library to read, modify and write PCB manufacturing files such as Gerber, Excellon and IPC-356 through a
pythonic API. Gerbonara can open a folder of manufacturing files, and parse file names and metadata to figure out which
file contains what. Gerbonara is tested using an extensive library of real-world example files from CAD tools including
KiCAD, Altium, Eagle, Allegro, gEDA, Fritzing, Siemens/Mentor Graphics PADS, and Target3001!.</p>
<p>Gerbonara's API is built on two principles:</p>
<dl class="docutils">
<dt><strong>Meaningful, object-oriented API</strong></dt>
<dd>Gerbonara abstracts away the details of the underlying file format such as tool indices, coordinate notation and
graphical state, and presents meaningful &quot;graphical objects&quot; such as a <cite>primitives.Line</cite>,
<cite>primitives.Arc</cite>, or <cite>Region</cite> through its API. These objects can be easily created,
manipulated or deleted from code without breaking anything else. You can even copy graphical objects between files,
and Gerbonara will automatically convert coordinate format, units etc. for you. <cite>GerberFile</cite> and
<cite>ExcellonFile</cite> use the same types of <cite>graphic objects &lt;object-api&gt;</cite>, so objects can be directly
copied between file types without conversion.</dd>
<dt><strong>Unit-safety</strong></dt>
<dd>Gerbonara embeds physical <cite>LengthUnit</cite> information in all objects. The high-level API such as
<cite>LayerStack.merge</cite> or <cite>GerberFile.offset</cite> accepts arguments with an explicitly given unit and
automatically converts them as needed. Objects can be copied between <cite>GerberFile</cite> instances and unit
conversion will be handled transparently in the background.</dd>
</dl>
<p>Gerbonara was started as an extensive refactoring of the <a class="reference external" href="https://github.com/opiopan/pcb-tools-extension">pcb-tools</a> and <a class="reference external" href="https://github.com/curtacircuitos/pcb-tools/issues">pcb-tools-extension</a> packages. Both of these
have statement-based APIs, that is, they parse input files into one python object for every line in the file. This means
that when saving files they can recreate the input file almost byte by byte, but manipulating a file by changing
statements without breaking things is <em>hard</em>.</p>
<p>Gerbonara powers <a class="reference external" href="https://github.com/jaseg/gerbolyze">gerbolyze</a>, a tool for converting <a class="reference external" href="https://en.wikipedia.org/wiki/Scalable_Vector_Graphics">SVG</a> vector graphics files into Gerber, and embedding <a class="reference external" href="https://en.wikipedia.org/wiki/Scalable_Vector_Graphics">SVG</a> into
existing Gerber files exported from a normal PCB tool for artistic purposes.</p>
<div class="section" id="features">
<h2>Features</h2>
<blockquote>
<ul class="simple">
<li>File I/O
* Gerber, Excellon (drill file), IPC-356 (netlist) read and write
* supports file-level operations: offset, rotate, merge for all file types</li>
<li>Modification API (<cite>GraphicObject</cite>)</li>
<li>Rendering API (<cite>GraphicPrimitive</cite>)</li>
<li>SVG export</li>
<li>Full aperture macro support, including transformations (offset, rotation)</li>
</ul>
</blockquote>
</div>
<div class="section" id="quick-start">
<h2>Quick Start</h2>
<p>First, install gerbonara from PyPI using pip:</p>
<pre class="code shell literal-block">
<span class="lineno"></span><span class="line">pip<span class="w"> </span>install<span class="w"> </span>--user<span class="w"> </span>gerbonara
</span></pre>
<p>Then, you are ready to read and write gerber files:</p>
<pre class="code python literal-block">
<span class="lineno"></span><span class="line"><span class="kn">from</span> <span class="nn">gerbonara</span> <span class="kn">import</span> <span class="n">LayerStack</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">stack</span> <span class="o">=</span> <span class="n">LayerStack</span><span class="o">.</span><span class="n">from_directory</span><span class="p">(</span><span class="s1">'output/gerber'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">w</span><span class="p">,</span> <span class="n">h</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">outline</span><span class="o">.</span><span class="n">size</span><span class="p">(</span><span class="s1">'mm'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'Board size is </span><span class="si">{</span><span class="n">w</span><span class="si">:</span><span class="s1">.1f</span><span class="si">}</span><span class="s1"> mm x </span><span class="si">{</span><span class="n">h</span><span class="si">:</span><span class="s1">.1f</span><span class="si">}</span><span class="s1"> mm'</span><span class="p">)</span>
</span></pre>
</div>
<div class="section" id="command-line-interface">
<h2>Command-Line Interface</h2>
<p>Gerbonara comes with a <cite>built-in command-line interface&lt;cli-doc&gt;</cite> that has functions for analyzing, rendering,
modifying, and merging Gerber files. To access it, use either the <tt class="docutils literal">gerbonara</tt> command that is part of the python
package, or run <tt class="docutils literal">python <span class="pre">-m</span> gerbonara</tt> For a list of functions or help on their usage, you can use:</p>
<pre class="code console literal-block">
<span class="lineno"></span><span class="line"><span class="gp">$ </span>python<span class="w"> </span>-m<span class="w"> </span>gerbonara<span class="w"> </span>--help<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="go">[...]</span></span>
<span class="lineno"></span><span class="line"><span class="go"></span><span class="gp">$ </span>python<span class="w"> </span>-m<span class="w"> </span>gerbonara<span class="w"> </span>render<span class="w"> </span>--help
</span></pre>
</div>
<div class="section" id="development">
<h2>Development</h2>
<p>Gerbonara is developed on Gitlab under the gerbolyze org:</p>
<p><a class="reference external" href="https://gitlab.com/gerbolyze/gerbonara/">https://gitlab.com/gerbolyze/gerbonara/</a></p>
<p>A mirror of the repository can be found at:</p>
<p><a class="reference external" href="https://git.jaseg.de/gerbonara">https://git.jaseg.de/gerbonara</a></p>
<p>Our issue tracker is also on Gitlab:</p>
<p><a class="reference external" href="https://gitlab.com/gerbolyze/gerbonara/-/issues">https://gitlab.com/gerbolyze/gerbonara/-/issues</a></p>
<p>The documentation can be found at gitlab:</p>
<p><a class="reference external" href="https://gerbolyze.gitlab.io/gerbonara/">https://gerbolyze.gitlab.io/gerbonara/</a></p>
<p>With Gerbonara, we aim to support as many different format variants as possible. If you have a file that Gerbonara can't
open, please file an issue on our issue tracker. Even if Gerbonara can open all your files, for regression testing we
are very interested in example files generated by any CAD or CAM tool that is not already on the list of supported
tools.</p>
</div>
<div class="section" id="supported-cad-tools">
<h2>Supported CAD Tools</h2>
<p>Compatibility with the output of these CAD tools is tested as part of our test suite using example files generated by
these tools. Note that not all of these tools come with default Gerber file naming rules, so YMMV if your Gerbers use
some non-standard naming convention.</p>
<blockquote>
<ul class="simple">
<li>Allegro</li>
<li>Altium</li>
<li>Diptrace</li>
<li>Eagle</li>
<li>EasyEDA</li>
<li>Fritzing</li>
<li>gEDA</li>
<li>KiCAD</li>
<li>pcb-rnd</li>
<li>Siemens / Mentor Graphics Xpedition</li>
<li>Siemens PADS</li>
<li>Target 3001!</li>
<li>Upverter</li>
<li>Zuken CR-8000</li>
</ul>
</blockquote>
</div>
</div>
</main><footer>
Copyright © 2023 Jan Sebastian Götte
/ <a href="/about/">About</a>
/ <a href="/imprint/">Imprint</a>
</footer>
<script>
if(navigator.getEnvironmentIntegrity!==undefined)document.querySelector('body').innerHTML=`<h1>Your browser
contains Google DRM</h1>"Web Environment Integrity" is a Google euphemism for a DRM that is designed to
prevent ad-blocking, and which Google has forced into their browsers against widespread public opposition.
In support of an open web, this website does not function with this DRM. Please install a browser such
as <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> that respects your freedom and supports
ad blockers.`;
</script>
</body>
</html>

163
projects/index.html Normal file
View file

@ -0,0 +1,163 @@
<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<title>Projects | Home</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="color-scheme" content="dark light">
<link rel="stylesheet" href="/style.css">
</head>
<body><nav>
<div class="internal">
<a href="/" title="Home">Home</a>
<a href="/blog/" title="Blog">Blog</a>
<a href="/projects/" title="Projects" class="active">Projects</a>
<a href="/about/" title="About">About</a>
</div>
<div class="external">
<a href="https://git.jaseg.de/" title="cgit">cgit</a>
<a href="https://github.com/jaseg" title="Github">Github</a>
<a href="https://gitlab.com/neinseg" title="Gitlab">Gitlab</a>
<a href="https://chaos.social/jaseg" title="Mastodon">Mastodon</a>
</span>
</nav>
<header>
<h1>Projects</h1>
<ul class="breadcrumbs">
<li><a href="/">jaseg.de</a></li><li><a href="/projects/">Projects</a></li>
</ul>
</header>
<main class="cards">
<div class="intro">
<div class="document">
<p>I maintain a number of open-source projects. Most of these I started out of some personal need or interest.
I strive to keep all of them up to date and maintained, so if you notice an issue with one of them, please
open an issue on the project's issue tracker.</p>
</div>
</div>
<div class="card"><h2><a href="/projects/gerbolyze/">Gerbolyze</a></h2>
<div class="summary">
<div class="document">
<p>Gerbolyze is a tool that allows the modification of Gerber PCB artwork with a vector graphics editor like Inkscape. Gerbolyze directly converts between SVG and Gerber, and accurately reproduces details that other tools can not.</p>
</div>
<a href="http://jaseg.de/projects/gerbolyze/">Read more</a>
</div>
<div class="links">
<a href="https://git.jaseg.de/gerbolyze.git">Sources</a>
<a href="https://github.com/jaseg/gerbolyze/issues">Issues</a>
<a href="https://gerbolyze.gitlab.io/gerbolyze">Docs</a>
<a href="https://pypi.org/project/gerbolyze">PyPI</a>
</div>
</div>
<div class="card"><h2><a href="/projects/gerbonara/">Gerbonara</a></h2>
<div class="summary">
<div class="document">
<p>Gerbonara is a user-friendly, powerful tool for reading, writing, modification and rendering of Gerber PCB artwork from the command line or from Python code. Gerbonara supports the Gerber dialects of all industry-standard EDA tools.</p>
</div>
<a href="http://jaseg.de/projects/gerbonara/">Read more</a>
</div>
<div class="links">
<a href="https://git.jaseg.de/gerbonara.git">Sources</a>
<a href="https://gitlab.com/gerbolyze/gerbonara/issues">Issues</a>
<a href="https://gerbolyze.gitlab.io/gerbonara">Docs</a>
<a href="https://pypi.org/project/gerbonara">PyPI</a>
</div>
</div>
<div class="card"><h2><a href="/projects/lolcat-c/">lolcat-c</a></h2>
<div class="summary">
<div class="document">
<p>lolcat-c is a small, high-performance re-implementation of the <a class="reference external" href="https://github.com/busyloop/lolcat">lolcat</a> rainbow cat utility. lolcat-c is meant as a lolcat that you can actually use in production. It is fast, not slowing down whatever you pipe through it, and it robustly handles real-world terminal output including escape sequences.</p>
</div>
<a href="http://jaseg.de/projects/lolcat-c/">Read more</a>
</div>
<div class="links">
<a href="https://git.jaseg.de/lolcat.git">Sources</a>
<a href="https://github.com/jaseg/lolcat">Github</a>
<a href="https://github.com/jaseg/lolcat/issues">Issues</a>
</div>
</div>
<div class="card"><h2><a href="/projects/python-mpv/">python-mpv</a></h2>
<div class="summary">
<div class="document">
<p>python-mpv is a small, ctypes-based Python library wrapping the libmpv media player library. Despite its small size and simple API, python-mpv allows advanced control over libmpv and beyond simple remote control of mpv can be used to embed mpv in OpenGL, Qt, and GTK-based Python applications.</p>
</div>
<a href="http://jaseg.de/projects/python-mpv/">Read more</a>
</div>
<div class="links">
<a href="https://git.jaseg.de/python-mpv.git">Sources</a>
<a href="https://github.com/jaseg/python-mpv/issues">Issues</a>
<a href="https://neinseg.gitlab.io/python-mpv">Docs</a>
<a href="https://pypi.org/project/mpv">PyPI</a>
</div>
</div>
<div class="card"><h2><a href="/projects/svg-flatten/">svg-flatten</a></h2>
<div class="summary">
<div class="document">
<p>svg-flatten is a command-line utility that performs vector occlusion and clipping on SVG files, producing a flattened SVG file without overlapping elements, without changing what the file looks like. svg-flatten is used as a part of gerbolyze.</p>
</div>
<a href="http://jaseg.de/projects/svg-flatten/">Read more</a>
</div>
<div class="links">
<a href="https://git.jaseg.de/gerbolyze.git/tree/svg-flatten?h=main">Sources</a>
<a href="https://github.com/jaseg/gerbolyze/issues">Issues</a>
<a href="https://gerbolyze.gitlab.io/svg-flatten">Docs</a>
</div>
</div>
<div class="card"><h2><a href="/projects/wsdiff/">wsdiff</a></h2>
<div class="summary">
<div class="document">
<p>wsdiff is a command-line utility that produces self-contained, syntax-highlighted, HTML-formatted diffs that support both unified and side-by-side diffs from a single source file using nothing but CSS magic.</p>
</div>
<a href="http://jaseg.de/projects/wsdiff/">Read more</a>
</div>
<div class="links">
<a href="https://git.jaseg.de/wsdiff.git">Sources</a>
<a href="https://github.com/jaseg/wsdiff/issues">Issues</a>
<a href="https://pypi.org/project/wsdiff">PyPI</a>
</div>
</div>
</main><footer>
Copyright © 2023 Jan Sebastian Götte
/ <a href="/about/">About</a>
/ <a href="/imprint/">Imprint</a>
</footer>
<script>
if(navigator.getEnvironmentIntegrity!==undefined)document.querySelector('body').innerHTML=`<h1>Your browser
contains Google DRM</h1>"Web Environment Integrity" is a Google euphemism for a DRM that is designed to
prevent ad-blocking, and which Google has forced into their browsers against widespread public opposition.
In support of an open web, this website does not function with this DRM. Please install a browser such
as <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> that respects your freedom and supports
ad blockers.`;
</script>
</body>
</html>

89
projects/index.xml Normal file
View file

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Projects on Home</title>
<link>http://jaseg.de/projects/</link>
<description>Recent content in Projects on Home</description>
<generator>Hugo -- gohugo.io</generator>
<language>en-us</language>
<copyright>Jan Sebastian Götte</copyright><atom:link href="http://jaseg.de/projects/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>Gerbolyze</title>
<link>http://jaseg.de/projects/gerbolyze/</link>
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
<guid>http://jaseg.de/projects/gerbolyze/</guid>
<description>&lt;div class=&#34;document&#34;&gt;
&lt;p&gt;Gerbolyze is a tool that allows the modification of Gerber PCB artwork with a vector graphics editor like Inkscape. Gerbolyze directly converts between SVG and Gerber, and accurately reproduces details that other tools can not.&lt;/p&gt;
&lt;/div&gt;</description>
</item>
<item>
<title>Gerbonara</title>
<link>http://jaseg.de/projects/gerbonara/</link>
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
<guid>http://jaseg.de/projects/gerbonara/</guid>
<description>&lt;div class=&#34;document&#34;&gt;
&lt;p&gt;Gerbonara is a user-friendly, powerful tool for reading, writing, modification and rendering of Gerber PCB artwork from the command line or from Python code. Gerbonara supports the Gerber dialects of all industry-standard EDA tools.&lt;/p&gt;
&lt;/div&gt;</description>
</item>
<item>
<title>lolcat-c</title>
<link>http://jaseg.de/projects/lolcat-c/</link>
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
<guid>http://jaseg.de/projects/lolcat-c/</guid>
<description>&lt;div class=&#34;document&#34;&gt;
&lt;p&gt;lolcat-c is a small, high-performance re-implementation of the &lt;a class=&#34;reference external&#34; href=&#34;https://github.com/busyloop/lolcat&#34;&gt;lolcat&lt;/a&gt; rainbow cat utility. lolcat-c is meant as a lolcat that you can actually use in production. It is fast, not slowing down whatever you pipe through it, and it robustly handles real-world terminal output including escape sequences.&lt;/p&gt;
&lt;/div&gt;</description>
</item>
<item>
<title>python-mpv</title>
<link>http://jaseg.de/projects/python-mpv/</link>
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
<guid>http://jaseg.de/projects/python-mpv/</guid>
<description>&lt;div class=&#34;document&#34;&gt;
&lt;p&gt;python-mpv is a small, ctypes-based Python library wrapping the libmpv media player library. Despite its small size and simple API, python-mpv allows advanced control over libmpv and beyond simple remote control of mpv can be used to embed mpv in OpenGL, Qt, and GTK-based Python applications.&lt;/p&gt;
&lt;/div&gt;</description>
</item>
<item>
<title>svg-flatten</title>
<link>http://jaseg.de/projects/svg-flatten/</link>
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
<guid>http://jaseg.de/projects/svg-flatten/</guid>
<description>&lt;div class=&#34;document&#34;&gt;
&lt;p&gt;svg-flatten is a command-line utility that performs vector occlusion and clipping on SVG files, producing a flattened SVG file without overlapping elements, without changing what the file looks like. svg-flatten is used as a part of gerbolyze.&lt;/p&gt;
&lt;/div&gt;</description>
</item>
<item>
<title>wsdiff</title>
<link>http://jaseg.de/projects/wsdiff/</link>
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
<guid>http://jaseg.de/projects/wsdiff/</guid>
<description>&lt;div class=&#34;document&#34;&gt;
&lt;p&gt;wsdiff is a command-line utility that produces self-contained, syntax-highlighted, HTML-formatted diffs that support both unified and side-by-side diffs from a single source file using nothing but CSS magic.&lt;/p&gt;
&lt;/div&gt;</description>
</item>
</channel>
</rss>

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -0,0 +1,132 @@
<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<title>lolcat-c | Home</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="color-scheme" content="dark light">
<link rel="stylesheet" href="/style.css">
</head>
<body><nav>
<div class="internal">
<a href="/" title="Home">Home</a>
<a href="/blog/" title="Blog">Blog</a>
<a href="/projects/" title="Projects">Projects</a>
<a href="/about/" title="About">About</a>
</div>
<div class="external">
<a href="https://git.jaseg.de/" title="cgit">cgit</a>
<a href="https://github.com/jaseg" title="Github">Github</a>
<a href="https://gitlab.com/neinseg" title="Gitlab">Gitlab</a>
<a href="https://chaos.social/jaseg" title="Mastodon">Mastodon</a>
</span>
</nav>
<header>
<h1>lolcat-c</h1>
<ul class="breadcrumbs">
<li><a href="/">jaseg.de</a></li>
<li><a href="/projects/">Projects</a></li><li><a href="/projects/lolcat-c/">lolcat-c</a></li>
</ul>
</header>
<main>
<div class="links">
<a href="https://git.jaseg.de/lolcat.git">Sources</a>
<a href="https://github.com/jaseg/lolcat">Github</a>
<a href="https://github.com/jaseg/lolcat/issues">Issues</a>
</div>
<div class="document">
<div class="section" id="what">
<h2>What?</h2>
<img alt="LOLCat-Rainbow.jpg" src="LOLCat-Rainbow.jpg" />
</div>
<div class="section" id="screenshot">
<h2>Screenshot</h2>
<img alt="screenshot.png" src="screenshot.png" />
<img alt="sl.gif" src="sl.gif" />
</div>
<div class="section" id="installation">
<h2>Installation</h2>
<div class="section" id="archlinux">
<h3>Archlinux</h3>
<p>There's an <a class="reference external" href="https://aur.archlinux.org/packages/c-lolcat">AUR package</a>:</p>
<pre class="code sh literal-block">
<span class="lineno"></span><span class="line">$<span class="w"> </span>git<span class="w"> </span>clone<span class="w"> </span>https://aur.archlinux.org/packages/c-lolcat<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>$<span class="w"> </span><span class="nb">cd</span><span class="w"> </span>c-lolcat<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>$<span class="w"> </span>makepkg<span class="w"> </span>-csi
</span></pre>
</div>
<div class="section" id="fedora">
<h3>Fedora</h3>
<pre class="code sh literal-block">
<span class="lineno"></span><span class="line">$<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>lolcat
</span></pre>
</div>
<div class="section" id="ubuntu-snap">
<h3>Ubuntu (Snap)</h3>
<p>See <a class="reference external" href="https://blog.simos.info/how-to-make-a-snap-package-for-lolcat-with-snapcraft-on-ubuntu/">this awesome blog post by a kind person from the internet</a>:</p>
<pre class="code sh literal-block">
<span class="lineno"></span><span class="line">$<span class="w"> </span>snap<span class="w"> </span>install<span class="w"> </span>lolcat-c
</span></pre>
</div>
<div class="section" id="mac">
<h3>Mac</h3>
<p>Build loclcat with:</p>
<pre class="code sh literal-block">
<span class="lineno"></span><span class="line">$<span class="w"> </span>make<span class="w"> </span>lolcat
</span></pre>
<p>...and put the resulting binary at a place of your choice.</p>
</div>
<div class="section" id="others">
<h3>Others</h3>
<pre class="code sh literal-block">
<span class="lineno"></span><span class="line">$<span class="w"> </span>make<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>sudo<span class="w"> </span>make<span class="w"> </span>install
</span></pre>
</div>
</div>
<div class="section" id="why">
<h2>Why?</h2>
<p>This <cite>lolcat</cite> clone is an attempt to reduce the world's carbon dioxide emissions by optimizing inefficient code. It's
&gt;10x as fast and &lt;0.1% as large as the original one.</p>
<pre class="code sh literal-block">
<span class="lineno"></span><span class="line">newton~/d/lolcat<span class="w"> </span>&lt;<span class="m">3</span><span class="w"> </span>dmesg&gt;foo<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>newton~/d/lolcat<span class="w"> </span>&lt;<span class="m">3</span><span class="w"> </span><span class="nb">time</span><span class="w"> </span>upstream/bin/lolcat<span class="w"> </span>foo<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="m">13</span>.51user<span class="w"> </span><span class="m">1</span>.34system<span class="w"> </span><span class="m">0</span>:15.99elapsed<span class="w"> </span><span class="m">92</span>%CPU<span class="w"> </span><span class="o">(</span>0avgtext+0avgdata<span class="w"> </span>10864maxresident<span class="o">)</span>k<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>0inputs+0outputs<span class="w"> </span><span class="o">(</span>0major+1716minor<span class="o">)</span>pagefaults<span class="w"> </span>0swaps<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>newton~/d/lolcat<span class="w"> </span>&lt;<span class="m">3</span><span class="w"> </span><span class="nb">time</span><span class="w"> </span>./lolcat<span class="w"> </span>foo<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="m">0</span>.02user<span class="w"> </span><span class="m">0</span>.00system<span class="w"> </span><span class="m">0</span>:00.09elapsed<span class="w"> </span><span class="m">34</span>%CPU<span class="w"> </span><span class="o">(</span>0avgtext+0avgdata<span class="w"> </span>1936maxresident<span class="o">)</span>k<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>0inputs+0outputs<span class="w"> </span><span class="o">(</span>0major+117minor<span class="o">)</span>pagefaults<span class="w"> </span>0swaps
</span></pre>
<p>Bonus comparison with <a class="reference external" href="https://github.com/tehmaze/lolcat/">python-lolcat</a>:</p>
<pre class="code sh literal-block">
<span class="lineno"></span><span class="line">newton~/d/lolcat<span class="w"> </span>&lt;<span class="m">3</span><span class="w"> </span>dmesg&gt;foo<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>$<span class="w"> </span><span class="nb">time</span><span class="w"> </span>python-lolcat<span class="w"> </span>foo<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="m">12</span>.27user<span class="w"> </span><span class="m">0</span>.00system<span class="w"> </span><span class="m">0</span>:12.29elapsed<span class="w"> </span><span class="m">99</span>%CPU<span class="w"> </span><span class="o">(</span>0avgtext+0avgdata<span class="w"> </span>11484maxresident<span class="o">)</span>k<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>0inputs+0outputs<span class="w"> </span><span class="o">(</span>0major+1627minor<span class="o">)</span>pagefaults<span class="w"> </span>0swaps<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>$<span class="w"> </span><span class="nb">time</span><span class="w"> </span>c-lolcat<span class="w"> </span>foo<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="m">0</span>.29user<span class="w"> </span><span class="m">0</span>.00system<span class="w"> </span><span class="m">0</span>:00.30elapsed<span class="w"> </span><span class="m">98</span>%CPU<span class="w"> </span><span class="o">(</span>0avgtext+0avgdata<span class="w"> </span>468maxresident<span class="o">)</span>k<span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>0inputs+0outputs<span class="w"> </span><span class="o">(</span>0major+21minor<span class="o">)</span>pagefaults<span class="w"> </span>0swaps
</span></pre>
<p>(Read: <cite>c-lolcat &lt;&lt; python-lolcat &lt;&lt; ruby-lolcat</cite>)</p>
</div>
</div>
</main><footer>
Copyright © 2023 Jan Sebastian Götte
/ <a href="/about/">About</a>
/ <a href="/imprint/">Imprint</a>
</footer>
<script>
if(navigator.getEnvironmentIntegrity!==undefined)document.querySelector('body').innerHTML=`<h1>Your browser
contains Google DRM</h1>"Web Environment Integrity" is a Google euphemism for a DRM that is designed to
prevent ad-blocking, and which Google has forced into their browsers against widespread public opposition.
In support of an open web, this website does not function with this DRM. Please install a browser such
as <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> that respects your freedom and supports
ad blockers.`;
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

BIN
projects/lolcat-c/sl.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View file

@ -0,0 +1,408 @@
<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<title>python-mpv | Home</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="color-scheme" content="dark light">
<link rel="stylesheet" href="/style.css">
</head>
<body><nav>
<div class="internal">
<a href="/" title="Home">Home</a>
<a href="/blog/" title="Blog">Blog</a>
<a href="/projects/" title="Projects">Projects</a>
<a href="/about/" title="About">About</a>
</div>
<div class="external">
<a href="https://git.jaseg.de/" title="cgit">cgit</a>
<a href="https://github.com/jaseg" title="Github">Github</a>
<a href="https://gitlab.com/neinseg" title="Gitlab">Gitlab</a>
<a href="https://chaos.social/jaseg" title="Mastodon">Mastodon</a>
</span>
</nav>
<header>
<h1>python-mpv</h1>
<ul class="breadcrumbs">
<li><a href="/">jaseg.de</a></li>
<li><a href="/projects/">Projects</a></li><li><a href="/projects/python-mpv/">python-mpv</a></li>
</ul>
</header>
<main>
<div class="links">
<a href="https://git.jaseg.de/python-mpv.git">Sources</a>
<a href="https://github.com/jaseg/python-mpv/issues">Issues</a>
<a href="https://neinseg.gitlab.io/python-mpv">Docs</a>
<a href="https://pypi.org/project/mpv">PyPI</a>
</div>
<div class="document">
<!-- vim: tw=120 sw=4 et -->
<p>python-mpv is a ctypes-based python interface to the mpv media player. It gives you more or less full control of all
features of the player, just as the lua interface does.</p>
<div class="section" id="installation">
<h2>Installation</h2>
<pre class="code bash literal-block">
<span class="lineno"></span><span class="line">pip<span class="w"> </span>install<span class="w"> </span>mpv
</span></pre>
<p>...though you can also realistically just copy <a class="reference external" href="https://raw.githubusercontent.com/jaseg/python-mpv/main/mpv.py">mpv.py</a> into your project as it's all nicely contained in one file.</p>
<div class="section" id="requirements">
<h3>Requirements</h3>
<div class="section" id="libmpv">
<h4>libmpv</h4>
<p><tt class="docutils literal">libmpv.so</tt> either locally (in your current working directory) or somewhere in your system library search path. This
module is somewhat lenient as far as <tt class="docutils literal">libmpv</tt> versions are concerned but since <tt class="docutils literal">libmpv</tt> is changing quite frequently
you'll only get all the newest features when using an up-to-date version of this module. The unit tests for this module
do some basic automatic version compatibility checks. If you discover anything missing here, please open an <a class="reference external" href="https://github.com/jaseg/python-mpv/issues">issue</a> or
submit a <a class="reference external" href="https://github.com/jaseg/python-mpv/pulls">pull request</a> on github.</p>
<p>On Windows you can place libmpv anywhere in your <tt class="docutils literal">%PATH%</tt> (e.g. next to <tt class="docutils literal">python.exe</tt>) or next to this module's
<tt class="docutils literal">mpv.py</tt>. Before falling back to looking in the mpv module's directory, python-mpv uses the DLL search order built
into ctypes, which is different to the one Windows uses internally. Consult <a class="reference external" href="https://stackoverflow.com/a/23805306">this stackoverflow post</a> for details.</p>
</div>
<div class="section" id="python-3-7-officially">
<h4>Python &gt;= 3.7 (officially)</h4>
<p>The <tt class="docutils literal">main</tt> branch officially only supports recent python releases (3.5 onwards), but there is the somewhat outdated
but functional <a class="reference external" href="https://github.com/jaseg/python-mpv/tree/py2compat">py2compat branch</a> providing Python 2 compatibility.</p>
</div>
<div class="section" id="supported-platforms">
<h4>Supported Platforms</h4>
<p><strong>Linux</strong>, <strong>Windows</strong> and <strong>OSX</strong> all seem to work mostly fine. For some notes on the installation on Windows see
<a class="reference external" href="https://github.com/jaseg/python-mpv/issues/60#issuecomment-352719773">this comment</a>. Shared library handling is quite bad on windows, so expect some pain there. On OSX there seems to be
some bug int the event logic. See <a class="reference external" href="https://github.com/jaseg/python-mpv/issues/36">issue 36</a> and <a class="reference external" href="https://github.com/jaseg/python-mpv/issues/61">issue 61</a> for details. Creating a pyQT window and having mpv draw
into it seems to be a workaround (about 10loc), but in case you want this fixed please weigh in on the issue tracker
since right now there is not many OSX users.</p>
</div>
</div>
</div>
<div class="section" id="usage">
<h2>Usage</h2>
<pre class="code python literal-block">
<span class="lineno"></span><span class="line"><span class="kn">import</span> <span class="nn">mpv</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span> <span class="o">=</span> <span class="n">mpv</span><span class="o">.</span><span class="n">MPV</span><span class="p">(</span><span class="n">ytdl</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">play</span><span class="p">(</span><span class="s1">'https://youtu.be/DOmdB7D-pUU'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">wait_for_playback</span><span class="p">()</span>
</span></pre>
<p>python-mpv mostly exposes mpv's built-in API to python, adding only some porcelain on top. Most &quot;<a class="reference external" href="https://mpv.io/manual/master/#list-of-input-commands">input commands</a>&quot; are mapped to methods of the MPV class. Check out these methods and their docstrings in <a class="reference external" href="https://github.com/jaseg/python-mpv/blob/main/mpv.py">the source</a> for things you can do. Additional controls and status information are exposed through <a class="reference external" href="https://mpv.io/manual/master/#properties">MPV properties</a>. These can be accessed like <tt class="docutils literal">player.metadata</tt>, <tt class="docutils literal">player.fullscreen</tt> and <tt class="docutils literal">player.loop_playlist</tt>.</p>
<div class="section" id="threading">
<h3>Threading</h3>
<p>The <tt class="docutils literal">mpv</tt> module starts one thread for event handling, since MPV sends events that must be processed quickly. The
event queue has a fixed maxmimum size and some operations can cause a large number of events to be sent.</p>
<p>If you want to handle threading yourself, you can pass <tt class="docutils literal">start_event_thread=False</tt> to the <tt class="docutils literal">MPV</tt> constructor and
manually call the <tt class="docutils literal">MPV</tt> object's <tt class="docutils literal">_loop</tt> function. If you have some strong need to not use threads and use some
external event loop (such as asyncio) instead you can do that, too with some work. The API of the backend C <tt class="docutils literal">libmpv</tt>
has a function for producing a sort of event file descriptor for a handle. You can use that to produce a file descriptor
that can be passed to an event loop to tell it to wake up the python-mpv event handler on every incoming event.</p>
<p>All API functions are thread-safe. If one is not, please file an issue on github.</p>
</div>
<div class="section" id="advanced-usage">
<h3>Advanced Usage</h3>
<div class="section" id="logging-properties-python-key-bindings-screenshots-and-youtube-dl">
<h4>Logging, Properties, Python Key Bindings, Screenshots and youtube-dl</h4>
<pre class="code python literal-block">
<span class="lineno"></span><span class="line"><span class="ch">#!/usr/bin/env python3</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span> <span class="nn">mpv</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="k">def</span> <span class="nf">my_log</span><span class="p">(</span><span class="n">loglevel</span><span class="p">,</span> <span class="n">component</span><span class="p">,</span> <span class="n">message</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="nb">print</span><span class="p">(</span><span class="s1">'[</span><span class="si">{}</span><span class="s1">] </span><span class="si">{}</span><span class="s1">: </span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">loglevel</span><span class="p">,</span> <span class="n">component</span><span class="p">,</span> <span class="n">message</span><span class="p">))</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span> <span class="o">=</span> <span class="n">mpv</span><span class="o">.</span><span class="n">MPV</span><span class="p">(</span><span class="n">log_handler</span><span class="o">=</span><span class="n">my_log</span><span class="p">,</span> <span class="n">ytdl</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">input_default_bindings</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">input_vo_keyboard</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="c1"># Property access, these can be changed at runtime</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="nd">&#64;player</span><span class="o">.</span><span class="n">property_observer</span><span class="p">(</span><span class="s1">'time-pos'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="k">def</span> <span class="nf">time_observer</span><span class="p">(</span><span class="n">_name</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="c1"># Here, _value is either None if nothing is playing or a float containing</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="c1"># fractional seconds since the beginning of the file.</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="nb">print</span><span class="p">(</span><span class="s1">'Now playing at </span><span class="si">{:.2f}</span><span class="s1">s'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">value</span><span class="p">))</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">fullscreen</span> <span class="o">=</span> <span class="kc">True</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">loop_playlist</span> <span class="o">=</span> <span class="s1">'inf'</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="c1"># Option access, in general these require the core to reinitialize</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="p">[</span><span class="s1">'vo'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'gpu'</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="nd">&#64;player</span><span class="o">.</span><span class="n">on_key_press</span><span class="p">(</span><span class="s1">'q'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="k">def</span> <span class="nf">my_q_binding</span><span class="p">():</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="nb">print</span><span class="p">(</span><span class="s1">'THERE IS NO ESCAPE'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="nd">&#64;player</span><span class="o">.</span><span class="n">on_key_press</span><span class="p">(</span><span class="s1">'s'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="k">def</span> <span class="nf">my_s_binding</span><span class="p">():</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">pillow_img</span> <span class="o">=</span> <span class="n">player</span><span class="o">.</span><span class="n">screenshot_raw</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">pillow_img</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="s1">'screenshot.png'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">play</span><span class="p">(</span><span class="s1">'https://youtu.be/DLzxrzFCyOs'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">wait_for_playback</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="k">del</span> <span class="n">player</span>
</span></pre>
</div>
<div class="section" id="skipping-silence-using-libav-filters">
<h4>Skipping silence using libav filters</h4>
<p>The following code uses the libav silencedetect filter to skip silence at the beginning of a file. It works by loading
the filter, then parsing its output from mpv's log. Thanks to Sean DeNigris on github (#202) for the original code!</p>
<pre class="code python literal-block">
<span class="lineno"></span><span class="line"><span class="ch">#!/usr/bin/env python3</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span> <span class="nn">sys</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span> <span class="nn">mpv</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">p</span> <span class="o">=</span> <span class="n">mpv</span><span class="o">.</span><span class="n">MPV</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">p</span><span class="o">.</span><span class="n">play</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="k">def</span> <span class="nf">skip_silence</span><span class="p">():</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">p</span><span class="o">.</span><span class="n">set_loglevel</span><span class="p">(</span><span class="s1">'debug'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">p</span><span class="o">.</span><span class="n">af</span> <span class="o">=</span> <span class="s1">'lavfi=[silencedetect=n=-20dB:d=1]'</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">p</span><span class="o">.</span><span class="n">speed</span> <span class="o">=</span> <span class="mi">100</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="n">evt</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">toks</span> <span class="o">=</span> <span class="n">evt</span><span class="p">[</span><span class="s1">'event'</span><span class="p">][</span><span class="s1">'text'</span><span class="p">]</span><span class="o">.</span><span class="n">split</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">if</span> <span class="s1">'silence_end:'</span> <span class="ow">in</span> <span class="n">toks</span><span class="p">:</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">return</span> <span class="nb">float</span><span class="p">(</span><span class="n">toks</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">p</span><span class="o">.</span><span class="n">time_pos</span> <span class="o">=</span> <span class="n">p</span><span class="o">.</span><span class="n">wait_for_event</span><span class="p">(</span><span class="s1">'log_message'</span><span class="p">,</span> <span class="n">cond</span><span class="o">=</span><span class="n">check</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">p</span><span class="o">.</span><span class="n">speed</span> <span class="o">=</span> <span class="mi">1</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">p</span><span class="o">.</span><span class="n">af</span> <span class="o">=</span> <span class="s1">''</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">skip_silence</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">p</span><span class="o">.</span><span class="n">wait_for_playback</span><span class="p">()</span>
</span></pre>
</div>
<div class="section" id="video-overlays">
<h4>Video overlays</h4>
<pre class="code python literal-block">
<span class="lineno"></span><span class="line"><span class="ch">#!/usr/bin/env python3</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span> <span class="nn">time</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span><span class="p">,</span> <span class="n">ImageDraw</span><span class="p">,</span> <span class="n">ImageFont</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span> <span class="nn">mpv</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span> <span class="o">=</span> <span class="n">mpv</span><span class="o">.</span><span class="n">MPV</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">loop</span> <span class="o">=</span> <span class="kc">True</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">play</span><span class="p">(</span><span class="s1">'test.webm'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">wait_until_playing</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">font</span> <span class="o">=</span> <span class="n">ImageFont</span><span class="o">.</span><span class="n">truetype</span><span class="p">(</span><span class="s1">'DejaVuSans.ttf'</span><span class="p">,</span> <span class="mi">40</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="k">while</span> <span class="ow">not</span> <span class="n">player</span><span class="o">.</span><span class="n">core_idle</span><span class="p">:</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">overlay</span> <span class="o">=</span> <span class="n">player</span><span class="o">.</span><span class="n">create_image_overlay</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">for</span> <span class="n">pos</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">500</span><span class="p">,</span> <span class="mi">5</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">ts</span> <span class="o">=</span> <span class="n">player</span><span class="o">.</span><span class="n">time_pos</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">if</span> <span class="n">ts</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">break</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">img</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s1">'RGBA'</span><span class="p">,</span> <span class="p">(</span><span class="mi">400</span><span class="p">,</span> <span class="mi">150</span><span class="p">),</span> <span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">d</span> <span class="o">=</span> <span class="n">ImageDraw</span><span class="o">.</span><span class="n">Draw</span><span class="p">(</span><span class="n">img</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">d</span><span class="o">.</span><span class="n">text</span><span class="p">((</span><span class="mi">10</span><span class="p">,</span> <span class="mi">10</span><span class="p">),</span> <span class="s1">'Hello World'</span><span class="p">,</span> <span class="n">font</span><span class="o">=</span><span class="n">font</span><span class="p">,</span> <span class="n">fill</span><span class="o">=</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">128</span><span class="p">))</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">d</span><span class="o">.</span><span class="n">text</span><span class="p">((</span><span class="mi">10</span><span class="p">,</span> <span class="mi">60</span><span class="p">),</span> <span class="sa">f</span><span class="s1">'t=</span><span class="si">{</span><span class="n">ts</span><span class="si">:</span><span class="s1">.3f</span><span class="si">}</span><span class="s1">'</span><span class="p">,</span> <span class="n">font</span><span class="o">=</span><span class="n">font</span><span class="p">,</span> <span class="n">fill</span><span class="o">=</span><span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">))</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">overlay</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">img</span><span class="p">,</span> <span class="n">pos</span><span class="o">=</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">pos</span><span class="p">,</span> <span class="n">pos</span><span class="p">))</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.05</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">overlay</span><span class="o">.</span><span class="n">remove</span><span class="p">()</span>
</span></pre>
</div>
<div class="section" id="playlist-handling">
<h4>Playlist handling</h4>
<pre class="code python literal-block">
<span class="lineno"></span><span class="line"><span class="ch">#!/usr/bin/env python3</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span> <span class="nn">mpv</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span> <span class="o">=</span> <span class="n">mpv</span><span class="o">.</span><span class="n">MPV</span><span class="p">(</span><span class="n">ytdl</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">input_default_bindings</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">input_vo_keyboard</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">playlist_append</span><span class="p">(</span><span class="s1">'https://youtu.be/PHIGke6Yzh8'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">playlist_append</span><span class="p">(</span><span class="s1">'https://youtu.be/Ji9qSuQapFY'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">playlist_append</span><span class="p">(</span><span class="s1">'https://youtu.be/6f78_Tf4Tdk'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">playlist_pos</span> <span class="o">=</span> <span class="mi">0</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="k">while</span> <span class="kc">True</span><span class="p">:</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="c1"># To modify the playlist, use player.playlist_{append,clear,move,remove}. player.playlist is read-only</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="nb">print</span><span class="p">(</span><span class="n">player</span><span class="o">.</span><span class="n">playlist</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">player</span><span class="o">.</span><span class="n">wait_for_playback</span><span class="p">()</span>
</span></pre>
</div>
<div class="section" id="directly-feeding-mpv-data-from-python">
<h4>Directly feeding mpv data from python</h4>
<pre class="code python literal-block">
<span class="lineno"></span><span class="line"><span class="ch">#!/usr/bin/env python3</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span> <span class="nn">mpv</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span> <span class="o">=</span> <span class="n">mpv</span><span class="o">.</span><span class="n">MPV</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="nd">&#64;player</span><span class="o">.</span><span class="n">python_stream</span><span class="p">(</span><span class="s1">'foo'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="k">def</span> <span class="nf">reader</span><span class="p">():</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s1">'test.webm'</span><span class="p">,</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">while</span> <span class="kc">True</span><span class="p">:</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">yield</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="mi">1024</span><span class="o">*</span><span class="mi">1024</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">play</span><span class="p">(</span><span class="s1">'python://foo'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">wait_for_playback</span><span class="p">()</span>
</span></pre>
</div>
<div class="section" id="using-external-subtitles">
<h4>Using external subtitles</h4>
<p>The easiest way to load custom subtitles from a file is to pass the <tt class="docutils literal"><span class="pre">--sub-file</span></tt> option to the <tt class="docutils literal">loadfile</tt> call:</p>
<pre class="code python literal-block">
<span class="lineno"></span><span class="line"><span class="ch">#!/usr/bin/env python3</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span> <span class="nn">mpv</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span> <span class="o">=</span> <span class="n">mpv</span><span class="o">.</span><span class="n">MPV</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">loadfile</span><span class="p">(</span><span class="s1">'test.webm'</span><span class="p">,</span> <span class="n">sub_file</span><span class="o">=</span><span class="s1">'test.srt'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">wait_for_playback</span><span class="p">()</span>
</span></pre>
<p>Note that you can also pass many other options to <tt class="docutils literal">loadfile</tt>. See the mpv docs for details.</p>
<p>If you want to add subtitle files or streams at runtime, you can use the <tt class="docutils literal"><span class="pre">sub-add</span></tt> command. <tt class="docutils literal"><span class="pre">sub-add</span></tt> can only be
called once the player is done loading the file and starts playing. An easy way to wait for this is to wait for the
<tt class="docutils literal"><span class="pre">core-idle</span></tt> property.</p>
<pre class="code python literal-block">
<span class="lineno"></span><span class="line"><span class="ch">#!/usr/bin/env python3</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span> <span class="nn">mpv</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span> <span class="o">=</span> <span class="n">mpv</span><span class="o">.</span><span class="n">MPV</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">play</span><span class="p">(</span><span class="s1">'test.webm'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">wait_until_playing</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">sub_add</span><span class="p">(</span><span class="s1">'test.srt'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span><span class="o">.</span><span class="n">wait_for_playback</span><span class="p">()</span>
</span></pre>
</div>
<div class="section" id="using-mpv-s-built-in-gui">
<h4>Using MPV's built-in GUI</h4>
<p>python-mpv is using mpv via libmpv. libmpv is meant for embedding into other applications and by default disables most
GUI features such as the OSD or keyboard input. To enable the built-in GUI, use the following options when initializing
the MPV instance. See <a class="reference external" href="https://github.com/jaseg/python-mpv/issues/61">Issue 102</a> for more details</p>
<pre class="code python literal-block">
<span class="lineno"></span><span class="line"><span class="c1"># Enable the on-screen controller and keyboard shortcuts</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span> <span class="o">=</span> <span class="n">mpv</span><span class="o">.</span><span class="n">MPV</span><span class="p">(</span><span class="n">input_default_bindings</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">input_vo_keyboard</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">osc</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="c1"># Alternative version using the old &quot;floating box&quot; style on-screen controller</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">player</span> <span class="o">=</span> <span class="n">mpv</span><span class="o">.</span><span class="n">MPV</span><span class="p">(</span><span class="n">player_operation_mode</span><span class="o">=</span><span class="s1">'pseudo-gui'</span><span class="p">,</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">script_opts</span><span class="o">=</span><span class="s1">'osc-layout=box,osc-seekbarstyle=bar,osc-deadzonesize=0,osc-minmousemove=3'</span><span class="p">,</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">input_default_bindings</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">input_vo_keyboard</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">osc</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></pre>
</div>
<div class="section" id="pyqt-embedding">
<h4>PyQT embedding</h4>
<pre class="code python literal-block">
<span class="lineno"></span><span class="line"><span class="ch">#!/usr/bin/env python3</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span> <span class="nn">mpv</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span> <span class="nn">sys</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">from</span> <span class="nn">PyQt5.QtWidgets</span> <span class="kn">import</span> <span class="o">*</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">from</span> <span class="nn">PyQt5.QtCore</span> <span class="kn">import</span> <span class="o">*</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="k">class</span> <span class="nc">Test</span><span class="p">(</span><span class="n">QMainWindow</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">parent</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">parent</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">container</span> <span class="o">=</span> <span class="n">QWidget</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">setCentralWidget</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">container</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">container</span><span class="o">.</span><span class="n">setAttribute</span><span class="p">(</span><span class="n">Qt</span><span class="o">.</span><span class="n">WA_DontCreateNativeAncestors</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">container</span><span class="o">.</span><span class="n">setAttribute</span><span class="p">(</span><span class="n">Qt</span><span class="o">.</span><span class="n">WA_NativeWindow</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">player</span> <span class="o">=</span> <span class="n">mpv</span><span class="o">.</span><span class="n">MPV</span><span class="p">(</span><span class="n">wid</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">container</span><span class="o">.</span><span class="n">winId</span><span class="p">())),</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">vo</span><span class="o">=</span><span class="s1">'x11'</span><span class="p">,</span> <span class="c1"># You may not need this</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">log_handler</span><span class="o">=</span><span class="nb">print</span><span class="p">,</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">loglevel</span><span class="o">=</span><span class="s1">'debug'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">player</span><span class="o">.</span><span class="n">play</span><span class="p">(</span><span class="s1">'test.webm'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">app</span> <span class="o">=</span> <span class="n">QApplication</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="c1"># This is necessary since PyQT stomps over the locale settings needed by libmpv.</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="c1"># This needs to happen after importing PyQT before creating the first mpv.MPV instance.</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span> <span class="nn">locale</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">locale</span><span class="o">.</span><span class="n">setlocale</span><span class="p">(</span><span class="n">locale</span><span class="o">.</span><span class="n">LC_NUMERIC</span><span class="p">,</span> <span class="s1">'C'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">win</span> <span class="o">=</span> <span class="n">Test</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">win</span><span class="o">.</span><span class="n">show</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">app</span><span class="o">.</span><span class="n">exec_</span><span class="p">())</span>
</span></pre>
</div>
<div class="section" id="pygobject-embedding">
<h4>PyGObject embedding</h4>
<pre class="code python literal-block">
<span class="lineno"></span><span class="line"><span class="ch">#!/usr/bin/env python3</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span> <span class="nn">gi</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span> <span class="nn">mpv</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">gi</span><span class="o">.</span><span class="n">require_version</span><span class="p">(</span><span class="s1">'Gtk'</span><span class="p">,</span> <span class="s1">'3.0'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">from</span> <span class="nn">gi.repository</span> <span class="kn">import</span> <span class="n">Gtk</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="k">class</span> <span class="nc">MainClass</span><span class="p">(</span><span class="n">Gtk</span><span class="o">.</span><span class="n">Window</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="nb">super</span><span class="p">(</span><span class="n">MainClass</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="fm">__init__</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">set_default_size</span><span class="p">(</span><span class="mi">600</span><span class="p">,</span> <span class="mi">400</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s2">&quot;destroy&quot;</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">on_destroy</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">widget</span> <span class="o">=</span> <span class="n">Gtk</span><span class="o">.</span><span class="n">Frame</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">widget</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">show_all</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="c1"># Must be created &gt;after&lt; the widget is shown, else property 'window' will be None</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">mpv</span> <span class="o">=</span> <span class="n">mpv</span><span class="o">.</span><span class="n">MPV</span><span class="p">(</span><span class="n">wid</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">widget</span><span class="o">.</span><span class="n">get_property</span><span class="p">(</span><span class="s2">&quot;window&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">get_xid</span><span class="p">()))</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">mpv</span><span class="o">.</span><span class="n">play</span><span class="p">(</span><span class="s2">&quot;test.webm&quot;</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">def</span> <span class="nf">on_destroy</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">widget</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">mpv</span><span class="o">.</span><span class="n">terminate</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">Gtk</span><span class="o">.</span><span class="n">main_quit</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="c1"># This is necessary since like Qt, Gtk stomps over the locale settings needed by libmpv.</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="c1"># Like with Qt, this needs to happen after importing Gtk but before creating the first mpv.MPV instance.</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="kn">import</span> <span class="nn">locale</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">locale</span><span class="o">.</span><span class="n">setlocale</span><span class="p">(</span><span class="n">locale</span><span class="o">.</span><span class="n">LC_NUMERIC</span><span class="p">,</span> <span class="s1">'C'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">application</span> <span class="o">=</span> <span class="n">MainClass</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">Gtk</span><span class="o">.</span><span class="n">main</span><span class="p">()</span>
</span></pre>
</div>
<div class="section" id="using-opengl-from-pygobject">
<h4>Using OpenGL from PyGObject</h4>
<p>Just like it is possible to render into a GTK widget through X11 windows, it <a class="reference external" href="https://gist.github.com/jaseg/657e8ecca3267c0d82ec85d40f423caa">also is possible to render into a GTK
widget using OpenGL</a> through this python API.</p>
</div>
<div class="section" id="using-opengl-from-pyqt5-qml">
<h4>Using OpenGL from PyQt5/QML</h4>
<p><a class="reference external" href="https://gitlab.com/robozman">Robozman</a> has mangaed to <a class="reference external" href="https://gitlab.com/robozman/python-mpv-qml-example">make mpv render into a PyQt5/QML widget using OpenGL</a> through this python API.</p>
</div>
<div class="section" id="using-mpv-inside-imgui-inside-opengl-via-glfw">
<h4>Using mpv inside imgui inside OpenGL via GLFW</h4>
<p><a class="reference external" href="https://github.com/dfaker">dfaker</a> has written a demo (<a class="reference external" href="https://github.com/dfaker/imgui_glfw_pythonmpv_demo/blob/main/main.py">link</a>) that uses mpv to render video into an <a class="reference external" href="https://github.com/ocornut/imgui">imgui</a> UI running on an OpenGL context inside <a class="reference external" href="https://www.glfw.org/">GLFW</a>. Check out their demo to see how to integrate with imgui/OpenGL and how to access properties and manage the lifecycle of an MPV instance.</p>
</div>
</div>
</div>
<div class="section" id="running-tests">
<h2>Running tests</h2>
<p>Use pytest to run tests.</p>
</div>
<div class="section" id="coding-conventions">
<h2>Coding Conventions</h2>
<p>The general aim is <a class="reference external" href="https://www.python.org/dev/peps/pep-0008/">PEP 8</a>, with liberal application of the &quot;consistency&quot; section. 120 cells line width. Four spaces.
No tabs. Probably don't bother making pure-formatting PRs except if you think it <em>really</em> helps readability or it
<em>really</em> irks you if you don't.</p>
</div>
<div class="section" id="license">
<h2>License</h2>
<p>python-mpv inherits the underlying libmpv's license, which can be either GPLv2 or later (default) or LGPLv2.1 or later.
For details, see <a class="reference external" href="https://github.com/mpv-player/mpv/blob/master/Copyright">the mpv copyright page</a>.</p>
</div>
</div>
</main><footer>
Copyright © 2023 Jan Sebastian Götte
/ <a href="/about/">About</a>
/ <a href="/imprint/">Imprint</a>
</footer>
<script>
if(navigator.getEnvironmentIntegrity!==undefined)document.querySelector('body').innerHTML=`<h1>Your browser
contains Google DRM</h1>"Web Environment Integrity" is a Google euphemism for a DRM that is designed to
prevent ad-blocking, and which Google has forced into their browsers against widespread public opposition.
In support of an open web, this website does not function with this DRM. Please install a browser such
as <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> that respects your freedom and supports
ad blockers.`;
</script>
</body>
</html>

View file

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<title>svg-flatten | Home</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="color-scheme" content="dark light">
<link rel="stylesheet" href="/style.css">
</head>
<body><nav>
<div class="internal">
<a href="/" title="Home">Home</a>
<a href="/blog/" title="Blog">Blog</a>
<a href="/projects/" title="Projects">Projects</a>
<a href="/about/" title="About">About</a>
</div>
<div class="external">
<a href="https://git.jaseg.de/" title="cgit">cgit</a>
<a href="https://github.com/jaseg" title="Github">Github</a>
<a href="https://gitlab.com/neinseg" title="Gitlab">Gitlab</a>
<a href="https://chaos.social/jaseg" title="Mastodon">Mastodon</a>
</span>
</nav>
<header>
<h1>svg-flatten</h1>
<ul class="breadcrumbs">
<li><a href="/">jaseg.de</a></li>
<li><a href="/projects/">Projects</a></li><li><a href="/projects/svg-flatten/">svg-flatten</a></li>
</ul>
</header>
<main>
<div class="links">
<a href="https://git.jaseg.de/gerbolyze.git/tree/svg-flatten?h=main">Sources</a>
<a href="https://github.com/jaseg/gerbolyze/issues">Issues</a>
<a href="https://gerbolyze.gitlab.io/svg-flatten">Docs</a>
</div>
<div class="document">
<p>svg-flatten is a command-line utility that performs vector occlusion and clipping on SVG files, producing a flattened
SVG file without overlapping elements, without changing what the file looks like. svg-flatten is used as a part of
gerbolyze.</p>
<p>I developed svg-flatten as part of <a class="reference external" href="http://jaseg.de/projects/gerbolyze/">gerbolyze</a>, but it can be used independently.</p>
</div>
</main><footer>
Copyright © 2023 Jan Sebastian Götte
/ <a href="/about/">About</a>
/ <a href="/imprint/">Imprint</a>
</footer>
<script>
if(navigator.getEnvironmentIntegrity!==undefined)document.querySelector('body').innerHTML=`<h1>Your browser
contains Google DRM</h1>"Web Environment Integrity" is a Google euphemism for a DRM that is designed to
prevent ad-blocking, and which Google has forced into their browsers against widespread public opposition.
In support of an open web, this website does not function with this DRM. Please install a browser such
as <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> that respects your freedom and supports
ad blockers.`;
</script>
</body>
</html>

105
projects/wsdiff/index.html Normal file
View file

@ -0,0 +1,105 @@
<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<title>wsdiff | Home</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="color-scheme" content="dark light">
<link rel="stylesheet" href="/style.css">
</head>
<body><nav>
<div class="internal">
<a href="/" title="Home">Home</a>
<a href="/blog/" title="Blog">Blog</a>
<a href="/projects/" title="Projects">Projects</a>
<a href="/about/" title="About">About</a>
</div>
<div class="external">
<a href="https://git.jaseg.de/" title="cgit">cgit</a>
<a href="https://github.com/jaseg" title="Github">Github</a>
<a href="https://gitlab.com/neinseg" title="Gitlab">Gitlab</a>
<a href="https://chaos.social/jaseg" title="Mastodon">Mastodon</a>
</span>
</nav>
<header>
<h1>wsdiff</h1>
<ul class="breadcrumbs">
<li><a href="/">jaseg.de</a></li>
<li><a href="/projects/">Projects</a></li><li><a href="/projects/wsdiff/">wsdiff</a></li>
</ul>
</header>
<main>
<div class="links">
<a href="https://git.jaseg.de/wsdiff.git">Sources</a>
<a href="https://github.com/jaseg/wsdiff/issues">Issues</a>
<a href="https://pypi.org/project/wsdiff">PyPI</a>
</div>
<div class="document">
<p>wsdiff is a python script that produces a diff of two files or directories as a single, self-contained HTML file. The
resulting diff works without Javascript and will automatically switch between inline and side-by-side formats depending
on available screen space.</p>
<div class="section" id="installation">
<h2>Installation</h2>
<pre class="code sh literal-block">
<span class="lineno"></span><span class="line">$<span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>wsdiff
</span></pre>
</div>
<div class="section" id="usage">
<h2>Usage</h2>
<pre class="literal-block">
<span class="lineno"></span><span class="line">usage: wsdiff [-h] [-b] [-s SYNTAX_CSS] [-l LEXER] [-L] [-t PAGETITLE]</span>
<span class="lineno"></span><span class="line"> [-o OUTPUT] [--header] [--content]</span>
<span class="lineno"></span><span class="line"> [old] [new]</span>
<span class="lineno"></span><span class="line"></span>
<span class="lineno"></span><span class="line">Given two source files or directories this application creates an html page</span>
<span class="lineno"></span><span class="line">that highlights the differences between the two.</span>
<span class="lineno"></span><span class="line"></span>
<span class="lineno"></span><span class="line">positional arguments:</span>
<span class="lineno"></span><span class="line"> old source file or directory to compare (&quot;before&quot; file)</span>
<span class="lineno"></span><span class="line"> new source file or directory to compare (&quot;after&quot; file)</span>
<span class="lineno"></span><span class="line"></span>
<span class="lineno"></span><span class="line">options:</span>
<span class="lineno"></span><span class="line"> -h, --help show this help message and exit</span>
<span class="lineno"></span><span class="line"> -b, --open Open output file in a browser</span>
<span class="lineno"></span><span class="line"> -s SYNTAX_CSS, --syntax-css SYNTAX_CSS</span>
<span class="lineno"></span><span class="line"> Path to custom Pygments CSS file for code syntax</span>
<span class="lineno"></span><span class="line"> highlighting</span>
<span class="lineno"></span><span class="line"> -l LEXER, --lexer LEXER</span>
<span class="lineno"></span><span class="line"> Manually select pygments lexer (default: guess from</span>
<span class="lineno"></span><span class="line"> filename, use -L to list available lexers.)</span>
<span class="lineno"></span><span class="line"> -L, --list-lexers List available lexers for -l/--lexer</span>
<span class="lineno"></span><span class="line"> -t PAGETITLE, --pagetitle PAGETITLE</span>
<span class="lineno"></span><span class="line"> Override page title of output HTML file</span>
<span class="lineno"></span><span class="line"> -o OUTPUT, --output OUTPUT</span>
<span class="lineno"></span><span class="line"> Name of output file (default: stdout)</span>
<span class="lineno"></span><span class="line"> --header Only output HTML header with stylesheets and stuff,</span>
<span class="lineno"></span><span class="line"> and no diff</span>
<span class="lineno"></span><span class="line"> --content Only output HTML content, without header
</span></pre>
</div>
<div class="section" id="example-output">
<h2>Example Output</h2>
<img alt="latest.png" src="latest.png" />
</div>
</div>
</main><footer>
Copyright © 2023 Jan Sebastian Götte
/ <a href="/about/">About</a>
/ <a href="/imprint/">Imprint</a>
</footer>
<script>
if(navigator.getEnvironmentIntegrity!==undefined)document.querySelector('body').innerHTML=`<h1>Your browser
contains Google DRM</h1>"Web Environment Integrity" is a Google euphemism for a DRM that is designed to
prevent ad-blocking, and which Google has forced into their browsers against widespread public opposition.
In support of an open web, this website does not function with this DRM. Please install a browser such
as <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> that respects your freedom and supports
ad blockers.`;
</script>
</body>
</html>

BIN
projects/wsdiff/latest.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB