pato1979

XML to PNG parse

Aug 6th, 2025 (edited)
63
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.44 KB | Source Code | 0 0
  1. import xml.etree.ElementTree as ET
  2. from PIL import Image, ImageDraw
  3. import os
  4.  
  5. # -------------------------------
  6. # 1. Parse the MusicXML file
  7. # -------------------------------
  8. xml_path = '.../b1_01.xml'
  9. tree = ET.parse(xml_path)
  10. root = tree.getroot()
  11.  
  12. # -------------------------------
  13. # 3. Initialize Variables
  14. # -------------------------------
  15. systems = []                    # List of lists: measures per system
  16. current_system = []
  17.  
  18. ySystems = []           # From new-system measures
  19. measures_widths = []            # [m1_width, m2_width, ...]
  20. xMargins = []                   # [left_margin, ...] per measure
  21.  
  22. all_x_positions = []            # Note X positions (px), by measure
  23. all_y_positions = []            # Note Y positions (px), by measure
  24.  
  25. measure_to_system_index = {}    # measure_num -> system_idx
  26.  
  27. # -------------------------------
  28. # 2. Extract Page Margins and Scaling
  29. # -------------------------------
  30. page_margins = root.find('.//page-layout/page-margins')
  31. page_left = float(page_margins.find('left-margin').text)
  32. page_top = float(page_margins.find('top-margin').text)
  33. # Find first system's top-system-distance
  34. first_measure = root.find('.//measure[@number="1"]')
  35. first_layout = first_measure.find('.//system-layout')
  36. if first_layout is not None:
  37.     lm_elem = first_layout.find('system-margins/left-margin')
  38.     ySystems.insert(0,float(first_layout.find('top-system-distance').text))
  39.     if lm_elem is not None:
  40.         left_margin_tenths = float(lm_elem.text)
  41.         # Insert at beginning
  42.         xMargins.insert(0, round(left_margin_tenths, 2))
  43.     else:
  44.         xMargins.insert(0, 0.0)
  45. else:
  46.     xMargins.insert(0, 0.0)
  47. top_system_distance = float(first_measure.find('.//system-layout/top-system-distance').text)
  48.  
  49.  
  50. scaling = root.find('.//scaling')
  51. mm_per_tenth = float(scaling.find('millimeters').text) / float(scaling.find('tenths').text)
  52. DPI = 300
  53. # 25.4 = millimeters per inch (exact conversion: 1 inch = 25.4 mm)
  54. tenths_to_pixels = mm_per_tenth * (DPI / 25.4)
  55.  
  56. # -------------------------------
  57. # 4. Single Loop Over All Measures
  58. # -------------------------------
  59. for measure in root.findall('.//measure'):
  60.     measure_num = int(measure.get('number'))
  61.  
  62.     # --- Detect new system ---
  63.     print_elem = measure.find('print')
  64.     new_system = False
  65.     if print_elem is not None and print_elem.get('new-system') == 'yes':
  66.         new_system = True
  67.         dist_elem = print_elem.find('.//system-layout/system-distance')
  68.         if dist_elem is not None:
  69.             ySystems.append(float(dist_elem.text))
  70.  
  71.     # Finalize current system if starting a new one
  72.     if new_system and current_system:
  73.         systems.append(current_system)
  74.         current_system = []
  75.     current_system.append(measure_num)
  76.  
  77.     # --- xOffsets: append this measure's width ---
  78.     width = float(measure.get('width'))
  79.     measures_widths.append(width)
  80.  
  81.     # --- xMargins ---
  82.     left_margin_val = 0.0
  83.     if print_elem is not None and print_elem.get('new-system') == 'yes':
  84.         layout = print_elem.find('system-layout')
  85.         if layout is not None:
  86.             lm_elem = layout.find('system-margins/left-margin')
  87.             if lm_elem is not None:
  88.                 left_margin_tenths = float(lm_elem.text)
  89.                 xMargins.append(round(left_margin_tenths, 2))
  90.    
  91.     # --- System index for this measure ---
  92.     sys_idx = len(systems)  # current number of completed systems
  93.  
  94. # Finalize last system
  95. if current_system:
  96.     systems.append(current_system)
  97.  
  98. # Build px's
  99. measures_widths_px = [round(val * tenths_to_pixels, 2) for val in measures_widths]
  100. xMargins_px = [round(val * tenths_to_pixels, 2) for val in xMargins]
  101. ySystems_px = [round(val * tenths_to_pixels, 2) for val in ySystems]
  102. page_left_px = round(page_left * tenths_to_pixels, 2)
  103. page_top_px = round(page_top * tenths_to_pixels, 2)
  104. top_system_distance_px = round(top_system_distance * tenths_to_pixels, 2)
  105.  
  106. # Map measure numbers to system index
  107. for sys_idx, sys in enumerate(systems):
  108.     for m in sys:
  109.         measure_to_system_index[m] = sys_idx
  110.  
  111. # Target measure number (1-based)
  112. n = 10  # Change this to any measure number
  113. measure_index = n - 1  # 0-based index
  114.  
  115. # Get which system this measure belongs to
  116. system_idx = measure_to_system_index[n]
  117.  
  118. # Get all measures in the same system
  119. current_system_measures = systems[system_idx]
  120.  
  121. # Find the position of the target measure within its system
  122. target_measure_pos_in_system = current_system_measures.index(n)
  123.  
  124. # Cumulative X offset from all previous measures
  125. cumulative_x_offset_px = sum(measures_widths_px[m - 1] for m in current_system_measures if m < n)
  126. # Cumulative vertical offset (Sum all ySystems_px up to this system)
  127. cumulative_y_offset_px = sum(ySystems_px[:system_idx+1])
  128.  
  129. # This measure's xMargin in pixels
  130. x_margin_px = xMargins_px[system_idx]   # already in pixels
  131.  
  132. # Base X origin for this measure
  133. measure_origin_x_px = round((cumulative_x_offset_px + page_left_px + x_margin_px),2)
  134. # Base Y origin for this system
  135. system_origin_y_px = round((cumulative_y_offset_px + page_top_px + 708),2)
  136.  
  137. # system 2 = 236, system 3 = 236 * 2, system 4 = 236 * 3 // 145 teenths is the closest to 236 and the only place to be found is in the default-y position for a 'g' pitch in staff=2 with f clef.
  138. print(145 * tenths_to_pixels)
  139.  
  140. # Now process notes in measure n
  141. measure = root.find(f'.//measure[@number="{n}"]')
  142. notes = measure.findall('note')
  143.  
  144. absolute_x_positions = []
  145. absolute_y_positions = []
  146.  
  147. for note in notes:
  148.     # xPosition
  149.     default_x_tenths = float(note.get('default-x'))
  150.     default_x_px = default_x_tenths * tenths_to_pixels
  151.     x_abs = measure_origin_x_px + default_x_px
  152.     absolute_x_positions.append(round(x_abs, 2))
  153.     # yPosition
  154.     default_y_tenths = float(note.get('default-y'))
  155.     default_y_px = default_y_tenths * tenths_to_pixels
  156.     y_abs = system_origin_y_px - default_y_px
  157.     absolute_y_positions.append(round(y_abs, 2))
  158.    
  159. # # DRAW
  160. # png_path = '.../b1_01.png'
  161. # output_path = '.../b1_01_with_dots.png'
  162.  
  163. # if not os.path.exists(png_path):
  164. #     raise FileNotFoundError(f"PNG file not found: {png_path}")
  165.  
  166. # image = Image.open(png_path).convert("RGB")
  167. # draw = ImageDraw.Draw(image)
  168.  
  169. # dot_radius = 3
  170. # for x, y in zip(absolute_x_positions, absolute_y_positions):
  171. #     draw.ellipse([x - dot_radius, y - dot_radius, x + dot_radius, y + dot_radius], fill='red', outline='red')
  172.  
  173. # image.save(output_path)
  174. # print(f"✅ Red dots saved to: {output_path}")
Advertisement
Add Comment
Please, Sign In to add comment