nakas commited on
Commit
efe5371
·
verified ·
1 Parent(s): 50487dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +70 -35
app.py CHANGED
@@ -1,50 +1,82 @@
1
  import gradio as gr
2
  from PIL import Image
3
- import piexif
4
- import piexif.helper
5
- from datetime import datetime
6
  import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
  def add_360_metadata(input_image):
9
  """Add 360 photo metadata to an image file."""
10
  try:
11
- # Open the image
12
  img = Image.open(input_image)
 
 
13
 
14
- # Prepare XMP metadata for 360 photo
15
- xmp_metadata = (
16
- '<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>\n'
17
- '<x:xmpmeta xmlns:x="adobe:ns:meta/">\n'
18
- '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n'
19
- '<rdf:Description rdf:about="" xmlns:GPano="http://ns.google.com/photos/1.0/panorama/">\n'
20
- '<GPano:ProjectionType>equirectangular</GPano:ProjectionType>\n'
21
- '<GPano:UsePanoramaViewer>True</GPano:UsePanoramaViewer>\n'
22
- '<GPano:CroppedAreaImageWidthPixels>' + str(img.width) + '</GPano:CroppedAreaImageWidthPixels>\n'
23
- '<GPano:CroppedAreaImageHeightPixels>' + str(img.height) + '</GPano:CroppedAreaImageHeightPixels>\n'
24
- '<GPano:FullPanoWidthPixels>' + str(img.width) + '</GPano:FullPanoWidthPixels>\n'
25
- '<GPano:FullPanoHeightPixels>' + str(img.height) + '</GPano:FullPanoHeightPixels>\n'
26
- '<GPano:CroppedAreaLeftPixels>0</GPano:CroppedAreaLeftPixels>\n'
27
- '<GPano:CroppedAreaTopPixels>0</GPano:CroppedAreaTopPixels>\n'
28
- '</rdf:Description>\n'
29
- '</rdf:RDF>\n'
30
- '</x:xmpmeta>\n'
31
- '<?xpacket end="w"?>'
32
- )
33
-
34
- # Create temporary filename for output
35
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
36
  output_filename = f"360_photo_{timestamp}.jpg"
 
37
 
38
- # Create EXIF dictionary with XMP metadata
39
- exif_dict = {"0th": {}, "Exif": {}, "GPS": {}, "1st": {}, "thumbnail": None}
40
- exif_dict["0th"][piexif.ImageIFD.XPComment] = xmp_metadata.encode('utf-16')
41
-
42
- # Convert EXIF dictionary to bytes
43
- exif_bytes = piexif.dump(exif_dict)
44
 
45
- # Save image with metadata
46
- output_path = os.path.join("/tmp", output_filename)
47
- img.save(output_path, "JPEG", exif=exif_bytes, quality=95)
48
 
49
  return output_path
50
 
@@ -57,7 +89,10 @@ iface = gr.Interface(
57
  inputs=gr.Image(type="filepath", label="Upload 360° Photo"),
58
  outputs=gr.Image(type="filepath", label="360° Photo with Metadata"),
59
  title="360° Photo Metadata Adder",
60
- description="Upload an equirectangular 360° photo to add necessary metadata for Google Photos and other 360° viewers. Your image should have a 2:1 aspect ratio for best results.",
 
 
 
61
  examples=[],
62
  cache_examples=False
63
  )
 
1
  import gradio as gr
2
  from PIL import Image
 
 
 
3
  import os
4
+ from datetime import datetime
5
+ import struct
6
+
7
+ def create_xmp_block(width, height):
8
+ """Create XMP metadata block following ExifTool's exact format."""
9
+ xmp = (
10
+ f'<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>\n'
11
+ f'<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="ExifTool">\n'
12
+ f'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n'
13
+ f'<rdf:Description rdf:about=""\n'
14
+ f'xmlns:GPano="http://ns.google.com/photos/1.0/panorama/"\n'
15
+ f'GPano:ProjectionType="equirectangular"\n'
16
+ f'GPano:UsePanoramaViewer="True"\n'
17
+ f'GPano:FullPanoWidthPixels="{width}"\n'
18
+ f'GPano:FullPanoHeightPixels="{height}"\n'
19
+ f'GPano:CroppedAreaImageWidthPixels="{width}"\n'
20
+ f'GPano:CroppedAreaImageHeightPixels="{height}"\n'
21
+ f'GPano:CroppedAreaLeftPixels="0"\n'
22
+ f'GPano:CroppedAreaTopPixels="0"/>\n'
23
+ f'</rdf:RDF>\n'
24
+ f'</x:xmpmeta>\n'
25
+ f'<?xpacket end="w"?>'
26
+ )
27
+ return xmp
28
+
29
+ def write_xmp_to_jpg(input_path, output_path, width, height):
30
+ """Write XMP metadata to JPEG file following ExifTool's method."""
31
+ # Read the original JPEG
32
+ with open(input_path, 'rb') as f:
33
+ data = f.read()
34
+
35
+ # Find the start of image marker
36
+ if data[0:2] != b'\xFF\xD8':
37
+ raise ValueError("Not a valid JPEG file")
38
+
39
+ # Create XMP data
40
+ xmp_data = create_xmp_block(width, height)
41
+
42
+ # Create APP1 segment for XMP
43
+ app1_marker = b'\xFF\xE1'
44
+ xmp_header = b'http://ns.adobe.com/xap/1.0/\x00'
45
+ xmp_bytes = xmp_data.encode('utf-8')
46
+ length = len(xmp_header) + len(xmp_bytes) + 2 # +2 for length bytes
47
+ length_bytes = struct.pack('>H', length)
48
+
49
+ # Construct new file content
50
+ output = bytearray()
51
+ output.extend(data[0:2]) # SOI marker
52
+ output.extend(app1_marker)
53
+ output.extend(length_bytes)
54
+ output.extend(xmp_header)
55
+ output.extend(xmp_bytes)
56
+ output.extend(data[2:]) # Rest of the original file
57
+
58
+ # Write the new file
59
+ with open(output_path, 'wb') as f:
60
+ f.write(output)
61
 
62
  def add_360_metadata(input_image):
63
  """Add 360 photo metadata to an image file."""
64
  try:
65
+ # Open and verify the image
66
  img = Image.open(input_image)
67
+ if img.width != 2 * img.height:
68
+ raise gr.Error("Image must have 2:1 aspect ratio for equirectangular projection")
69
 
70
+ # Create output filename
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
72
  output_filename = f"360_photo_{timestamp}.jpg"
73
+ output_path = os.path.join("/tmp", output_filename)
74
 
75
+ # First save as high-quality JPEG
76
+ img.save(output_path, "JPEG", quality=95)
 
 
 
 
77
 
78
+ # Then inject XMP metadata directly into JPEG file
79
+ write_xmp_to_jpg(output_path, output_path, img.width, img.height)
 
80
 
81
  return output_path
82
 
 
89
  inputs=gr.Image(type="filepath", label="Upload 360° Photo"),
90
  outputs=gr.Image(type="filepath", label="360° Photo with Metadata"),
91
  title="360° Photo Metadata Adder",
92
+ description=(
93
+ "Upload an equirectangular 360° photo to add metadata for Google Photos and other 360° viewers.\n"
94
+ "Important: Image must have 2:1 aspect ratio (width = 2 × height)."
95
+ ),
96
  examples=[],
97
  cache_examples=False
98
  )