diff --git a/imagepipeline/modules/composite.py b/imagepipeline/modules/composite.py index 4e3ac96..e63b6f5 100644 --- a/imagepipeline/modules/composite.py +++ b/imagepipeline/modules/composite.py @@ -35,7 +35,7 @@ class CompositeModule(SubprocessModule): "mode": Param( "string", default="over", - choices=("over", "multiply", "screen"), + choices=("over", "multiply", "screen", "linearburn", "hardmix"), help="ImageMagick -compose mode", ), "output_ext": Param( @@ -55,7 +55,14 @@ class CompositeModule(SubprocessModule): raise ValueError("composite requires at least one matched input group") command = self.resolve_command() - mode = ctx.params["mode"] + # ImageMagick accepts these case-insensitively; keep canonical spellings. + mode = { + "over": "Over", + "multiply": "Multiply", + "screen": "Screen", + "linearburn": "LinearBurn", + "hardmix": "HardMix", + }[ctx.params["mode"]] output_ext = ctx.params["output_ext"] foreground_opacity = ctx.params["foreground_opacity"] ctx.output_dir.mkdir(parents=True, exist_ok=True) diff --git a/imagepipeline/modules/darktable_style.py b/imagepipeline/modules/darktable_style.py index 79ed33e..d6652de 100644 --- a/imagepipeline/modules/darktable_style.py +++ b/imagepipeline/modules/darktable_style.py @@ -90,6 +90,9 @@ class DarktableStyleModule(SubprocessModule): ) self.log_image(ctx, index, total, src) format_name, conf_options = resolve_export_format(src, out_ext) + # darktable-cli options must come before --core; anything after + # --core is parsed as darktable GUI flags (e.g. --style-overwrite + # after --core makes darktable 5.x print usage and fail). cmd = [ "darktable-cli", str(src), @@ -98,12 +101,16 @@ class DarktableStyleModule(SubprocessModule): style, "--out-ext", format_name, - "--core", - "--configdir", - str(config_dir), ] - for conf in conf_options: - cmd.extend(["--conf", conf]) if style_overwrite: cmd.append("--style-overwrite") + cmd.extend( + [ + "--core", + "--configdir", + str(config_dir), + ] + ) + for conf in conf_options: + cmd.extend(["--conf", conf]) run_command(cmd, timeout=self.default_timeout) diff --git a/pipelines/pipeline_crusaders_goblue.py b/pipelines/pipeline_crusaders_goblue.py new file mode 100644 index 0000000..19a7b20 --- /dev/null +++ b/pipelines/pipeline_crusaders_goblue.py @@ -0,0 +1,359 @@ +#!/usr/bin/env python3 +"""GoBlue — curated Crusaders looks for sideline / blog edits. + +Keeps vortex / ink_press / cyan_punch, pulls proven combines from older +pipelines (aichelberg, crusaders, orange, rezepttest) recolored to club blues, +plus a set of new G'MIC mid-layer looks. +""" + +from pathlib import Path + +from imagepipeline import Pipeline + +INPUT = Path( + "/home/frank/pics/20260719_Biberach Beavers - Albershausen Crusaders/" + "darktable_exported/png" +) + +OUTPUT_BASE = Path.home() / "pipeline_output" +EXISTING_OUTPUTS: dict[str, Path] = {} +CONTINUE_FROM: Path | None = None + +# Club blues (#GoBlue) +COLOR1 = "#0064b0" # 0,100,176 +COLOR2 = "#00badf" # 0,186,223 +R1, G1, B1 = 0, 100, 176 +R2, G2, B2 = 0, 186, 223 +ALPHA1 = 160 +ALPHA2 = 110 + +# --- shared / kept from previous goblue --- +GMIC_TONE_MAPPING = "-fx_map_tones 0.5,0.7,0.1,30,0" +GMIC_ANGULAR = "-fx_blur_angular 2.5,50,50,0,0,7,0" +GMIC_INK_WASH = "-fx_ink_wash 0.14,23,0,0.5,0.54,2.25,0,0,0,0,0,0,0,0,0" +GMIC_DROP_SHADOW = f"-fx_drop_shadow3d 0,0,0,10,1,1,2,0.5,{R2},{G2},{B2},200,0" + +# --- from aichelberg (on tone-mapped original) --- +GMIC_CUTOUT = "-fx_cutout 4,0.5,4,1" +GMIC_HUFFMAN_GLITCHES = "-fx_huffman_glitches 30,0,25,0,0,0,0,0,50,50" +GMIC_LOCAL_SIMILARITY = "-local_similarity_mask 50,50,50,128,4,10" +GMIC_LUMA_INVERT = "-Lylejk_Luma_Invert 1,0" +GMIC_CRAYONGRAFFITI = "-fx_crayongraffiti2 300,50,1,0.4,12,1,2,2,0" + +# --- from crusaders (custom gradient B on rembg) --- +GMIC_GRADIENT_B = ( + f'-fx_custom_gradient 0,0,0,1,2,1,0,128,100,100,2,0,1,0,"",1,0,' + f"{R2},{G2},{B2},255,{R1},{G1},{B1},255,255,255,0,255,255,255,255,255,0,255," + f"255,255,0,255,0,255,0,0,255,255,128,128,128,255,255,0,255,255,0,0,0,0" +) + +# --- from rezepttest bokeh, crusaders colors --- +GMIC_BOKEH = ( + f"-fx_bokeh 3,5,0,30,8,4,0.3,0.2,{R1},{G1},{B1},{ALPHA1},0.7,30,20,20,1,2," + f"{R2},{G2},{B2},{ALPHA2},0.15" +) + +# --- new mid-layer filters --- +GMIC_LOOSE_PHOTOS = ( + "-fx_loose_photos 60,40,50,100,50,360,0,2,255,255,255,50,25,0,0,0,0,0,0,50,1,1,1" +) +GMIC_BLUR_LINEAR = "-fx_blur_linear 10,0.5,0,0,2,7,0" +GMIC_TUNNEL = "-fx_tunnel 4,80,50,50,0.2,0" +GMIC_BARBOUILLAGE = "-samj_Barbouillage_Paint_Daub 2,2,100,0.2,1,4,1,0,8" +# Multi Thresholds with club-blue palette (dark → COLOR1 → COLOR2 → light → near-white) +GMIC_MULTI_THRESHOLD = ( + f"-tran_multi_threshold 50,100,150,200," + f"0,20,40,{R1},{G1},{B1},{R2},{G2},{B2},126,200,227,232,244,252" +) +GMIC_OLDSCHOOL_8BITS = "-fx_8bits 25,800,16" + + +def main() -> None: + with Pipeline( + name="crusaders-goblue", + input_dir=INPUT, + output_base=OUTPUT_BASE, + existing_outputs=EXISTING_OUTPUTS or None, + continue_from=CONTINUE_FROM, + ) as p: + rembg_out = p.step("rembg", inputs="input", step_id="rembg_out") + tone = p.step("gmic", inputs="input", command=GMIC_TONE_MAPPING, step_id="tone") + + # ========== kept from previous goblue ========== + angular = p.step( + "gmic", inputs="input", command=GMIC_ANGULAR, step_id="angular" + ) + vortex = p.step("composite", inputs=[angular, rembg_out], step_id="vortex") + + ink = p.step("gmic", inputs=tone, command=GMIC_INK_WASH, step_id="ink") + ink_press = p.step("composite", inputs=[ink, rembg_out], step_id="ink_press") + + radial_bg = p.step( + "imagemagick_fill", + inputs="input", + color1=COLOR1, + color2=COLOR2, + gradient=True, + radial=True, + step_id="radial_bg", + ) + rembg_shadow = p.step( + "gmic", inputs=rembg_out, command=GMIC_DROP_SHADOW, step_id="rembg_shadow" + ) + punch_mid = p.step( + "composite", inputs=[radial_bg, rembg_shadow], step_id="punch_mid" + ) + cyan_punch = p.step( + "composite", inputs=[punch_mid, rembg_out], step_id="cyan_punch" + ) + + # ========== from aichelberg (tone → filter → mid → rembg) ========== + input_cutout = p.step( + "gmic", inputs=tone, command=GMIC_CUTOUT, step_id="input_cutout" + ) + cutout_mid = p.step( + "composite", inputs=[tone, input_cutout], step_id="cutout_mid" + ) + composite_cutout = p.step( + "composite", inputs=[cutout_mid, rembg_out], step_id="composite_cutout" + ) + + input_huffman = p.step( + "gmic", + inputs=tone, + command=GMIC_HUFFMAN_GLITCHES, + step_id="input_huffman_glitches", + ) + huffman_mid = p.step( + "composite", + inputs=[tone, input_huffman], + step_id="huffman_glitches_mid", + ) + composite_huffman_glitches = p.step( + "composite", + inputs=[huffman_mid, rembg_out], + step_id="composite_huffman_glitches", + ) + + input_local_sim = p.step( + "gmic", + inputs=tone, + command=GMIC_LOCAL_SIMILARITY, + step_id="input_local_similarity", + ) + local_sim_mid = p.step( + "composite", + inputs=[tone, input_local_sim], + step_id="local_similarity_mid", + ) + composite_local_similarity = p.step( + "composite", + inputs=[local_sim_mid, rembg_out], + step_id="composite_local_similarity", + ) + + input_luma_invert = p.step( + "gmic", inputs=tone, command=GMIC_LUMA_INVERT, step_id="input_luma_invert" + ) + luma_invert_mid = p.step( + "composite", + inputs=[tone, input_luma_invert], + step_id="luma_invert_mid", + ) + composite_luma_invert = p.step( + "composite", + inputs=[luma_invert_mid, rembg_out], + step_id="composite_luma_invert", + ) + + input_crayon = p.step( + "gmic", + inputs=tone, + command=GMIC_CRAYONGRAFFITI, + step_id="input_crayongraffiti", + ) + crayon_mid = p.step( + "composite", + inputs=[tone, input_crayon], + step_id="crayongraffiti_mid", + ) + composite_crayongraffiti = p.step( + "composite", + inputs=[crayon_mid, rembg_out], + step_id="composite_crayongraffiti", + ) + + # ========== from crusaders ========== + # composite_04: radial club-blue gradient + rembg + composite_04 = p.step( + "composite", inputs=[radial_bg, rembg_out], step_id="composite_04" + ) + + # composite_12: rembg custom gradient B + rembg + rembg_gradient_b = p.step( + "gmic", inputs=rembg_out, command=GMIC_GRADIENT_B, step_id="rembg_gradient_b" + ) + composite_12 = p.step( + "composite", inputs=[rembg_gradient_b, rembg_out], step_id="composite_12" + ) + + # ========== from orange (composite_02: original + drop shadow + rembg) ========== + shadow_mid = p.step( + "composite", inputs=["input", rembg_shadow], step_id="shadow_mid" + ) + composite_02 = p.step( + "composite", inputs=[shadow_mid, rembg_out], step_id="composite_02" + ) + + # ========== from rezepttest (composite_bokeh, club blues) ========== + input_bokeh = p.step( + "gmic", inputs="input", command=GMIC_BOKEH, step_id="input_bokeh" + ) + bokeh_mid = p.step( + "composite", inputs=["input", input_bokeh], step_id="bokeh_mid" + ) + composite_bokeh = p.step( + "composite", inputs=[bokeh_mid, rembg_out], step_id="composite_bokeh" + ) + + # ========== new compositions ========== + # original / loose photos (linearburn 50%) / rembg + loose = p.step( + "gmic", inputs="input", command=GMIC_LOOSE_PHOTOS, step_id="loose_photos" + ) + loose_mid = p.step( + "composite", + inputs=["input", loose], + mode="linearburn", + foreground_opacity=0.5, + step_id="loose_photos_mid", + ) + composite_loose_photos = p.step( + "composite", + inputs=[loose_mid, rembg_out], + step_id="composite_loose_photos", + ) + + # original with blur linear / rembg + blur_linear = p.step( + "gmic", inputs="input", command=GMIC_BLUR_LINEAR, step_id="blur_linear" + ) + composite_blur_linear = p.step( + "composite", + inputs=[blur_linear, rembg_out], + step_id="composite_blur_linear", + ) + + # original with tunnel / rembg + tunnel = p.step("gmic", inputs="input", command=GMIC_TUNNEL, step_id="tunnel") + composite_tunnel = p.step( + "composite", inputs=[tunnel, rembg_out], step_id="composite_tunnel" + ) + + # original with barbouillage / rembg + barbouillage = p.step( + "gmic", + inputs="input", + command=GMIC_BARBOUILLAGE, + step_id="barbouillage", + ) + composite_barbouillage = p.step( + "composite", + inputs=[barbouillage, rembg_out], + step_id="composite_barbouillage", + ) + + # original / luma invert (multiply 50%) / rembg + luma_on_input = p.step( + "gmic", + inputs="input", + command=GMIC_LUMA_INVERT, + step_id="luma_invert_on_input", + ) + luma_mul_mid = p.step( + "composite", + inputs=["input", luma_on_input], + mode="multiply", + foreground_opacity=0.5, + step_id="luma_invert_multiply_mid", + ) + composite_luma_invert_multiply = p.step( + "composite", + inputs=[luma_mul_mid, rembg_out], + step_id="composite_luma_invert_multiply", + ) + + # original / multi thresholds (hardmix 50%) / rembg + multi_threshold = p.step( + "gmic", + inputs="input", + command=GMIC_MULTI_THRESHOLD, + step_id="multi_threshold", + ) + multi_mid = p.step( + "composite", + inputs=["input", multi_threshold], + mode="hardmix", + foreground_opacity=0.5, + step_id="multi_threshold_mid", + ) + composite_multi_threshold = p.step( + "composite", + inputs=[multi_mid, rembg_out], + step_id="composite_multi_threshold", + ) + + # original / oldschool 8bits (screen) / rembg + oldschool = p.step( + "gmic", + inputs="input", + command=GMIC_OLDSCHOOL_8BITS, + step_id="oldschool_8bits", + ) + oldschool_mid = p.step( + "composite", + inputs=["input", oldschool], + mode="screen", + step_id="oldschool_8bits_mid", + ) + composite_oldschool_8bits = p.step( + "composite", + inputs=[oldschool_mid, rembg_out], + step_id="composite_oldschool_8bits", + ) + + p.step( + "xcf_stack", + inputs=[ + "input", + rembg_out, + vortex, + ink_press, + cyan_punch, + composite_cutout, + composite_huffman_glitches, + composite_local_similarity, + composite_luma_invert, + composite_crayongraffiti, + composite_04, + composite_12, + composite_02, + composite_bokeh, + composite_loose_photos, + composite_blur_linear, + composite_tunnel, + composite_barbouillage, + composite_luma_invert_multiply, + composite_multi_threshold, + composite_oldschool_8bits, + ], + step_id="xcf_looks", + ) + + output_root = p.run() + + print(f"Pipeline finished. Output: {output_root}") + + +if __name__ == "__main__": + main() diff --git a/pipelines/pipeline_orange.py b/pipelines/pipeline_orange.py index 26cebf4..d49d188 100644 --- a/pipelines/pipeline_orange.py +++ b/pipelines/pipeline_orange.py @@ -5,7 +5,7 @@ from pathlib import Path from imagepipeline import Pipeline -INPUT = Path("/home/frank/pics/20260704_Hellraisers und The Shape Schlossplatz/darktable_exported") +INPUT = Path("/home/frank/pics/20260726_Hellraisers Schlossplatz die Zweite/darktable_exported/png") OUTPUT_BASE = Path.home() / "pipeline_output" # Reuse outputs from a previous run or external folder (key = step id, e.g. rembg_01). @@ -14,24 +14,24 @@ EXISTING_OUTPUTS: dict[str, Path] = {} # Resume an aborted run: point to its output root folder (or None for a fresh run). CONTINUE_FROM: Path | None = None -COLOR1 = "#AA4E00" -COLOR2 = "#EDDD93" +COLOR1 = "#732f74" +COLOR2 = "#552577" -# COLOR1 = #AA4E00 -> 170,78,0; COLOR2 = #EDDD93 -> 237,221,147 -GMIC_DROP_SHADOW = "-fx_drop_shadow3d 0,0,0,10,1,1,2,0.5,237,221,147,200,0" +# COLOR1 = #732f74 -> 45,18,45; COLOR2 = #552577 -> 33,14,46 +GMIC_DROP_SHADOW = "-fx_drop_shadow3d 0,0,0,10,1,1,2,0.5,33,14,46,200,0" GMIC_BWRECOLOR = ( - "-fx_bwrecolorize 0,0,0,0,0,1,0,2,237,221,147,255,170,78,0,255," - "158,137,189,255,224,191,228,255,170,78,0,255,255,255,255,255,255,255," - "255,255,170,78,0,255" + "-fx_bwrecolorize 0,0,0,0,0,1,0,2,33,14,46,255,45,18,45,255," + "158,137,189,255,224,191,228,255,45,18,45,255,255,255,255,255,255,255," + "255,255,45,18,45,255" ) GMIC_GRADIENT_A = ( - '-fx_custom_gradient 0,0,0,1,2,1,0,128,100,100,2,0,1,0,"",1,0,170,78,0,255,' - "237,221,147,255,255,255,0,255,255,255,255,255,0,255,255,255,0,255,0,255,0,0," + '-fx_custom_gradient 0,0,0,1,2,1,0,128,100,100,2,0,1,0,"",1,0,45,18,45,255,' + "33,14,46,255,255,255,0,255,255,255,255,255,0,255,255,255,0,255,0,255,0,0," "255,255,128,128,128,255,255,0,255,255,0,0,0,0" ) GMIC_GRADIENT_B = ( - '-fx_custom_gradient 0,0,0,1,2,1,0,128,100,100,2,0,1,0,"",1,0,237,221,147,255,' - "170,78,0,255,255,255,0,255,255,255,255,255,0,255,255,255,0,255,0,255,0,0," + '-fx_custom_gradient 0,0,0,1,2,1,0,128,100,100,2,0,1,0,"",1,0,33,14,46,255,' + "45,18,45,255,255,255,0,255,255,255,255,255,0,255,255,255,0,255,0,255,0,0," "255,255,128,128,128,255,255,0,255,255,0,0,0,0" ) diff --git a/pipelines/pipeline_sports.py b/pipelines/pipeline_sports.py new file mode 100644 index 0000000..8abae51 --- /dev/null +++ b/pipelines/pipeline_sports.py @@ -0,0 +1,294 @@ +#!/usr/bin/env python3 +"""Sports — generic red/yellow looks for sideline / blog edits. + +Keeps proven composites from crusaders-goblue (recolored), plus four new +BG/FG G'MIC blend recipes. +""" + +from pathlib import Path + +from imagepipeline import Pipeline + +INPUT = Path( + "/home/frank/pics/20260726_Bikepark Winnenden/darktable_exported/png" +) + +OUTPUT_BASE = Path.home() / "pipeline_output" +EXISTING_OUTPUTS: dict[str, Path] = {} +CONTINUE_FROM: Path | None = Path( + "/home/frank/pipeline_output/sports_260726190157" +) + +# Standard sports red / yellow +COLOR1 = "#e30613" # 227,6,19 +COLOR2 = "#ffcc00" # 255,204,0 +R1, G1, B1 = 227, 6, 19 +R2, G2, B2 = 255, 204, 0 +ALPHA1 = 160 +ALPHA2 = 110 + +# --- shared / kept from goblue --- +GMIC_TONE_MAPPING = "-fx_map_tones 0.5,0.7,0.1,30,0" +GMIC_ANGULAR = "-fx_blur_angular 2.5,50,50,0,0,7,0" +GMIC_INK_WASH = "-fx_ink_wash 0.14,23,0,0.5,0.54,2.25,0,0,0,0,0,0,0,0,0" + +GMIC_CUTOUT = "-fx_cutout 4,0.5,4,1" +GMIC_HUFFMAN_GLITCHES = "-fx_huffman_glitches 30,0,25,0,0,0,0,0,50,50" +GMIC_LOCAL_SIMILARITY = "-local_similarity_mask 50,50,50,128,4,10" +GMIC_LUMA_INVERT = "-Lylejk_Luma_Invert 1,0" +GMIC_CRAYONGRAFFITI = "-fx_crayongraffiti2 300,50,1,0.4,12,1,2,2,0" + +GMIC_BOKEH = ( + f"-fx_bokeh 3,5,0,30,8,4,0.3,0.2,{R1},{G1},{B1},{ALPHA1},0.7,30,20,20,1,2," + f"{R2},{G2},{B2},{ALPHA2},0.15" +) + +GMIC_LOOSE_PHOTOS = ( + "-fx_loose_photos 60,40,50,100,50,360,0,2,255,255,255,50,25,0,0,0,0,0,0,50,1,1,1" +) +GMIC_BLUR_LINEAR = "-fx_blur_linear 10,0.5,0,0,2,7,0" +GMIC_TUNNEL = "-fx_tunnel 4,80,50,50,0.2,0" + +# --- new BG/FG recipes (filter on copy → G'MIC blend → next layer) --- +# Rainbowify + lchlightness / Autofill Coloring Book + interpolation +GMIC_RAINBOWIFY_AUTOFILL = ( + "+fx_rep_rainbowify 0,0,100 blend lchlightness " + "+gui_rep_acb 180,0,1,0,250000 blend interpolation" +) +# Hard Sketch + burn / Jpr Line Edges + add +GMIC_HARDSKETCH_LINE_EDGES = ( + "+fx_hardsketchbw 300,50,1,0.1,20,0,4 blend burn " + "+jpr_line_edges 2,2,4,1 blend add" +) +# Blur [Linear] + shapeaverage / Charred Plastic + blue +GMIC_BLUR_CHARRED = ( + "+fx_blur_linear 10,0.5,0,0,2,7,0 blend shapeaverage " + "+fx_charred_plastic 1,10,40,1,10,0,0,2,6,5,20,0,11 blend blue" +) +# Colored Pencils + hue / Black Crayon Graffiti + green +GMIC_PENCILS_CRAYON = ( + "+fx_cpencil 1.3,50,20,2,2,1 blend hue " + "+fx_crayongraffiti2 300,50,1,0.4,12,1,2,2,0 blend green" +) + + +def main() -> None: + with Pipeline( + name="sports", + input_dir=INPUT, + output_base=OUTPUT_BASE, + existing_outputs=EXISTING_OUTPUTS or None, + continue_from=CONTINUE_FROM, + ) as p: + rembg_out = p.step("rembg", inputs="input", step_id="rembg_out") + tone = p.step("gmic", inputs="input", command=GMIC_TONE_MAPPING, step_id="tone") + + # ========== kept from goblue ========== + angular = p.step( + "gmic", inputs="input", command=GMIC_ANGULAR, step_id="angular" + ) + vortex = p.step("composite", inputs=[angular, rembg_out], step_id="vortex") + + ink = p.step("gmic", inputs=tone, command=GMIC_INK_WASH, step_id="ink") + ink_press = p.step("composite", inputs=[ink, rembg_out], step_id="ink_press") + + input_cutout = p.step( + "gmic", inputs=tone, command=GMIC_CUTOUT, step_id="input_cutout" + ) + cutout_mid = p.step( + "composite", inputs=[tone, input_cutout], step_id="cutout_mid" + ) + composite_cutout = p.step( + "composite", inputs=[cutout_mid, rembg_out], step_id="composite_cutout" + ) + + input_huffman = p.step( + "gmic", + inputs=tone, + command=GMIC_HUFFMAN_GLITCHES, + step_id="input_huffman_glitches", + ) + huffman_mid = p.step( + "composite", + inputs=[tone, input_huffman], + step_id="huffman_glitches_mid", + ) + composite_huffman_glitches = p.step( + "composite", + inputs=[huffman_mid, rembg_out], + step_id="composite_huffman_glitches", + ) + + input_local_sim = p.step( + "gmic", + inputs=tone, + command=GMIC_LOCAL_SIMILARITY, + step_id="input_local_similarity", + ) + local_sim_mid = p.step( + "composite", + inputs=[tone, input_local_sim], + step_id="local_similarity_mid", + ) + composite_local_similarity = p.step( + "composite", + inputs=[local_sim_mid, rembg_out], + step_id="composite_local_similarity", + ) + + input_crayon = p.step( + "gmic", + inputs=tone, + command=GMIC_CRAYONGRAFFITI, + step_id="input_crayongraffiti", + ) + crayon_mid = p.step( + "composite", + inputs=[tone, input_crayon], + step_id="crayongraffiti_mid", + ) + composite_crayongraffiti = p.step( + "composite", + inputs=[crayon_mid, rembg_out], + step_id="composite_crayongraffiti", + ) + + input_bokeh = p.step( + "gmic", inputs="input", command=GMIC_BOKEH, step_id="input_bokeh" + ) + bokeh_mid = p.step( + "composite", inputs=["input", input_bokeh], step_id="bokeh_mid" + ) + composite_bokeh = p.step( + "composite", inputs=[bokeh_mid, rembg_out], step_id="composite_bokeh" + ) + + loose = p.step( + "gmic", inputs="input", command=GMIC_LOOSE_PHOTOS, step_id="loose_photos" + ) + loose_mid = p.step( + "composite", + inputs=["input", loose], + mode="linearburn", + foreground_opacity=0.5, + step_id="loose_photos_mid", + ) + composite_loose_photos = p.step( + "composite", + inputs=[loose_mid, rembg_out], + step_id="composite_loose_photos", + ) + + blur_linear = p.step( + "gmic", inputs="input", command=GMIC_BLUR_LINEAR, step_id="blur_linear" + ) + composite_blur_linear = p.step( + "composite", + inputs=[blur_linear, rembg_out], + step_id="composite_blur_linear", + ) + + tunnel = p.step("gmic", inputs="input", command=GMIC_TUNNEL, step_id="tunnel") + composite_tunnel = p.step( + "composite", inputs=[tunnel, rembg_out], step_id="composite_tunnel" + ) + + luma_on_input = p.step( + "gmic", + inputs="input", + command=GMIC_LUMA_INVERT, + step_id="luma_invert_on_input", + ) + luma_mul_mid = p.step( + "composite", + inputs=["input", luma_on_input], + mode="multiply", + foreground_opacity=0.5, + step_id="luma_invert_multiply_mid", + ) + composite_luma_invert_multiply = p.step( + "composite", + inputs=[luma_mul_mid, rembg_out], + step_id="composite_luma_invert_multiply", + ) + + # ========== new BG/FG recipes ========== + rainbowify_autofill = p.step( + "gmic", + inputs="input", + command=GMIC_RAINBOWIFY_AUTOFILL, + step_id="rainbowify_autofill", + ) + composite_rainbowify_autofill = p.step( + "composite", + inputs=[rainbowify_autofill, rembg_out], + step_id="composite_rainbowify_autofill", + ) + + hardsketch_line_edges = p.step( + "gmic", + inputs="input", + command=GMIC_HARDSKETCH_LINE_EDGES, + step_id="hardsketch_line_edges", + ) + composite_hardsketch_line_edges = p.step( + "composite", + inputs=[hardsketch_line_edges, rembg_out], + step_id="composite_hardsketch_line_edges", + ) + + blur_charred = p.step( + "gmic", + inputs="input", + command=GMIC_BLUR_CHARRED, + step_id="blur_charred", + ) + composite_blur_charred = p.step( + "composite", + inputs=[blur_charred, rembg_out], + step_id="composite_blur_charred", + ) + + pencils_crayon = p.step( + "gmic", + inputs="input", + command=GMIC_PENCILS_CRAYON, + step_id="pencils_crayon", + ) + composite_pencils_crayon = p.step( + "composite", + inputs=[pencils_crayon, rembg_out], + step_id="composite_pencils_crayon", + ) + + p.step( + "xcf_stack", + inputs=[ + "input", + rembg_out, + vortex, + ink_press, + composite_cutout, + composite_huffman_glitches, + composite_local_similarity, + composite_crayongraffiti, + composite_bokeh, + composite_loose_photos, + composite_blur_linear, + composite_tunnel, + composite_luma_invert_multiply, + composite_rainbowify_autofill, + composite_hardsketch_line_edges, + composite_blur_charred, + composite_pencils_crayon, + ], + step_id="xcf_looks", + ) + + output_root = p.run() + + print(f"Pipeline finished. Output: {output_root}") + + +if __name__ == "__main__": + main() diff --git a/pipelines/pipeline_watermark_f12_2000px.py b/pipelines/pipeline_watermark_f12_2000px.py index f92b36d..26a1afa 100644 --- a/pipelines/pipeline_watermark_f12_2000px.py +++ b/pipelines/pipeline_watermark_f12_2000px.py @@ -6,7 +6,7 @@ from pathlib import Path from imagepipeline import Pipeline INPUT = Path( - "/home/frank/pics/20260712_Aichelberg Indians - Heidelberg Hedgehogs/darktable_exported/edits" + "/home/frank/pics/20260726_Hellraisers Schlossplatz die Zweite/darktable_exported/gimp" ) OUTPUT_BASE = Path.home() / "pipeline_output" @@ -21,7 +21,7 @@ def main() -> None: output_base=OUTPUT_BASE, ) as p: resized = p.step("imagemagick_resize", inputs="input", max_edge=MAX_EDGE) - p.step("darktable_style", inputs=resized, style=STYLE) + p.step("darktable_style", inputs=resized, style=STYLE, out_ext="jpg") output_root = p.run() print(f"Pipeline finished. Output: {output_root}") diff --git a/tests/integration_helpers.py b/tests/integration_helpers.py index af9fdb1..6e9dce1 100644 --- a/tests/integration_helpers.py +++ b/tests/integration_helpers.py @@ -36,10 +36,10 @@ def has_darktable_style(style_name: str, config_dir: Path | None = None) -> bool style_name, "--out-ext", "png", + "--style-overwrite", "--core", "--configdir", str(config), - "--style-overwrite", ] result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: diff --git a/tests/test_workflow_modules.py b/tests/test_workflow_modules.py index 4c10b6b..a8f1a36 100644 --- a/tests/test_workflow_modules.py +++ b/tests/test_workflow_modules.py @@ -260,6 +260,10 @@ class TestModuleParameters: def test_composite_mode_choices(self) -> None: with pytest.raises(ValueError, match="must be one of"): CompositeModule.validate_module_params({"mode": "invalid"}) + params = CompositeModule.validate_module_params({"mode": "linearburn"}) + assert params["mode"] == "linearburn" + params = CompositeModule.validate_module_params({"mode": "hardmix"}) + assert params["mode"] == "hardmix" def test_rembg_defaults(self) -> None: params = RembgModule.validate_module_params({})