Various tools for working with Ardour session files
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

fix-unused-playlists.py 3.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. #!/usr/bin/env python3
  2. # Copyright (C) 2018 nickolas360 <contact@nickolas360.com>
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. import sys
  17. DEBUG = False
  18. FORCE_SECURE = False
  19. try:
  20. import defusedxml.ElementTree as ElementTree
  21. except ImportError:
  22. if FORCE_SECURE:
  23. sys.exit("Error: The 'defusedxml' package must be installed.")
  24. import xml.etree.ElementTree as ElementTree
  25. class ExitError(Exception):
  26. def __init__(self, message):
  27. super().__init__(message)
  28. @property
  29. def message(self):
  30. return self.args[0]
  31. def read_xml(path):
  32. try:
  33. return ElementTree.parse(path)
  34. except FileNotFoundError as e:
  35. raise ExitError("File not found: '{}'".format(path)) from e
  36. def get_session(tree):
  37. return tree.getroot()
  38. def enforce_version(session):
  39. version_elem = session.find("ProgramVersion")
  40. if version_elem is None:
  41. raise ExitError("Could not get version from project file.")
  42. version_str = version_elem.attrib.get("modified-with")
  43. if version_str is None:
  44. version_str = version_elem.attrib.get("created-with")
  45. if version_str is None:
  46. raise ExitError("Could not get version from project file.")
  47. if not version_str.startswith("Ardour 6."):
  48. raise ExitError("Only Ardour 6 project files are supported.")
  49. def is_playlist_used(playlist, session):
  50. try:
  51. playlist_id = playlist.attrib["id"]
  52. except KeyError:
  53. return True
  54. if session.find(".//Route[@midi-playlist={!r}]".format(playlist_id)):
  55. return True
  56. if session.find(".//Route[@audio-playlist={!r}]".format(playlist_id)):
  57. return True
  58. orig_track_id = playlist.attrib.get("orig-track-id")
  59. if orig_track_id is None:
  60. return False
  61. return bool(session.find(".//Route[@id={!r}]".format(orig_track_id)))
  62. def get_playlists(session):
  63. return session.findall(".//Playlist")
  64. def get_unused_playlists(playlists, session):
  65. return [p for p in playlists if not is_playlist_used(p, session)]
  66. def remove_unused_playlist(playlist, session):
  67. orig_track_id = playlist.attrib.get("orig-track-id")
  68. try:
  69. playlist_id = playlist.attrib["id"]
  70. except KeyError as e:
  71. raise ExitError("Cannot remove playlist without an ID.") from e
  72. parent = session.find(".//Playlist[@id='{}']/..".format(playlist_id))
  73. parent.remove(playlist)
  74. if orig_track_id is None:
  75. return
  76. obj_query = "Object[@id={!r}]".format("strip " + orig_track_id)
  77. obj_parents = session.findall(".//{}/..".format(obj_query))
  78. for obj_parent in obj_parents:
  79. objs = obj_parent.findall(obj_query)
  80. for obj in objs:
  81. obj_parent.remove(obj)
  82. def run(argv):
  83. if len(argv) != 2:
  84. sys.exit(
  85. "Usage: fix-unused-playlists.py <ardour-project-file>",
  86. )
  87. path = argv[1]
  88. tree = read_xml(path)
  89. session = get_session(tree)
  90. enforce_version(session)
  91. playlists = get_playlists(session)
  92. unused_playlists = get_unused_playlists(playlists, session)
  93. for playlist in unused_playlists:
  94. remove_unused_playlist(playlist, session)
  95. tree.write(path, xml_declaration=True, encoding="utf-8")
  96. def main(argv):
  97. try:
  98. run(argv)
  99. except ExitError as e:
  100. print(e.message, file=sys.stderr)
  101. if DEBUG:
  102. raise
  103. return 1
  104. return 0
  105. if __name__ == "__main__":
  106. sys.exit(main(sys.argv))