diff --git a/dynmap2bluemap.py b/dynmap2bluemap.py
new file mode 100755
index 0000000..a376388
--- /dev/null
+++ b/dynmap2bluemap.py
@@ -0,0 +1,108 @@
+#!/usr/bin/python3
+
+"""
+A simple python3 script to convert Dynmap Markers to BlueMap markers.
+Copyright (C) 2025 Benjamin Burkhardt
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+"""
+
+
+import yaml # to read from the Dynmap input file format (YAML!)
+import json # to output in proper JSON format
+import argparse # to parse the cli args!
+import html # to unescape certain html-escaped strings (e.g. dynmap converts ' to ')
+
+parser = argparse.ArgumentParser(
+ prog='dynmap2bluemap - Markers',
+ description='A small helper script converting the basic structure of Dynmap markers to the one of BlueMap.',
+ epilog='© Benjamin Burkhardt, 2025')
+
+parser.add_argument('-m', '--minimal', help='Choose wether the output shall be minimal or not (»beautified«)!', action='store_true')
+parser.add_argument('-n', '--indent', help='Specify the number of whitespaces to introduce for indentation in outputted json', default=4, type=int)
+parser.add_argument('-i', '--input', help='Specify the file to read Dynmap formatted input from', default='input.yaml')
+parser.add_argument('-o', '--output', help='Specify the file to write BlueMap formatted output to (if left, will be printed out on stdout)')
+parser.add_argument('-p', '--privacynerd', help='Change how labels are converted according to the PrivacynerdMC conventions; DO NOT USE if you don\'t know what you are doing.', action='store_true')
+
+args = parser.parse_args()
+
+distance_mapping = {
+ 0: 15000,
+ 1: 3000,
+ 2: 2000,
+ 3: 1300,
+ 4: 800,
+ 5: 500,
+ 6: 300,
+}
+
+with open(f"{args.input}") as stream:
+ try:
+ print(f"Reading from {args.input}...")
+ input_parsed = yaml.safe_load(stream)
+ print(f"Read the dynmap markers from {args.input}!\n")
+ except yaml.YAMLError as exc:
+ print(exc)
+ exit(1)
+
+print("Converting the Dynmap's marker structure to fit BlueMap...")
+output = ""
+for nr,marker_set in enumerate(input_parsed['sets'].values()):
+ out_marker_set = dict()
+ out_marker_set['label'] = marker_set['label']
+ out_marker_set['toggleable'] = True
+ out_marker_set['default_hidden'] = marker_set['hide']
+ out_markers = {}
+ for i,old_marker in enumerate(marker_set['markers'].values()):
+ if args.privacynerd:
+ new_label = html.unescape(old_marker['label']).split(' - ')[0]
+ new_detail = "
".join([''+new_label+'', '' + html.unescape(old_marker['label']).split(' - ')[1] + ''])
+ else:
+ new_label = html.unescape(old_marker['label'])
+ new_detail = ""
+ new_min_distance = (distance_mapping[old_marker["maxzoom"]] if "maxzoom" in old_marker.keys() else 0)
+ new_max_distance = (distance_mapping[old_marker["minzoom"]] if "minzoom" in old_marker.keys() else 100000000)
+ new_marker = {
+ 'type': 'poi',
+ 'label': new_label,
+ 'detail': new_detail,
+ 'listed': True,
+ 'min-distance': new_min_distance,
+ 'max-distance': new_max_distance,
+ 'position': {'x':old_marker['x'], 'y': old_marker['y'], 'z': old_marker['z']},
+ 'icon': f'assets/custom-poi/{old_marker["icon"]}.png',
+ 'anchor': {'x': 16, 'y': 16},
+ }
+ out_markers[f'marker_{i}'] = new_marker
+ out_marker_set['markers'] = out_markers
+
+ if args.minimal:
+ output = f"{output}\nmarker_set_{nr}: {json.dumps(out_marker_set)}"
+ else:
+ output = f"{output}\nmarker_set_{nr}: {json.dumps(out_marker_set, indent=args.indent)}"
+
+output = output[1:]
+print("Converted to BlueMap format.\n")
+
+if args.output:
+ with open(f"{args.output}", "w") as o:
+ print(f"Writing into {args.output} (this overrides currently existing contents of the file!)...")
+ o.write(output)
+ print(f"Wrote the output into {args.output}!")
+else:
+ print("Copy the following lines into the marker_sets dict of any world.conf that you BlueMap instance reads.\nSee also: https://bluemap.bluecolored.de/wiki/customization/Markers.html\n")
+ print("------- CONFIG START -------")
+ print(output)
+ print("------- CONFIG END -------")
+
diff --git a/input.yaml b/input.yaml
new file mode 100644
index 0000000..726657d
--- /dev/null
+++ b/input.yaml
@@ -0,0 +1,29 @@
+%YAML 1.1
+---
+isSafe: true
+sets:
+ markers:
+ hide: false
+ circles: {
+ }
+ deficon: default
+ areas: {
+ }
+ label: Markers
+ markers:
+ marker_1:
+ world: world
+ markup: false
+ x: 0
+ icon: spawn
+ y: 64
+ z: 0
+ label: Spawn
+ minzoom: 1
+
+ lines: {
+ }
+ layerprio: 0
+playersets: {
+ }
+
diff --git a/output.json b/output.json
new file mode 100644
index 0000000..39828c4
--- /dev/null
+++ b/output.json
@@ -0,0 +1,25 @@
+marker_set_0: {
+ "label": "Markers",
+ "toggleable": true,
+ "default_hidden": false,
+ "markers": {
+ "marker_0": {
+ "type": "poi",
+ "label": "Spawn",
+ "detail": "",
+ "listed": true,
+ "min-distance": 0,
+ "max-distance": 3000,
+ "position": {
+ "x": 0,
+ "y": 64,
+ "z": 0
+ },
+ "icon": "assets/custom-poi/spawn.png",
+ "anchor": {
+ "x": 16,
+ "y": 16
+ }
+ }
+ }
+}
\ No newline at end of file