appcast_convert.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import os
  2. from copy import deepcopy
  3. import xmltodict
  4. DELTA_BASE_URL = "https://cdn-fastly.obsproject.com/downloads/sparkle_deltas"
  5. def convert_appcast(filename):
  6. print("Converting", filename)
  7. in_path = os.path.join("output/appcasts", filename)
  8. out_path = os.path.join("output/appcasts/stable", filename.replace("_v2", ""))
  9. with open(in_path, "rb") as f:
  10. xml_data = f.read()
  11. if not xml_data:
  12. return
  13. appcast = xmltodict.parse(xml_data, force_list=("item",))
  14. out_appcast = deepcopy(appcast)
  15. # Remove anything but stable channel items.
  16. new_list = []
  17. for _item in appcast["rss"]["channel"]["item"]:
  18. item = deepcopy(_item)
  19. branch = item.pop("sparkle:channel", "stable")
  20. if branch != "stable":
  21. continue
  22. # Remove delta information (incompatible with Sparkle 1.x)
  23. item.pop("sparkle:deltas", None)
  24. new_list.append(item)
  25. out_appcast["rss"]["channel"]["item"] = new_list
  26. with open(out_path, "wb") as f:
  27. xmltodict.unparse(out_appcast, output=f, pretty=True)
  28. # Also create legacy appcast from x86 version.
  29. if "x86" in filename:
  30. out_path = os.path.join("output/appcasts/stable", "updates.xml")
  31. with open(out_path, "wb") as f:
  32. xmltodict.unparse(out_appcast, output=f, pretty=True)
  33. def adjust_appcast(filename):
  34. print("Adjusting", filename)
  35. file_path = os.path.join("output/appcasts", filename)
  36. with open(file_path, "rb") as f:
  37. xml_data = f.read()
  38. if not xml_data:
  39. return
  40. arch = "arm64" if "arm64" in filename else "x86_64"
  41. appcast = xmltodict.parse(xml_data, force_list=("item", "enclosure"))
  42. out_appcast = deepcopy(appcast)
  43. out_appcast["rss"]["channel"]["title"] = "OBS Studio"
  44. out_appcast["rss"]["channel"]["link"] = "https://obsproject.com/"
  45. new_list = []
  46. for _item in appcast["rss"]["channel"]["item"]:
  47. item = deepcopy(_item)
  48. # Fix changelog URL
  49. # Sparkle doesn't allow us to specify the URL for a specific update,
  50. # so we set the full release notes link instead and then rewrite the
  51. # appcast. Yay.
  52. if release_notes_link := item.pop("sparkle:fullReleaseNotesLink", None):
  53. item["sparkle:releaseNotesLink"] = release_notes_link
  54. # If deltas exist, update their URLs to match server layout
  55. # (generate_appcast doesn't allow this).
  56. if deltas := item.get("sparkle:deltas", None):
  57. for delta_item in deltas["enclosure"]:
  58. delta_filename = delta_item["@url"].rpartition("/")[2]
  59. delta_item["@url"] = f"{DELTA_BASE_URL}/{arch}/{delta_filename}"
  60. new_list.append(item)
  61. out_appcast["rss"]["channel"]["item"] = new_list
  62. with open(file_path, "wb") as f:
  63. xmltodict.unparse(out_appcast, output=f, pretty=True)
  64. if __name__ == "__main__":
  65. for ac_file in os.listdir("output/appcasts"):
  66. if ".xml" not in ac_file:
  67. continue
  68. if "v2" not in ac_file:
  69. # generate_appcast may download legacy appcast files and update them as well.
  70. # Those generated files are not backwards-compatible, so delete whatever v1
  71. # files it may have created and recreate them manually.
  72. os.remove(os.path.join("output/appcasts", ac_file))
  73. continue
  74. adjust_appcast(ac_file)
  75. convert_appcast(ac_file)