Surn commited on
Commit
e621926
·
1 Parent(s): d8c5068

Fix permalink downloads

Browse files
.gitattributes CHANGED
@@ -34,8 +34,8 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  images/logo.png filter=lfs diff=lfs merge=lfs -text
37
- images/beeuty_545jlbh1_v12_alpha96_300dpi.png filter=lfs diff=lfs merge=lfs -text
38
- images/beeuty_545jlbh1_v12_alpha96_300dpi_depth.png filter=lfs diff=lfs merge=lfs -text
39
  *.glb filter=lfs diff=lfs merge=lfs -text
40
  *.gltf filter=lfs diff=lfs merge=lfs -text
41
  *.ply filter=lfs diff=lfs merge=lfs -text
 
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  images/logo.png filter=lfs diff=lfs merge=lfs -text
37
+ images/slider/beeuty_545jlbh1_v12_alpha96_300dpi.png filter=lfs diff=lfs merge=lfs -text
38
+ images/slider/beeuty_545jlbh1_v12_alpha96_300dpi_depth.png filter=lfs diff=lfs merge=lfs -text
39
  *.glb filter=lfs diff=lfs merge=lfs -text
40
  *.gltf filter=lfs diff=lfs merge=lfs -text
41
  *.ply filter=lfs diff=lfs merge=lfs -text
app.py CHANGED
@@ -12,22 +12,156 @@ default_folder = "saved_models/3d_model_" + format(random.randint(1, 999999), "0
12
  def getVersions():
13
  #return html_versions
14
  return version_info.versions_html()
 
15
 
16
- def load_data(query_params, model_3d, image_slider):
17
- # set default values or pull from querystring
18
- model_url = query_params.get("3d", None) if query_params else None
19
- hm_url = query_params.get("hm", None) if query_params else None
20
- img_url = query_params.get("image", None) if query_params else None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  slider_images = []
22
- if hm_url:
23
- slider_images.append(hm_url)
24
  if img_url:
25
- slider_images.append(img_url)
 
 
 
 
 
 
 
 
 
 
 
 
26
  if not slider_images:
27
- slider_images = ["images/beeuty_545jlbh1_v12_alpha96_300dpi.png", "images/beeuty_545jlbh1_v12_alpha96_300dpi_depth.png"]
 
28
  if not model_url:
29
- model_url = "models/beeuty_545jlbh1_300dpi.glb"
30
- return model_url, slider_images
 
 
 
 
 
 
 
 
 
 
31
 
32
  def process_upload(files, current_model, current_images):
33
  """
@@ -141,20 +275,24 @@ with gr.Blocks(css_paths="style_20250503.css", title="3D viewer", theme='Surn/be
141
  max_lines=5,
142
  lines=3
143
  )
144
- gr.Markdown("### Copy the link above to share your model and images.", elem_classes="solid centered",)
145
  with gr.Row():
146
  gr.HTML(value=getVersions(), visible=True, elem_id="versions")
147
 
148
  # Use JavaScript to pass the query parameters to your callback.
149
  viewer3d.load(
150
  load_data,
151
- inputs=[gr.JSON(), model_3d, image_slider],
152
- outputs=[model_3d, image_slider],
153
- js="""() => {
154
- const params = Object.fromEntries(new URLSearchParams(window.location.search));
155
- return params;
156
- }""",
157
  scroll_to_output=True
 
 
 
 
 
 
 
 
158
  )
159
 
160
  # Process uploaded files to update the Model3D or ImageSlider component.
@@ -165,7 +303,11 @@ with gr.Blocks(css_paths="style_20250503.css", title="3D viewer", theme='Surn/be
165
  scroll_to_output=True,
166
  api_name="process_upload",
167
  show_progress=True
168
-
 
 
 
 
169
  )
170
  # Generate a permalink based on the current model, images, and folder name.
171
  permalink_button.click(
 
12
  def getVersions():
13
  #return html_versions
14
  return version_info.versions_html()
15
+ # Process URLs and download files if needed.
16
 
17
+ def process_url(url, default_ext=".png"):
18
+ """Download file from URL if it's a remote URL and return its local path.
19
+
20
+ Performs HuggingFace authentication if the URL requires it.
21
+ The caller can pass an appropriate default_ext (e.g. ".glb" for models).
22
+ Uses huggingface_hub library for HuggingFace URLs for better authentication.
23
+ """
24
+ if not url:
25
+ return None
26
+
27
+ # If it's already a local file, return it.
28
+ if os.path.exists(url) or not (url.startswith('http://') or url.startswith('https://')):
29
+ return url
30
+
31
+ # Parse URL to get components
32
+ try:
33
+ import urllib.request
34
+ from urllib.parse import urlparse
35
+
36
+ # Create filename from URL.
37
+ parsed_url = urlparse(url)
38
+ filename = os.path.basename(parsed_url.path)
39
+ if not filename:
40
+ filename = f"downloaded_{hash(url) % 10000}.file"
41
+
42
+ # Add extension if missing.
43
+ ext = os.path.splitext(filename)[1].lower()
44
+ if not ext:
45
+ filename += default_ext
46
+
47
+ # Create local path.
48
+ local_path = os.path.join(constants.TMPDIR, filename)
49
+
50
+ # If the file is hosted on HuggingFace, use huggingface_hub
51
+ if 'huggingface.co' in url or 'hf.co' in url:
52
+ try:
53
+ from huggingface_hub import login, hf_hub_download
54
+
55
+ # Log in to HuggingFace
56
+ login(token=constants.HF_API_TOKEN)
57
+
58
+ # Extract repo information from URL
59
+ # Format: https://huggingface.co/datasets/{repo_id}/resolve/main/{path}
60
+ if '/datasets/' in url and '/resolve/main/' in url:
61
+ parts = url.split('/datasets/')[1].split('/resolve/main/')
62
+ repo_id = parts[0]
63
+
64
+ # The remaining path may contain subfolders and filename
65
+ full_path = parts[1]
66
+
67
+ # Extract the filename and subfolder
68
+ if '/' in full_path:
69
+ subfolder, filename = full_path.rsplit('/', 1)
70
+ else:
71
+ subfolder = None
72
+ filename = full_path
73
+
74
+ print(f"Downloading from HF repo '{repo_id}', filename '{filename}', subfolder '{subfolder}'")
75
+
76
+ # Download using huggingface_hub
77
+ local_path = hf_hub_download(
78
+ repo_id=repo_id,
79
+ filename=filename,
80
+ subfolder=subfolder,
81
+ repo_type="dataset",
82
+ local_dir=constants.TMPDIR,
83
+ local_dir_use_symlinks=False
84
+ )
85
+ return local_path
86
+ else:
87
+ # Fall back to standard download for other HF URLs
88
+ print("URL format not recognized for huggingface_hub download, falling back to standard method")
89
+ except Exception as e:
90
+ print(f"Error using huggingface_hub download: {e}, falling back to standard method")
91
+
92
+ # Standard download for non-HF URLs or as fallback
93
+ print(f"Downloading {url} to {local_path}")
94
+ urllib.request.urlretrieve(url, local_path)
95
+ return local_path
96
+
97
+ except Exception as e:
98
+ print(f"Error downloading file {url}: {e}")
99
+ return url # Return original URL if download fails
100
+
101
+
102
+ def load_data(request: gr.Request, model_3d, image_slider):
103
+ """
104
+ Load data from query parameters, download files if needed,
105
+ and use current component values as defaults if no query parameters are provided.
106
+
107
+ If query parameters are provided, generate a permalink using storage.generate_permalink_from_urls.
108
+
109
+ Parameters:
110
+ request: Gradio request object containing query parameters.
111
+ model_3d: Current value or component for the 3D model.
112
+ image_slider: Current value or component for the image slider.
113
+
114
+ Returns:
115
+ tuple: (model_url, slider_images, permalink)
116
+ - model_url: processed URL for the 3D model.
117
+ - slider_images: processed list of image URLs.
118
+ - permalink: a generated permalink if query parameters were provided,
119
+ or an empty string if not.
120
+ """
121
+ # Parse query parameters.
122
+ query_params = dict(request.query_params) if request is not None else {}
123
+
124
+ # Extract URLs from query parameters.
125
+ model_url = query_params.get("3d", None)
126
+ hm_url = query_params.get("hm", None)
127
+ img_url = query_params.get("image", None)
128
+
129
+ # Process the model URL if provided.
130
+ if model_url:
131
+ model_url = process_url(model_url, default_ext=".glb")
132
+
133
+ # Process image URLs if provided.
134
  slider_images = []
 
 
135
  if img_url:
136
+ local_img = process_url(img_url, default_ext=".png")
137
+ if local_img:
138
+ slider_images.append(local_img)
139
+ if hm_url:
140
+ local_hm = process_url(hm_url, default_ext=".png")
141
+ if local_hm:
142
+ slider_images.append(local_hm)
143
+
144
+
145
+ # Set default values if no URLs provided:
146
+ default_model = getattr(model_3d, "value", model_3d)
147
+ default_images = getattr(image_slider, "value", image_slider)
148
+
149
  if not slider_images:
150
+ slider_images = default_images if default_images else constants.default_slider_images
151
+
152
  if not model_url:
153
+ model_url = default_model if default_model else constants.default_model_3d
154
+
155
+ # If any query parameters were provided, generate a permalink.
156
+ permalink = ""
157
+ if query_params:
158
+ try:
159
+ # Use the helper function defined in storage.py
160
+ permalink = storage.generate_permalink_from_urls(model_url, hm_url, img_url)
161
+ except Exception as e:
162
+ print(f"Error generating permalink: {e}")
163
+
164
+ return model_url, slider_images, permalink
165
 
166
  def process_upload(files, current_model, current_images):
167
  """
 
275
  max_lines=5,
276
  lines=3
277
  )
278
+ gr.Markdown("### Copy the permalink to share your model and images.", elem_classes="solid centered",)
279
  with gr.Row():
280
  gr.HTML(value=getVersions(), visible=True, elem_id="versions")
281
 
282
  # Use JavaScript to pass the query parameters to your callback.
283
  viewer3d.load(
284
  load_data,
285
+ inputs=[model_3d, image_slider],
286
+ outputs=[model_3d, image_slider, permalink],
 
 
 
 
287
  scroll_to_output=True
288
+ ).then(
289
+ # If the returned permalink (link) is non-empty then make the permalink row visible
290
+ # and disable the permalink button; otherwise, hide the row and enable the button.
291
+ lambda link: (gr.update(visible=True), gr.update(interactive=False))
292
+ if link and len(link) > 0
293
+ else (gr.update(visible=False), gr.update(interactive=True)),
294
+ inputs=[permalink],
295
+ outputs=[permalink_row, permalink_button]
296
  )
297
 
298
  # Process uploaded files to update the Model3D or ImageSlider component.
 
303
  scroll_to_output=True,
304
  api_name="process_upload",
305
  show_progress=True
306
+ ).then(
307
+ # After a successful upload, enable the permalink button.
308
+ lambda m, i: gr.update(interactive=True),
309
+ inputs=[model_3d, image_slider],
310
+ outputs=[permalink_button]
311
  )
312
  # Generate a permalink based on the current model, images, and folder name.
313
  permalink_button.click(
images/beeuty_545jlbh1_v12_alpha96_300dpi.png DELETED

Git LFS Details

  • SHA256: 3c12d3e23e3131195571378b143cbd310d4ed83bed28a0b178c2f6931c611ad7
  • Pointer size: 132 Bytes
  • Size of remote file: 5.03 MB
images/beeuty_545jlbh1_v12_alpha96_300dpi_depth.png DELETED

Git LFS Details

  • SHA256: f65cff4e1a39bc37405dfe6832161ebfdee01d25d688141be63a84dca7113e3e
  • Pointer size: 131 Bytes
  • Size of remote file: 217 kB
modules/constants.py CHANGED
@@ -27,4 +27,11 @@ model_extensions = {".glb", ".gltf", ".obj", ".ply"}
27
  model_extensions_list = list(model_extensions)
28
  image_extensions = {".png", ".jpg", ".jpeg", ".webp"}
29
  image_extensions_list = list(image_extensions)
30
- upload_file_types = model_extensions_list + image_extensions_list
 
 
 
 
 
 
 
 
27
  model_extensions_list = list(model_extensions)
28
  image_extensions = {".png", ".jpg", ".jpeg", ".webp"}
29
  image_extensions_list = list(image_extensions)
30
+ upload_file_types = model_extensions_list + image_extensions_list
31
+
32
+ default_slider_images = [
33
+ "images/slider/beeuty_545jlbh1_v12_alpha96_300dpi.png",
34
+ "images/slider/beeuty_545jlbh1_v12_alpha96_300dpi_depth.png"
35
+ ]
36
+
37
+ default_model_3d = "models/beeuty_545jlbh1_300dpi.glb"
modules/storage.py CHANGED
@@ -6,7 +6,50 @@ import shutil
6
  from huggingface_hub import login, upload_folder
7
  from modules.constants import HF_API_TOKEN, upload_file_types, model_extensions, image_extensions
8
 
9
- def upload_files_to_repo(files, repo_id, folder_name, create_permalink=False, repo_type="dataset"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  """
11
  Uploads multiple files to a Hugging Face repository using a batch upload approach via upload_folder.
12
 
@@ -32,7 +75,7 @@ def upload_files_to_repo(files, repo_id, folder_name, create_permalink=False, re
32
  valid_files = []
33
 
34
  # Ensure folder_name does not have a trailing slash.
35
- folder_name = folder_name.rstrip("/")
36
 
37
  # Filter for valid files based on allowed extensions.
38
  for f in files:
@@ -46,7 +89,7 @@ def upload_files_to_repo(files, repo_id, folder_name, create_permalink=False, re
46
  if not valid_files:
47
  return [] # or raise an exception
48
 
49
- # Create a temporary directory; copy valid files directly into it (without using folder_name).
50
  with tempfile.TemporaryDirectory() as temp_dir:
51
  for file_path in valid_files:
52
  filename = os.path.basename(file_path)
@@ -73,25 +116,11 @@ def upload_files_to_repo(files, repo_id, folder_name, create_permalink=False, re
73
  link = f"{base_url_external}/{filename}"
74
  individual_links.append(link)
75
 
76
- # If exactly 3 files are provided and create_permalink is True,
77
- # check if they contain 1 model file and 2 image files.
78
  if create_permalink and len(valid_files) == 3:
79
- model_link = None
80
- images_links = []
81
- for f in valid_files:
82
- filename = os.path.basename(f)
83
- ext = os.path.splitext(filename)[1].lower()
84
- if ext in model_extensions:
85
- if model_link is None:
86
- model_link = f"{base_url_external}/{filename}"
87
- elif ext in image_extensions:
88
- images_links.append(f"{base_url_external}/{filename}")
89
- if model_link and len(images_links) == 2:
90
- # Construct a permalink to the viewer project with querystring parameters.
91
- base_viewer_url = "https://huggingface.co/spaces/Surn/3D-Viewer"
92
- params = {"3d": model_link, "hm": images_links[0], "image": images_links[1]}
93
- query_str = urllib.parse.urlencode(params)
94
- permalink = f"{base_viewer_url}?{query_str}"
95
  return response, permalink
96
 
97
  # Otherwise, return individual tuples for each file.
 
6
  from huggingface_hub import login, upload_folder
7
  from modules.constants import HF_API_TOKEN, upload_file_types, model_extensions, image_extensions
8
 
9
+ def generate_permalink(valid_files, base_url_external, permalink_viewer_url="surn-3d-viewer.hf.space"):
10
+ """
11
+ Given a list of valid files, checks if they contain exactly 1 model file and 2 image files.
12
+ Constructs and returns a permalink URL with query parameters if the criteria is met.
13
+ Otherwise, returns None.
14
+ """
15
+ model_link = None
16
+ images_links = []
17
+ for f in valid_files:
18
+ filename = os.path.basename(f)
19
+ ext = os.path.splitext(filename)[1].lower()
20
+ if ext in model_extensions:
21
+ if model_link is None:
22
+ model_link = f"{base_url_external}/{filename}"
23
+ elif ext in image_extensions:
24
+ images_links.append(f"{base_url_external}/{filename}")
25
+ if model_link and len(images_links) == 2:
26
+ # Construct a permalink to the viewer project with query parameters.
27
+ permalink_viewer_url = f"https:{permalink_viewer_url}/"
28
+ params = {"3d": model_link, "hm": images_links[0], "image": images_links[1]}
29
+ query_str = urllib.parse.urlencode(params)
30
+ return f"{permalink_viewer_url}?{query_str}"
31
+ return None
32
+
33
+ def generate_permalink_from_urls(model_url, hm_url, img_url, permalink_viewer_url="surn-3d-viewer.hf.space"):
34
+ """
35
+ Constructs and returns a permalink URL with query string parameters for the viewer.
36
+ Each parameter is passed separately so that the image positions remain consistent.
37
+
38
+ Parameters:
39
+ model_url (str): Processed URL for the 3D model.
40
+ hm_url (str): Processed URL for the height map image.
41
+ img_url (str): Processed URL for the main image.
42
+ permalink_viewer_url (str): The base viewer URL.
43
+
44
+ Returns:
45
+ str: The generated permalink URL.
46
+ """
47
+ import urllib.parse
48
+ params = {"3d": model_url, "hm": hm_url, "image": img_url}
49
+ query_str = urllib.parse.urlencode(params)
50
+ return f"https://{permalink_viewer_url}/?{query_str}"
51
+
52
+ def upload_files_to_repo(files, repo_id, folder_name, create_permalink=False, repo_type="dataset", permalink_viewer_url="surn-3d-viewer.hf.space"):
53
  """
54
  Uploads multiple files to a Hugging Face repository using a batch upload approach via upload_folder.
55
 
 
75
  valid_files = []
76
 
77
  # Ensure folder_name does not have a trailing slash.
78
+ folder_name = folder_name.rstrip("/")
79
 
80
  # Filter for valid files based on allowed extensions.
81
  for f in files:
 
89
  if not valid_files:
90
  return [] # or raise an exception
91
 
92
+ # Create a temporary directory; copy valid files directly into it.
93
  with tempfile.TemporaryDirectory() as temp_dir:
94
  for file_path in valid_files:
95
  filename = os.path.basename(file_path)
 
116
  link = f"{base_url_external}/{filename}"
117
  individual_links.append(link)
118
 
119
+ # If permalink creation is requested and exactly 3 valid files are provided,
120
+ # try to generate a permalink using generate_permalink().
121
  if create_permalink and len(valid_files) == 3:
122
+ permalink = generate_permalink(valid_files, base_url_external, permalink_viewer_url)
123
+ if permalink:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  return response, permalink
125
 
126
  # Otherwise, return individual tuples for each file.
requirements.txt CHANGED
@@ -1,3 +1,4 @@
1
  torch --extra-index-url https://download.pytorch.org/whl/cu124
2
  transformers
3
- python-dotenv
 
 
1
  torch --extra-index-url https://download.pytorch.org/whl/cu124
2
  transformers
3
+ python-dotenv
4
+ huggingface_hub