XML Source Cleaner (Lua Script) | Remove Problematic Sources and Regions

Heyo,

So I made this script the other day to help-out someone who had a couple of corrupted/problematic regions in their XML (.ardour) file, which prevented their snapshot from loading at all…

I used this script to simply eliminate them…
…and apparently their snapshot was able to open again! :grin:


It basically does the following:

  1. Scans the chosen .ardour file for <Source> entries.
  2. Presents them to the user.
  3. Recreates/overwrites the XML file with every original line except any <Source… or <Region… lines/blocks that referenced the selected source.

Here are some pictures of it:

Screen Shot 2025-05-22 at 1.24.29 PM

Screen Shot 2025-05-22 at 1.25.12 PM

Screen Shot 2025-05-22 at 1.25.20 PM

Screen Shot 2025-05-22 at 1.25.28 PM

Screen Shot 2025-05-22 at 1.25.51 PM


So maybe this can help someone else later, too!
:grin: :+1:

Download it here: XML Source Cleaner
-Or copy and paste from the code below…

~Peace,
-J


[PS: If anyone sees any fatal flaws in the code, please let me know! O___O]


ardour {
  ["type"] = "EditorAction",
  name = "XML Source Cleaner",
  license = "GPL",
  author = "J.K.Lookinland & ALSA",
  description = "Erase a specific source and its regions from a selected .ardour file. (2025-5-22)"
}

function factory () return function ()

  -- Step 1: Introduce text and file-picker...
  local dialog_opts = {
    { type = "heading", title = "Eliminate unwanted/problematic sources and regions from an XML file." },
    { type = "label", title = " " }, -- Spacer
    { type = "label", title = "This script allows you to remove a specific <Source> entry from an .ardour" },
    { type = "label", title = "session file, along with all associated <Region> entries that reference it." },
    { type = "label", title = " " }, -- Spacer
    { type = "heading", title = "HOW TO USE:" },
    { type = "label", title = "1. Select the .ardour file you would like to alter." },
    { type = "heading", title = "(⚠ WARNING: Make sure it is NOT the one currently in use!)" },
    { type = "label", title = "2. Select the source entry you'd like to erase (via the presented dropdown-menu.)" },
    { type = "label", title = "3. Confirm your selection, and hit OK!" },
    { type = "label", title = " " }, -- Spacer
    { type = "heading", title = "⚠ WARNING:" },
    { type = "heading", title = "This operation is destructive. It is STRONGLY recommended you run this" },
    { type = "heading", title = "on a COPY of the .ardour file you wish to alter, and NOT the original." },
    { type = "label", title = " " }, -- Spacer
    { type = "file", key = "filepath", title = "Select the .ardour Session File to Clean", 
      path = ARDOUR.LuaAPI.build_filename(Session:path(), Session:name() .. ".ardour") }
  }

  local dlg = LuaDialog.Dialog("XML Source Cleaner", dialog_opts)
  local res = dlg:run()
  if not res then return end

  local filepath = res["filepath"]

  -- Step 2: Find <Source> entries...
  local sources = {}
  local source_id_map = {}
  local lines = {}

  local f = io.open(filepath, "r")
  if not f then
    LuaDialog.Message("Error", "Could not open file: " .. filepath, LuaDialog.MessageType.Error):run()
    return
  end

  for line in f:lines() do
    table.insert(lines, line)

    local id = line:match('<Source.-id="(%d+)"')
    local name = line:match('name="([^"]+)"')
    if id and name then
      local label = name .. " [ID " .. id .. "]"
      table.insert(sources, label)
      source_id_map[label] = id
    end
  end
  f:close()

  if #sources == 0 then
    LuaDialog.Message(
      "Error!",
      "No <Source> entries were found in this XML file!",
      LuaDialog.MessageType.Error,
      LuaDialog.ButtonType.Close
    ):run()
    return
  end

  -- Step 3: Present a dropdown menu with found sources...
  local dropdown_map = {} -- key = label, value = ID
  for label, id in pairs(source_id_map) do
    dropdown_map[label] = id
  end

  local sel_dlg = LuaDialog.Dialog("Select A Found Source...", {
    { type = "dropdown", key = "src", title = "Found Source(s)", values = dropdown_map, default = next(dropdown_map) }
  })

  local sel_res = sel_dlg:run()
  if not sel_res then return end

  local selected_id = sel_res["src"]

  if not selected_id then
    LuaDialog.Message(
      "Error!",
      "Unable to resolve source ID...",
      LuaDialog.MessageType.Error,
      LuaDialog.ButtonType.Close
    ):run()
    return
  end

  -- Step 4: Confirm before proceeding...
  local selected_label = nil
  for label, id in pairs(dropdown_map) do
    if id == selected_id then
      selected_label = label
      break
    end
  end

  local confirm_dialog = LuaDialog.Dialog("Confirm Deletion...", {
    { type = "label", title = "You have selected the following source:" },
    { type = "label", title = selected_label },
    { type = "label", title = " " }, -- Spacer
    { type = "heading", title = "Pressing OK will remove this source and ALL region blocks that reference it." },
    { type = "label", title = "Do you wish to continue?" },
    { type = "label", title = " " } -- Spacer
  })
  
  local confirmed = confirm_dialog:run()
  if not confirmed then return end
              
  -- Step 5: Rebuild the XML without the selected <Source> and its <Region>(s)...
  local new_lines = {}
  local deleted_regions = 0
  local skip_region = false
  local skip_source = false

  for i, line in ipairs(lines) do
    -- Handle self-closing Region with source-0="id"
    if line:match('<Region.-source%-0="' .. selected_id .. '".-/>') then
      deleted_regions = deleted_regions + 1 -- Just count it, then onto the next line...
  
    -- Start of multi-line <Region> that uses the selected source
    elseif line:match('<Region.-source%-0="' .. selected_id .. '"') and not line:match("/>") then
      skip_region = true -- Will pivot to the next elseif for the next line...
      deleted_regions = deleted_regions + 1 -- Count it, then onto the next line...
  
    -- Skip everything until we hit </Region>
    elseif skip_region then
      if line:match("</Region>") then -- Only flip skip_region back to false when we encounter the end of the larger region block...
        skip_region = false
      end
  
    -- Handle <Source> tag (self-closing or block)
    elseif line:match('<Source [^>]-id="' .. selected_id .. '"') then
      if line:match("/>") then
        -- A single-line, 'self-closing' source; skip it...
      else
        skip_source = true -- If the <Source... block is longer than one line, this will pivot to the next elseif for the next line...
      end
  
    -- Skip inside multiline Source block
    elseif skip_source then
      if line:match("</Source>") then -- Only flip skip_source back to false when we encounter the end of the larger source block...
        skip_source = false
      end
  
    -- If nothing prior matches, we then keep and preserve the line of the XML:
    else
      table.insert(new_lines, line)
    end
  end

  -- Step 6: Overwrite the file with cleaned contents...
  local out = io.open(filepath, "w")
  if not out then
    LuaDialog.Message("Error", "Failed to open file for writing: " .. filepath, LuaDialog.MessageType.Error, LuaDialog.ButtonType.Close):run()
    return
  end

  for _, line in ipairs(new_lines) do
    out:write(line .. "\n")
  end
  out:close()

  -- Step 7: Final confirmation!
  local test_string = string.format("--> Removed source %s and %d associated region(s)!", tostring(selected_id), deleted_regions)

  LuaDialog.Message(
    "Cleanup Complete!",
    test_string,
    LuaDialog.MessageType.Info,
    LuaDialog.ButtonType.Close
  ):run()

end end
5 Likes

Formidable! I’ll download the script just in case, being the one who had the problem that initialized it… Thanks again, stardom for you dear boy!

1 Like

Doesn’t the latest version of ardour already do this?

It does. The script might still be useful for someone using 8.X (where X is less than 12) and for some they cannot or do not want to upgrade. I’d still recommend using 8.12, however.

HA

Well I wasted a few hours then, hahaha. :man_facepalming:
Conglaturation…

Eh, perhaps this script will come in handy for other, potential situations. w/e

Off topic a bit, is there a way to easily remove duplicate MIDI notes in Ardour? Sometimes drum notes accidentally overlap and it creates an annoying sound

There is no existing workflow for that.

Ardour does have a policy preference for what to do what notes overlap. It is in Edit > Preferences > MIDI

1 Like

great script thank you

1 Like

Nope, those hours weren’t wasted. You did a great job rescuing the session. In hindsight, the upgrade to 8.12 may have been an easy solution, but for some reason the information didn’t get through at the time. Apart from that, a bit of pioneering work every once in a while is quite satisfying, isn’t it?

However, 8.12 has been downloaded and is waiting to be installed. But to honor you, the current song will be finished with your edited session file on 8.11.

This sounds great. Paul, how is this script accessed? I cant find it.
Many thanks

1 Like

If you mean the preference item, yes, sorry it is a session property, under Session > Properties

1 Like

No, I meant the sources cleaner like ghostonacid made, I have I misunderstood?

1 Like

Yo!

Follow the exact instructions I just wrote here, but for the cleaner script.

Let me know if you have any more questions! :grimacing: :+1:
-J

I’m confused now. Isn’t a script with this function already available in Ardour 8.12, which I am using?
Many thanks

1 Like

It is not a script, it is part of the open session code in Ardour. If the existing session has a length error, Ardour will correct while opening.

1 Like

The script I wrote is a standalone script that merely removes regions and source entries from an XML (.ardour) file in an effort to fix problematic ones. And it might still come in handy for some other file-corruption situations, who knows?

But apparently, as @ccaudle (and others) said, if you are ever having a similar issue as the person that I helped, then the XML should now automatically be repaired by Ardour 8.12.0 by simply opening the file.

So hopefully that clears-up the distinction! :+1:

Thanks GhostonAcid. Its great that you contribute to help other users. :slightly_smiling_face:
I’ve now found what I was looking for, its in the Session menu under Clean up.

1 Like

Thanks Chris. The function I was looking for is Clean-up unused sources/regions in the Session menu.
Its good that a solution has been found for the container length bug, it was a crazy thing, which happened to me a lot of times.

1 Like