Spaces:
Running
on
Zero
Running
on
Zero
Permalink Update
Browse files- app.py +88 -9
- requirements.txt +3 -3
- utils/constants.py +6 -0
- utils/file_utils.py +1 -20
- utils/storage.py +127 -0
app.py
CHANGED
@@ -34,9 +34,12 @@ import gc
|
|
34 |
# Import functions from modules
|
35 |
from utils.file_utils import (
|
36 |
cleanup_temp_files,
|
37 |
-
get_file_parts
|
38 |
-
generate_permalink_from_urls
|
39 |
)
|
|
|
|
|
|
|
|
|
40 |
|
41 |
from utils.color_utils import (
|
42 |
hex_to_rgb,
|
@@ -133,6 +136,7 @@ user_dir = constants.TMPDIR
|
|
133 |
lora_models = get_lora_models()
|
134 |
selected_index = gr.State(value=-1)
|
135 |
#html_versions = version_info.versions_html()
|
|
|
136 |
|
137 |
image_processor: Optional[DPTImageProcessor] = None
|
138 |
depth_model: Optional[DPTForDepthEstimation] = None
|
@@ -804,7 +808,7 @@ def load_trellis_model():
|
|
804 |
loaded = False
|
805 |
if TRELLIS_PIPELINE == None:
|
806 |
try:
|
807 |
-
TRELLIS_PIPELINE = TrellisImageTo3DPipeline.from_pretrained("
|
808 |
TRELLIS_PIPELINE.cuda()
|
809 |
# Preload with a dummy image to finalize initialization
|
810 |
try:
|
@@ -1175,6 +1179,42 @@ def update_permalink(glb, gaussian, depth_out, depth_src_file):
|
|
1175 |
permalink = generate_permalink_from_urls(smallest_file, depth_out, depth_src_file)
|
1176 |
return gr.update(visible=True), permalink
|
1177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1178 |
@spaces.GPU()
|
1179 |
def getVersions():
|
1180 |
#return html_versions
|
@@ -1595,9 +1635,28 @@ with gr.Blocks(css_paths="style_20250314.css", title=title, theme='Surn/beeuty',
|
|
1595 |
gr.Markdown("""
|
1596 |
### Files over 10 MB may not display in the 3D model viewer
|
1597 |
""", elem_id="file_size_info", elem_classes="intro" )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1598 |
with gr.Row(visible=False) as permalink_row:
|
1599 |
-
|
1600 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
1601 |
|
1602 |
is_multiimage = gr.State(False)
|
1603 |
output_buf = gr.State()
|
@@ -1847,8 +1906,8 @@ with gr.Blocks(css_paths="style_20250314.css", title=title, theme='Surn/beeuty',
|
|
1847 |
outputs=[glb_file]
|
1848 |
).then(
|
1849 |
fn=update_permalink,
|
1850 |
-
inputs=[glb_file, gaussian_file, depth_output, ddd_image_path], #
|
1851 |
-
outputs=[permalink_row,
|
1852 |
)
|
1853 |
|
1854 |
extract_gaussian_btn.click(
|
@@ -1861,9 +1920,29 @@ with gr.Blocks(css_paths="style_20250314.css", title=title, theme='Surn/beeuty',
|
|
1861 |
).then(
|
1862 |
fn=update_permalink,
|
1863 |
inputs=[glb_file, gaussian_file, depth_output, ddd_image_path], # Use the actual depth source file here
|
1864 |
-
outputs=[permalink_row,
|
1865 |
)
|
1866 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1867 |
if __name__ == "__main__":
|
1868 |
constants.load_env_vars(constants.dotenv_path)
|
1869 |
logging.basicConfig(
|
@@ -1896,7 +1975,7 @@ if __name__ == "__main__":
|
|
1896 |
# image_processor = DPTImageProcessor.from_pretrained("Intel/dpt-large")
|
1897 |
# depth_model = DPTForDepthEstimation.from_pretrained("Intel/dpt-large", ignore_mismatched_sizes=True)
|
1898 |
if constants.IS_SHARED_SPACE:
|
1899 |
-
TRELLIS_PIPELINE = TrellisImageTo3DPipeline.from_pretrained("
|
1900 |
TRELLIS_PIPELINE.to(device)
|
1901 |
try:
|
1902 |
TRELLIS_PIPELINE.preprocess_image(Image.fromarray(np.zeros((512, 512, 3), dtype=np.uint8)), 512, True) # Preload rembg
|
|
|
34 |
# Import functions from modules
|
35 |
from utils.file_utils import (
|
36 |
cleanup_temp_files,
|
37 |
+
get_file_parts
|
|
|
38 |
)
|
39 |
+
from utils.storage import (
|
40 |
+
generate_permalink_from_urls,
|
41 |
+
upload_files_to_repo,
|
42 |
+
)
|
43 |
|
44 |
from utils.color_utils import (
|
45 |
hex_to_rgb,
|
|
|
136 |
lora_models = get_lora_models()
|
137 |
selected_index = gr.State(value=-1)
|
138 |
#html_versions = version_info.versions_html()
|
139 |
+
default_folder = "saved_models/3d_model_" + format(random.randint(1, 999999), "06d")
|
140 |
|
141 |
image_processor: Optional[DPTImageProcessor] = None
|
142 |
depth_model: Optional[DPTForDepthEstimation] = None
|
|
|
808 |
loaded = False
|
809 |
if TRELLIS_PIPELINE == None:
|
810 |
try:
|
811 |
+
TRELLIS_PIPELINE = TrellisImageTo3DPipeline.from_pretrained("Surn/TRELLIS-image-large")
|
812 |
TRELLIS_PIPELINE.cuda()
|
813 |
# Preload with a dummy image to finalize initialization
|
814 |
try:
|
|
|
1179 |
permalink = generate_permalink_from_urls(smallest_file, depth_out, depth_src_file)
|
1180 |
return gr.update(visible=True), permalink
|
1181 |
|
1182 |
+
def create_permalink(glb, gaussian, depth_out, depth_src_file, folder_name_permalink):
|
1183 |
+
"""
|
1184 |
+
create a permalink for the 3D model and depth image.
|
1185 |
+
Args:
|
1186 |
+
glb (str): Path to the extracted GLB file.
|
1187 |
+
gaussian (str): Path to the extracted Gaussian file.
|
1188 |
+
depth_out (str): File path for the depth image.
|
1189 |
+
depth_src_file (str): File path for the image used to generate the 3D model.
|
1190 |
+
folder_path (str): The folder path where the files are stored.
|
1191 |
+
Returns:
|
1192 |
+
str: The permalink URL.
|
1193 |
+
"""
|
1194 |
+
file_candidates = {}
|
1195 |
+
if glb and os.path.exists(glb):
|
1196 |
+
file_candidates[glb] = os.path.getsize(glb)
|
1197 |
+
if gaussian and os.path.exists(gaussian):
|
1198 |
+
file_candidates[gaussian] = os.path.getsize(gaussian)
|
1199 |
+
|
1200 |
+
# if not file_candidates:
|
1201 |
+
# return gr.update(visible=False), ""
|
1202 |
+
smallest_file = min(file_candidates, key=file_candidates.get)
|
1203 |
+
|
1204 |
+
permalink = upload_files_to_repo(
|
1205 |
+
files=[smallest_file, depth_out, depth_src_file],
|
1206 |
+
repo_id="Surn/Storage",
|
1207 |
+
folder_name=folder_name_permalink,
|
1208 |
+
create_permalink=True,
|
1209 |
+
repo_type="dataset"
|
1210 |
+
)[1]
|
1211 |
+
return permalink
|
1212 |
+
|
1213 |
+
def open_permalink(url: str) -> str:
|
1214 |
+
if url and url.strip():
|
1215 |
+
return f'<script>window.open("{url}", "_blank");</script>'
|
1216 |
+
return '<script>alert("Permalink textbox is empty!");</script>'
|
1217 |
+
|
1218 |
@spaces.GPU()
|
1219 |
def getVersions():
|
1220 |
#return html_versions
|
|
|
1635 |
gr.Markdown("""
|
1636 |
### Files over 10 MB may not display in the 3D model viewer
|
1637 |
""", elem_id="file_size_info", elem_classes="intro" )
|
1638 |
+
|
1639 |
+
# New UI elements for general file upload and permalink generation
|
1640 |
+
with gr.Column(scale=1):
|
1641 |
+
folder_name_permalink = gr.Textbox(
|
1642 |
+
label="Folder Name in Permalink Repo",
|
1643 |
+
placeholder="e.g., my_cool_model_set",
|
1644 |
+
value=default_folder, # Default folder name
|
1645 |
+
elem_classes="solid small centered"
|
1646 |
+
)
|
1647 |
+
permalink_button = gr.Button(
|
1648 |
+
"Generate Permalink with Folder Name in Repo",
|
1649 |
+
elem_classes="solid small centered"
|
1650 |
+
)
|
1651 |
with gr.Row(visible=False) as permalink_row:
|
1652 |
+
permalink = gr.Textbox(
|
1653 |
+
label="Viewer Permalink",
|
1654 |
+
show_copy_button=True,
|
1655 |
+
elem_classes="solid small centered",
|
1656 |
+
max_lines=3
|
1657 |
+
)
|
1658 |
+
view_permalink_button = gr.Button("View in Permalink", elem_classes="solid small centered", variant="secondary")
|
1659 |
+
hidden_html = gr.HTML(visible=False)
|
1660 |
|
1661 |
is_multiimage = gr.State(False)
|
1662 |
output_buf = gr.State()
|
|
|
1906 |
outputs=[glb_file]
|
1907 |
).then(
|
1908 |
fn=update_permalink,
|
1909 |
+
inputs=[glb_file, gaussian_file, depth_output, ddd_image_path], # Use the actual depth source file here
|
1910 |
+
outputs=[permalink_row, permalink] # This updates the GLB/Gaussian specific permalink
|
1911 |
)
|
1912 |
|
1913 |
extract_gaussian_btn.click(
|
|
|
1920 |
).then(
|
1921 |
fn=update_permalink,
|
1922 |
inputs=[glb_file, gaussian_file, depth_output, ddd_image_path], # Use the actual depth source file here
|
1923 |
+
outputs=[permalink_row, permalink] # This updates the GLB/Gaussian specific permalink
|
1924 |
)
|
1925 |
|
1926 |
+
# Create a permalink based on the current model, images, and folder name.
|
1927 |
+
permalink_button.click(
|
1928 |
+
fn=create_permalink,
|
1929 |
+
inputs=[glb_file, gaussian_file, depth_output, ddd_image_path, folder_name_permalink],
|
1930 |
+
outputs=[permalink],
|
1931 |
+
scroll_to_output=True
|
1932 |
+
).then(
|
1933 |
+
lambda link: gr.update(visible=True) if link and len(link) > 0 else gr.update(visible=False),
|
1934 |
+
inputs=[permalink],
|
1935 |
+
outputs=[permalink_row]
|
1936 |
+
)
|
1937 |
+
|
1938 |
+
view_permalink_button.click(
|
1939 |
+
fn=open_permalink,
|
1940 |
+
inputs=[permalink],
|
1941 |
+
outputs=[hidden_html],
|
1942 |
+
show_progress=False
|
1943 |
+
)
|
1944 |
+
|
1945 |
+
|
1946 |
if __name__ == "__main__":
|
1947 |
constants.load_env_vars(constants.dotenv_path)
|
1948 |
logging.basicConfig(
|
|
|
1975 |
# image_processor = DPTImageProcessor.from_pretrained("Intel/dpt-large")
|
1976 |
# depth_model = DPTForDepthEstimation.from_pretrained("Intel/dpt-large", ignore_mismatched_sizes=True)
|
1977 |
if constants.IS_SHARED_SPACE:
|
1978 |
+
TRELLIS_PIPELINE = TrellisImageTo3DPipeline.from_pretrained("Surn/TRELLIS-image-large")
|
1979 |
TRELLIS_PIPELINE.to(device)
|
1980 |
try:
|
1981 |
TRELLIS_PIPELINE.preprocess_image(Image.fromarray(np.zeros((512, 512, 3), dtype=np.uint8)), 512, True) # Preload rembg
|
requirements.txt
CHANGED
@@ -73,6 +73,6 @@ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.3/flash_attn
|
|
73 |
https://huggingface.co/spaces/Surn/HexaGrid/resolve/main/wheels/diff_gaussian_rasterization-0.0.0-cp310-cp310-linux_x86_64.whl?download=true
|
74 |
https://huggingface.co/spaces/Surn/Hexagrid/resolve/main/wheels/nvdiffrast-0.3.3-cp310-cp310-linux_x86_64.whl?download=true
|
75 |
#Windows only
|
76 |
-
#https://huggingface.co/spaces/Surn/HexaGrid/main/wheels/flash_attn-2.7.4.post1-cp312-cp312-win_amd64.whl?download=true
|
77 |
-
#https://huggingface.co/spaces/Surn/HexaGrid/main/wheels/diff_gaussian_rasterization-0.0.0-cp312-cp312-win_amd64.whl?download=true
|
78 |
-
#https://huggingface.co/spaces/Surn/HexaGrid/main/wheels/nvdiffrast-0.3.3-py3-none-any.whl
|
|
|
73 |
https://huggingface.co/spaces/Surn/HexaGrid/resolve/main/wheels/diff_gaussian_rasterization-0.0.0-cp310-cp310-linux_x86_64.whl?download=true
|
74 |
https://huggingface.co/spaces/Surn/Hexagrid/resolve/main/wheels/nvdiffrast-0.3.3-cp310-cp310-linux_x86_64.whl?download=true
|
75 |
#Windows only
|
76 |
+
#https://huggingface.co/spaces/Surn/HexaGrid/resolve/main/wheels/flash_attn-2.7.4.post1-cp312-cp312-win_amd64.whl?download=true
|
77 |
+
#https://huggingface.co/spaces/Surn/HexaGrid/resolve/main/wheels/diff_gaussian_rasterization-0.0.0-cp312-cp312-win_amd64.whl?download=true
|
78 |
+
#https://huggingface.co/spaces/Surn/HexaGrid/resolve/main/wheels/nvdiffrast-0.3.3-py3-none-any.whl
|
utils/constants.py
CHANGED
@@ -67,6 +67,12 @@ SCALE_FACTOR = (12/5)
|
|
67 |
TMPDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tmp')
|
68 |
os.makedirs(TMPDIR, exist_ok=True)
|
69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
70 |
|
71 |
PROMPTS = {
|
72 |
"Mecha Wasteland Arena": "Regional overhead view, directly from above, centered on the map, orthographic Mecha battlefield map. post-industrial wasteland with crumbling structures, volcanic ridges, scrapyards, and ash plains. Features elevated overwatch positions for long-range combat and tight brawling areas for close-quarters engagements. Partial edge hexes are black. Colors: red, gray, muted orange, ash white, dark brown.",
|
|
|
67 |
TMPDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tmp')
|
68 |
os.makedirs(TMPDIR, exist_ok=True)
|
69 |
|
70 |
+
model_extensions = {".glb", ".gltf", ".obj", ".ply"}
|
71 |
+
model_extensions_list = list(model_extensions)
|
72 |
+
image_extensions = {".png", ".jpg", ".jpeg", ".webp"}
|
73 |
+
image_extensions_list = list(image_extensions)
|
74 |
+
upload_file_types = model_extensions_list + image_extensions_list
|
75 |
+
|
76 |
|
77 |
PROMPTS = {
|
78 |
"Mecha Wasteland Arena": "Regional overhead view, directly from above, centered on the map, orthographic Mecha battlefield map. post-industrial wasteland with crumbling structures, volcanic ridges, scrapyards, and ash plains. Features elevated overwatch positions for long-range combat and tight brawling areas for close-quarters engagements. Partial edge hexes are black. Colors: red, gray, muted orange, ash white, dark brown.",
|
utils/file_utils.py
CHANGED
@@ -103,23 +103,4 @@ def get_unique_file_path(directory, filename, file_ext, counter=0):
|
|
103 |
return get_unique_file_path(directory, filename, file_ext, counter + 1)
|
104 |
|
105 |
# Example usage:
|
106 |
-
# new_file_path = get_unique_file_path(video_dir, title_file_name, video_new_ext)
|
107 |
-
|
108 |
-
def generate_permalink_from_urls(model_url, hm_url, img_url, permalink_viewer_url="surn-3d-viewer.hf.space"):
|
109 |
-
"""
|
110 |
-
Constructs and returns a permalink URL with query string parameters for the viewer.
|
111 |
-
Each parameter is passed separately so that the image positions remain consistent.
|
112 |
-
|
113 |
-
Parameters:
|
114 |
-
model_url (str): Processed URL for the 3D model.
|
115 |
-
hm_url (str): Processed URL for the height map image.
|
116 |
-
img_url (str): Processed URL for the main image.
|
117 |
-
permalink_viewer_url (str): The base viewer URL.
|
118 |
-
|
119 |
-
Returns:
|
120 |
-
str: The generated permalink URL.
|
121 |
-
"""
|
122 |
-
import urllib.parse
|
123 |
-
params = {"3d": model_url, "hm": hm_url, "image": img_url}
|
124 |
-
query_str = urllib.parse.urlencode(params)
|
125 |
-
return f"https://{permalink_viewer_url}/?{query_str}"
|
|
|
103 |
return get_unique_file_path(directory, filename, file_ext, counter + 1)
|
104 |
|
105 |
# Example usage:
|
106 |
+
# new_file_path = get_unique_file_path(video_dir, title_file_name, video_new_ext)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
utils/storage.py
ADDED
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# utils/storage.py
|
2 |
+
import os
|
3 |
+
import urllib.parse
|
4 |
+
import tempfile
|
5 |
+
import shutil
|
6 |
+
from huggingface_hub import login, upload_folder
|
7 |
+
from utils.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 |
+
|
56 |
+
Parameters:
|
57 |
+
files (list): A list of file paths (str) to upload.
|
58 |
+
repo_id (str): The repository ID on Hugging Face for storage, e.g. "Surn/Storage".
|
59 |
+
folder_name (str): The subfolder within the repository where files will be saved.
|
60 |
+
create_permalink (bool): If True and if exactly three files are uploaded (1 model and 2 images),
|
61 |
+
returns a single permalink to the project with query parameters.
|
62 |
+
Otherwise, returns individual permalinks for each file.
|
63 |
+
repo_type (str): Repository type ("space", "dataset", etc.). Default is "dataset".
|
64 |
+
|
65 |
+
Returns:
|
66 |
+
If create_permalink is True and files match the criteria:
|
67 |
+
tuple: (response, permalink) where response is the output of the batch upload
|
68 |
+
and permalink is the URL string (with fully qualified file paths) for the project.
|
69 |
+
Otherwise:
|
70 |
+
list: A list of tuples (response, permalink) for each file.
|
71 |
+
"""
|
72 |
+
# Log in using the HF API token.
|
73 |
+
login(token=HF_API_TOKEN)
|
74 |
+
|
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:
|
82 |
+
file_name = f if isinstance(f, str) else f.name if hasattr(f, "name") else None
|
83 |
+
if file_name is None:
|
84 |
+
continue
|
85 |
+
ext = os.path.splitext(file_name)[1].lower()
|
86 |
+
if ext in upload_file_types:
|
87 |
+
valid_files.append(f)
|
88 |
+
|
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)
|
96 |
+
dest_path = os.path.join(temp_dir, filename)
|
97 |
+
shutil.copy(file_path, dest_path)
|
98 |
+
|
99 |
+
# Batch upload all files in the temporary folder.
|
100 |
+
# Files will be uploaded under the folder (path_in_repo) given by folder_name.
|
101 |
+
response = upload_folder(
|
102 |
+
folder_path=temp_dir,
|
103 |
+
repo_id=repo_id,
|
104 |
+
repo_type=repo_type,
|
105 |
+
path_in_repo=folder_name,
|
106 |
+
commit_message="Batch upload files"
|
107 |
+
)
|
108 |
+
|
109 |
+
# Construct external URLs for each uploaded file.
|
110 |
+
# For datasets, files are served at:
|
111 |
+
# https://huggingface.co/datasets/<repo_id>/resolve/main/<folder_name>/<filename>
|
112 |
+
base_url_external = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{folder_name}"
|
113 |
+
individual_links = []
|
114 |
+
for file_path in valid_files:
|
115 |
+
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_from_urls(individual_links[0], individual_links[1], individual_links[2], permalink_viewer_url)
|
123 |
+
if permalink:
|
124 |
+
return [response, permalink]
|
125 |
+
|
126 |
+
# Otherwise, return individual tuples for each file.
|
127 |
+
return [(response, link) for link in individual_links]
|