Compare commits

..

18 Commits

Author SHA1 Message Date
Max 4a7ca281dd more symmetrical grid 2026-06-01 21:54:44 +02:00
Max d3b7ebfc15 Edit Grid spacings for 3x2 monitor 2026-06-01 21:51:01 +02:00
Max 3114e0ae7f Overview should also use calculated equivalent counts 2026-06-01 21:23:08 +02:00
Max fb7bcf5780 Fix compacting 2026-06-01 21:14:07 +02:00
Max d1b569979e Change badge font 2026-06-01 20:46:04 +02:00
Max 4c00d3b232 Add compressionlevel batch to Item 2026-06-01 20:34:26 +02:00
Max 5907cd8f27 Implement the page now showing equivalent item counts for all compression levels 2026-06-01 20:29:20 +02:00
Max 5915e928c3 Fix scrolling / update race condition 2026-06-01 16:13:21 +02:00
Max 6d22b5547c smaller scale 2026-06-01 15:40:56 +02:00
Max 911d51815d temporarily set text scale back to one 2026-06-01 15:37:38 +02:00
Max 4c77395fc7 Improve asnyc updates 2026-06-01 15:36:06 +02:00
Max 4653050f45 Revert chunked loading, improve async loading 2026-06-01 15:26:03 +02:00
Max f8ad2a6439 Devide update into steps for better async behaviour 2026-06-01 15:18:35 +02:00
Max 514c7e9b22 Async count updates 2026-06-01 15:10:15 +02:00
Max 0c33acf06d Mebride compatibility fix 2026-06-01 14:57:40 +02:00
Max 63775ae3ea Fix compacting 2026-06-01 14:49:44 +02:00
Max 862aa03549 compact atc_chains 2026-06-01 14:45:09 +02:00
Max 816a28986c Removed pngs from chain json 2026-06-01 14:33:19 +02:00
5 changed files with 28431 additions and 31659 deletions
+1 -31561
View File
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
from pathlib import Path
def crop_icon(icon: str | None) -> list[int | str] | None:
if not icon:
return None
lines = icon.split("\n")
xs: list[int] = []
ys: list[int] = []
for y, line in enumerate(lines):
for x, char in enumerate(line):
if char != " ":
xs.append(x)
ys.append(y)
if not xs:
return [0, 0, ""]
min_x = min(xs)
max_x = max(xs)
min_y = min(ys)
max_y = max(ys)
cropped_lines = [line[min_x : max_x + 1].rstrip() for line in lines[min_y : max_y + 1]]
return [min_x, min_y, "\n".join(cropped_lines)]
def compact_chains(data: object) -> list[list[object]]:
if isinstance(data, dict):
chains = data.get("chains")
else:
chains = data
if not isinstance(chains, list):
raise ValueError("Input JSON does not contain a valid chains array")
compact: list[list[object]] = []
for chain in chains:
if isinstance(chain, list):
if len(chain) == 2 and isinstance(chain[1], list):
compact.append(chain)
continue
if len(chain) == 3 and isinstance(chain[1], int) and isinstance(chain[2], list):
compact.append([chain[0], chain[2]])
continue
raise ValueError("Invalid compact chain entry")
if not isinstance(chain, dict):
raise ValueError("Expected verbose or compact chain entries as input")
items = chain.get("items")
if not isinstance(items, list) or not items:
continue
base_id = chain["base_id"]
compact_items: list[list[object]] = []
for item in items:
compact_items.append([
item["item_id"],
crop_icon(item.get("icon_nfp_16x16")),
])
compact.append([base_id, compact_items])
return compact
def main() -> int:
parser = argparse.ArgumentParser(
description="Rewrite atc_chains.json into a compact format used by compcount.lua."
)
parser.add_argument("source", nargs="?", default="atc_chains_uncompressed.json")
parser.add_argument("destination", nargs="?", default="atc_chains.json")
args = parser.parse_args()
source_path = Path(args.source)
destination_path = Path(args.destination)
original_text = source_path.read_text(encoding="utf-8")
data = json.loads(original_text)
compact = {"chains": compact_chains(data)}
compact_text = json.dumps(compact, separators=(",", ":"))
destination_path.write_text(compact_text, encoding="utf-8")
print(f"Compacted {source_path} -> {destination_path}")
print(f"Old size: {len(original_text.encode('utf-8'))} bytes")
print(f"New size: {len(compact_text.encode('utf-8'))} bytes")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+494 -98
View File
@@ -26,10 +26,28 @@ local mf = require("morefonts-pe")
local frame = Pine3D.newFrame()
frame:setBackgroundColor(colors.black)
local meSystem = peripheral.find("meBridge") or peripheral.find("rsBridge")
local function findStorageBridge()
return peripheral.find("meBridge")
or peripheral.find("me_bridge")
or peripheral.find("rsBridge")
or peripheral.find("rs_bridge")
end
local meSystem = findStorageBridge()
local function ensureStorageBridge()
if not meSystem then
meSystem = findStorageBridge()
end
return meSystem
end
local chainItemsByBaseId
local chainAllItemsByBaseId
local baseItemIds
local allChainItemIds
local itemById
local defaultBaseId
local whitelistLookup
@@ -37,8 +55,15 @@ local fallbackIcon = false
local backButton = false
local scrollUpButton = false
local scrollDownButton = false
local overviewCachedCounts = {}
local overviewCachedSortedItemIds = nil
local overviewScrollCurrentRow = 0
local overviewScrollTargetRow = 0
local globalItemCounts = {}
local globalCountRefreshInterval = 5
local globalCountRefreshJob
local globalCountRefreshTimer
local globalCountLastRefreshAt = 0
local drawOverview
local function ensureWhitelistLoaded()
@@ -71,6 +96,55 @@ local function ensureWhitelistLoaded()
end
end
local function getCompressionLevel(itemId, stage)
if stage == "item" then
return 0
end
if type(stage) == "string" then
local level = tonumber(stage:match("^(%d+)x$"))
if level then
return level
end
end
if type(itemId) == "string" then
local level = tonumber(itemId:match("_(%d+)x$"))
if level then
return level
end
end
return 0
end
local function createChainItem(itemId, iconNfp, stage)
return {
id = itemId,
icon_nfp = iconNfp,
stage = stage,
compression_level = getCompressionLevel(itemId, stage),
equivalent_factor = 1,
}
end
local function applyChainEquivalentFactors(baseId, orderedItems)
local baseIndex = nil
for i = 1, #orderedItems do
if orderedItems[i].id == baseId then
baseIndex = i
break
end
end
assert(baseIndex, "Missing base item in chain: " .. tostring(baseId))
for i = 1, #orderedItems do
orderedItems[i].equivalent_factor = 9 ^ (i - baseIndex)
end
end
local function ensureChainsLoaded()
if not chainItemsByBaseId then
ensureWhitelistLoaded()
@@ -79,40 +153,74 @@ local function ensureChainsLoaded()
local data = textutils.unserializeJSON(file.readAll())
file.close()
assert(type(data) == "table" and type(data.chains) == "table", "Invalid JSON data")
local chains = data
if type(data) == "table" and type(data.chains) == "table" then
chains = data.chains
end
assert(type(chains) == "table", "Invalid JSON data")
chainItemsByBaseId = {}
chainAllItemsByBaseId = {}
baseItemIds = {}
allChainItemIds = {}
itemById = {}
for _, chain in ipairs(data.chains) do
if not whitelistLookup or whitelistLookup[chain.base_id] then
for _, chain in ipairs(chains) do
local baseId = chain.base_id or chain[1]
if not whitelistLookup or whitelistLookup[baseId] then
if not defaultBaseId then
defaultBaseId = chain.base_id
defaultBaseId = baseId
end
baseItemIds[#baseItemIds + 1] = chain.base_id
baseItemIds[#baseItemIds + 1] = baseId
local items = {}
local itemCount = 0
local orderedItems = {}
local pageItems = {}
for _, item in ipairs(chain.items or {}) do
if item.stage == "item" then
itemById[item.item_id] = {
id = item.item_id,
icon_nfp = item.icon_nfp_16x16,
}
if chain.base_id then
for _, item in ipairs(chain.items or {}) do
local chainItem = createChainItem(item.item_id, item.icon_nfp_16x16, item.stage)
orderedItems[#orderedItems + 1] = chainItem
itemById[item.item_id] = chainItem
allChainItemIds[#allChainItemIds + 1] = item.item_id
end
else
local compactItems = nil
if type(chain[2]) == "number" then
compactItems = chain[3] or {}
elseif type(chain[2]) == "table" then
compactItems = chain[2]
elseif type(chain[2]) == "string" and type(chain[3]) == "table" then
orderedItems[1] = createChainItem(baseId, chain[2], "item")
itemById[baseId] = orderedItems[1]
allChainItemIds[#allChainItemIds + 1] = baseId
compactItems = chain[3]
else
itemCount = itemCount + 1
items[itemCount] = {
id = item.item_id,
icon_nfp = item.icon_nfp_16x16,
}
itemById[item.item_id] = items[itemCount]
error("Invalid compact chain entry for " .. tostring(baseId))
end
for i = 1, #compactItems do
local compactItem = compactItems[i]
local chainItem = createChainItem(compactItem[1], compactItem[2], compactItem[3])
orderedItems[#orderedItems + 1] = chainItem
itemById[compactItem[1]] = chainItem
allChainItemIds[#allChainItemIds + 1] = compactItem[1]
end
end
chainItemsByBaseId[chain.base_id] = items
applyChainEquivalentFactors(baseId, orderedItems)
for i = 1, #orderedItems do
pageItems[#pageItems + 1] = orderedItems[i]
end
chainAllItemsByBaseId[baseId] = orderedItems
chainItemsByBaseId[baseId] = pageItems
end
end
end
@@ -124,6 +232,12 @@ local function getPageItems(base_id)
return chainItemsByBaseId[base_id or defaultBaseId] or {}
end
local function getAllChainItems(base_id)
ensureChainsLoaded()
return chainAllItemsByBaseId[base_id or defaultBaseId] or {}
end
local function getBaseItemIds()
ensureChainsLoaded()
@@ -164,13 +278,49 @@ local function parseNfpImage(nfp)
return image
end
local function parseIconData(iconData)
if type(iconData) == "string" then
return parseNfpImage(iconData)
end
if type(iconData) ~= "table" then
return nil
end
local offsetX = tonumber(iconData[1]) or 0
local offsetY = tonumber(iconData[2]) or 0
local croppedImage = parseNfpImage(iconData[3])
if not croppedImage then
return nil
end
if offsetX == 0 and offsetY == 0 then
return croppedImage
end
local image = {}
for y, row in pairs(croppedImage) do
local shiftedRow = {}
for x, value in pairs(row) do
shiftedRow[x + offsetX] = value
end
image[y + offsetY] = shiftedRow
end
return image
end
local function getItemIcon(item)
if not item then
return nil
end
if item.icon == nil then
item.icon = parseNfpImage(item.icon_nfp) or false
item.icon = parseIconData(item.icon_nfp) or false
item.icon_nfp = nil
end
@@ -178,12 +328,14 @@ local function getItemIcon(item)
end
local function getMeItemCount(itemId)
if not meSystem or not itemId then
local bridge = ensureStorageBridge()
if not bridge or not itemId then
return 0
end
if type(meSystem.getItem) == "function" then
local ok, item = pcall(meSystem.getItem, { name = itemId })
if type(bridge.getItem) == "function" then
local ok, item = pcall(bridge.getItem, { name = itemId })
if ok and item then
return tonumber(item.amount or item.count or item.qty) or 0
end
@@ -192,6 +344,38 @@ local function getMeItemCount(itemId)
return 0
end
local function listBridgeItems(bridge)
local listItems = bridge.listItems or bridge.getItems or bridge.listAvailableItems
if not listItems then
return nil
end
local ok, items = pcall(listItems, {})
if ok and type(items) == "table" then
local normalized = {}
for _, item in pairs(items) do
normalized[#normalized + 1] = item
end
return normalized
end
ok, items = pcall(listItems)
if ok and type(items) == "table" then
local normalized = {}
for _, item in pairs(items) do
normalized[#normalized + 1] = item
end
return normalized
end
return nil
end
local function getMeItemCounts(itemIds)
local counts = {}
@@ -199,56 +383,205 @@ local function getMeItemCounts(itemIds)
counts[itemIds[i]] = 0
end
if not meSystem then
local bridge = ensureStorageBridge()
if not bridge then
return counts
end
local unresolved = {}
local unresolvedCount = 0
local snapshot = listBridgeItems(bridge)
if not snapshot then
return counts
end
local wanted = {}
for i = 1, #itemIds do
local itemId = itemIds[i]
local count = getMeItemCount(itemId)
counts[itemId] = count
if count == 0 then
unresolved[itemId] = true
unresolvedCount = unresolvedCount + 1
end
wanted[itemIds[i]] = true
end
if unresolvedCount == 0 then
return counts
end
local listItems = meSystem.listItems or meSystem.getItems or meSystem.listAvailableItems
if not listItems then
return counts
end
local ok, items = pcall(listItems)
if not ok or type(items) ~= "table" then
return counts
end
for i = 1, #items do
local item = items[i]
for i = 1, #snapshot do
local item = snapshot[i]
local itemId = item and (item.name or item.id or item.item_id)
if itemId and unresolved[itemId] then
if itemId and wanted[itemId] then
counts[itemId] = tonumber(item.amount or item.count or item.qty) or 0
unresolved[itemId] = nil
unresolvedCount = unresolvedCount - 1
if unresolvedCount == 0 then
break
end
end
end
return counts
end
local function makeZeroCounts(itemIds)
local counts = {}
for i = 1, #itemIds do
counts[itemIds[i]] = 0
end
return counts
end
local function createAsyncCountsFromSnapshotJob(itemIds, snapshot, batchSize, baseCounts)
local counts = {}
local index = 1
local wanted = {}
batchSize = math.max(1, math.floor(tonumber(batchSize) or 1))
for i = 1, #itemIds do
wanted[itemIds[i]] = true
counts[itemIds[i]] = 0
end
if type(baseCounts) == "table" then
for itemId, count in pairs(baseCounts) do
if wanted[itemId] then
counts[itemId] = tonumber(count) or 0
end
end
end
return {
step = function()
local lastIndex = math.min(#snapshot, index + batchSize - 1)
for i = index, lastIndex do
local item = snapshot[i]
local itemId = item and (item.name or item.id or item.item_id)
if itemId and wanted[itemId] then
counts[itemId] = tonumber(item.amount or item.count or item.qty) or 0
end
end
index = lastIndex + 1
return index > #snapshot, counts
end,
}
end
local function getGlobalItemCounts()
ensureChainsLoaded()
return globalItemCounts
end
local function hasGlobalItemCounts()
return next(globalItemCounts) ~= nil
end
local function getBaseEquivalentCount(baseId, itemCounts)
local total = 0
local chainItems = getAllChainItems(baseId)
for i = 1, #chainItems do
local item = chainItems[i]
total = total + (tonumber(itemCounts[item.id]) or 0) * (item.equivalent_factor or 1)
end
return total
end
local function getEquivalentLevelCounts(baseId, pageItems, itemCounts)
local equivalentCounts = {}
local baseEquivalentCount = getBaseEquivalentCount(baseId, itemCounts)
for i = 1, #pageItems do
local item = pageItems[i]
local factor = item.equivalent_factor or 1
equivalentCounts[item.id] = math.floor(baseEquivalentCount / factor)
end
return equivalentCounts
end
local function getEquivalentBaseCounts(baseIds, itemCounts)
local equivalentCounts = {}
for i = 1, #baseIds do
local baseId = baseIds[i]
equivalentCounts[baseId] = getBaseEquivalentCount(baseId, itemCounts)
end
return equivalentCounts
end
local function createGlobalCountRefreshJob()
ensureChainsLoaded()
local bridge = ensureStorageBridge()
if not bridge then
return nil
end
local snapshot = listBridgeItems(bridge)
if not snapshot then
return nil
end
local job = createAsyncCountsFromSnapshotJob(allChainItemIds or {}, snapshot, 25, globalItemCounts)
return {
step = function()
local isDone, counts = job.step()
if isDone then
globalItemCounts = counts
globalCountLastRefreshAt = os.epoch("utc")
end
return isDone, counts
end,
}
end
local function shouldStartGlobalCountRefresh()
if globalCountRefreshJob then
return false
end
if not hasGlobalItemCounts() then
return true
end
return os.epoch("utc") - globalCountLastRefreshAt >= globalCountRefreshInterval * 1000
end
local function startGlobalCountRefreshIfNeeded(force)
if globalCountRefreshJob then
return false
end
if not force and not shouldStartGlobalCountRefresh() then
return false
end
globalCountRefreshJob = createGlobalCountRefreshJob()
if not globalCountRefreshJob then
return false
end
globalCountRefreshTimer = os.startTimer(0.01)
return true
end
local function stepGlobalCountRefresh(timerId)
if not globalCountRefreshJob or timerId ~= globalCountRefreshTimer then
return false, false
end
local isDone = globalCountRefreshJob.step()
if isDone then
globalCountRefreshJob = nil
globalCountRefreshTimer = nil
return true, true
end
globalCountRefreshTimer = os.startTimer(0.01)
return true, false
end
local function getFallbackIcon()
if fallbackIcon == false then
fallbackIcon = parseNfpImage(table.concat({
@@ -442,7 +775,7 @@ local function assertBufferValid(frame)
end
end
local function drawItem(img, cellX, cellY, count, offsetY)
local function drawItem(img, cellX, cellY, count, offsetY, item)
-- Convert normal monitor cells into Pine3D teletext pixels.
local x = cellToPixelX(cellX)
local y = cellToPixelY(cellY) + (offsetY or 0)
@@ -459,6 +792,19 @@ local function drawItem(img, cellX, cellY, count, offsetY)
end
end
local compressionLevel = item and tonumber(item.compression_level) or 0
if compressionLevel > 0 and y <= frame.buffer.height and y + 8 >= 1 then
mf.writeOn(frame, tostring(compressionLevel), colors.red, x + 15, y, {
font = "fonts/PixelPlace",
textAlign = "right",
anchorHor = "right",
anchorVer = "top",
condense = true,
scale = 1,
})
end
local text = formatCount(count)
local rightX = x + 16 -- x + math.max(imgW * scaleX - 1, 7)
@@ -485,8 +831,8 @@ local function drawItem(img, cellX, cellY, count, offsetY)
end
local function runScrollableGrid(options)
local entries = {}
local counts = {}
local entries = options.getInitialEntries()
local counts = options.getInitialCounts(entries)
local visibleItemCount = 0
local totalRows = 0
local maxScrollRow = 0
@@ -494,13 +840,14 @@ local function runScrollableGrid(options)
local targetScrollRow = 0
local refreshTimer
local animationTimer
local pendingCacheSync = false
local columns = 3
local columns = 5
local rowsPerView = 3
local baseCellX = 4
local baseCellX = 3
local baseCellY = 2
local colStepCells = 12
local rowStepCells = 6
local colStepCells = 8+3
local rowStepCells = 5+3
local rowStepPixels = rowStepCells * 3
local scrollbarX = SCREEN_WIDTH - 1
local scrollbarWidth = 2
@@ -564,8 +911,8 @@ local function runScrollableGrid(options)
end
end
local function refreshGridData()
entries, counts = options.getEntriesAndCounts()
local function syncFromCache()
entries, counts = options.getEntriesAndCountsFromCache(entries, counts)
end
local function renderGrid()
@@ -596,7 +943,8 @@ local function runScrollableGrid(options)
baseCellX + colStepCells * col,
baseCellY + rowStepCells * row,
counts[options.getId(entry)] or 0,
rowOffsetPixels
rowOffsetPixels,
entry
)
end
@@ -623,17 +971,21 @@ local function runScrollableGrid(options)
frame:drawBuffer()
end
refreshGridData()
syncFromCache()
renderGrid()
refreshTimer = os.startTimer(options.refreshSeconds or 30)
if options.shouldStartRefreshImmediately == nil or options.shouldStartRefreshImmediately(entries, counts) then
startGlobalCountRefreshIfNeeded(false)
end
refreshTimer = os.startTimer(options.refreshSeconds or globalCountRefreshInterval)
while true do
local event, p1, x, y = os.pullEvent()
if event == "timer" and p1 == refreshTimer then
refreshGridData()
renderGrid()
refreshTimer = os.startTimer(options.refreshSeconds or 30)
startGlobalCountRefreshIfNeeded(false)
refreshTimer = os.startTimer(options.refreshSeconds or globalCountRefreshInterval)
scheduleAnimation()
elseif event == "timer" and p1 == animationTimer then
local delta = targetScrollRow - currentScrollRow
@@ -661,6 +1013,23 @@ local function runScrollableGrid(options)
persistScrollState()
renderGrid()
if not animationTimer and pendingCacheSync then
pendingCacheSync = false
syncFromCache()
renderGrid()
end
elseif event == "timer" then
local handled, isDone = stepGlobalCountRefresh(p1)
if handled and isDone then
if animationTimer or math.abs(targetScrollRow - currentScrollRow) > 0.001 then
pendingCacheSync = true
else
syncFromCache()
renderGrid()
end
end
elseif event == "monitor_touch" and p1 == monName then
if options.handleChromeTouch and options.handleChromeTouch(x, y) then
return
@@ -717,9 +1086,15 @@ local function drawPage(base_id)
end
runScrollableGrid({
refreshSeconds = 5,
getEntriesAndCounts = function()
return pageItems, getMeItemCounts(pageItemIds)
refreshSeconds = globalCountRefreshInterval,
getInitialEntries = function()
return pageItems
end,
getInitialCounts = function()
return getEquivalentLevelCounts(base_id, pageItems, getGlobalItemCounts())
end,
getEntriesAndCountsFromCache = function(currentEntries)
return currentEntries, getEquivalentLevelCounts(base_id, pageItems, getGlobalItemCounts())
end,
getId = function(entry)
return entry.id
@@ -748,8 +1123,45 @@ drawOverview = function()
local items = getBaseItemIds()
local defaultIcon = getFallbackIcon()
local function sortOverviewItems(itemCounts)
local sortedItemIds = {}
for i = 1, #items do
sortedItemIds[i] = items[i]
end
table.sort(sortedItemIds, function(a, b)
local countA = itemCounts[a] or 0
local countB = itemCounts[b] or 0
if countA ~= countB then
return countA > countB
end
return a < b
end)
return sortedItemIds
end
runScrollableGrid({
refreshSeconds = 30,
refreshSeconds = globalCountRefreshInterval,
getInitialEntries = function()
if overviewCachedSortedItemIds and #overviewCachedSortedItemIds > 0 then
return overviewCachedSortedItemIds
end
return sortOverviewItems(overviewCachedCounts)
end,
getInitialCounts = function()
local counts = getGlobalItemCounts()
if next(counts) ~= nil then
overviewCachedCounts = getEquivalentBaseCounts(items, counts)
end
return overviewCachedCounts
end,
getInitialScrollState = function()
return overviewScrollCurrentRow, overviewScrollTargetRow
end,
@@ -757,26 +1169,10 @@ drawOverview = function()
overviewScrollCurrentRow = currentRow
overviewScrollTargetRow = targetRow
end,
getEntriesAndCounts = function()
local itemCounts = getMeItemCounts(items)
local sortedItemIds = {}
for i = 1, #items do
sortedItemIds[i] = items[i]
end
table.sort(sortedItemIds, function(a, b)
local countA = itemCounts[a] or 0
local countB = itemCounts[b] or 0
if countA ~= countB then
return countA > countB
end
return a < b
end)
return sortedItemIds, itemCounts
getEntriesAndCountsFromCache = function()
overviewCachedCounts = getEquivalentBaseCounts(items, getGlobalItemCounts())
overviewCachedSortedItemIds = sortOverviewItems(overviewCachedCounts)
return overviewCachedSortedItemIds, overviewCachedCounts
end,
getId = function(entry)
return entry
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
from pathlib import Path
def remove_icon_png_base64_lines(path: Path) -> int:
original = path.read_text(encoding="utf-8")
lines = original.splitlines(keepends=True)
kept_lines = []
removed = 0
for line in lines:
if '"icon_png_base64"' in line:
removed += 1
continue
kept_lines.append(line)
if removed:
path.write_text("".join(kept_lines), encoding="utf-8")
return removed
def main() -> int:
parser = argparse.ArgumentParser(
description="Remove icon_png_base64 fields from atc_chains.json while preserving all other text."
)
parser.add_argument(
"path",
nargs="?",
default="atc_chains.json",
help="Path to the JSON file to rewrite in place.",
)
args = parser.parse_args()
path = Path(args.path)
removed = remove_icon_png_base64_lines(path)
print(f"Removed {removed} icon_png_base64 field(s) from {path}.")
return 0
if __name__ == "__main__":
raise SystemExit(main())