Spaces:
Running
Running
Fix Gradio file reading (handle NamedString and file-like objects)
Browse files
app.py
CHANGED
|
@@ -117,28 +117,80 @@ def generate_video(avatar_file, audio_file):
|
|
| 117 |
"""
|
| 118 |
Gradio callback to generate a lip‑synced video.
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
Parameters
|
| 121 |
----------
|
| 122 |
-
avatar_file :
|
| 123 |
-
Uploaded image or video containing the face.
|
| 124 |
-
|
| 125 |
-
|
|
|
|
|
|
|
| 126 |
|
| 127 |
Returns
|
| 128 |
-------
|
| 129 |
-
str
|
| 130 |
-
Path to the generated MP4 file (relative to Gradio working directory)
|
|
|
|
| 131 |
"""
|
| 132 |
if avatar_file is None or audio_file is None:
|
| 133 |
return None
|
| 134 |
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
with tempfile.TemporaryDirectory() as tmpdir:
|
| 137 |
avatar_path = Path(tmpdir) / "avatar"
|
| 138 |
audio_path = Path(tmpdir) / "audio"
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
audio_path.write_bytes(audio_file.read())
|
| 142 |
# Validate audio length
|
| 143 |
try:
|
| 144 |
validate_audio_length(str(audio_path))
|
|
|
|
| 117 |
"""
|
| 118 |
Gradio callback to generate a lip‑synced video.
|
| 119 |
|
| 120 |
+
This function receives the uploaded avatar and audio files from Gradio's
|
| 121 |
+
``gr.File`` inputs. Depending on the ``type`` parameter of the file
|
| 122 |
+
component and the version of Gradio, the objects passed into this
|
| 123 |
+
function can take on different forms. They may be file-like objects
|
| 124 |
+
supporting ``read()``, simple strings containing a path on disk, or
|
| 125 |
+
``NamedString`` instances with a ``name`` attribute pointing to a
|
| 126 |
+
temporary file location. To robustly handle all of these cases, we
|
| 127 |
+
normalise the inputs by copying their contents into a temporary
|
| 128 |
+
directory, ensuring that subsequent processing always operates on
|
| 129 |
+
filesystem paths. This avoids ``AttributeError`` issues such as
|
| 130 |
+
``'NamedString' object has no attribute 'read'`` seen with newer
|
| 131 |
+
versions of Gradio.
|
| 132 |
+
|
| 133 |
Parameters
|
| 134 |
----------
|
| 135 |
+
avatar_file : Any
|
| 136 |
+
Uploaded image or video containing the face. Can be a file-like
|
| 137 |
+
object, a path string, or a NamedString/UploadFile depending on
|
| 138 |
+
Gradio version.
|
| 139 |
+
audio_file : Any
|
| 140 |
+
Uploaded audio file. Same possible types as ``avatar_file``.
|
| 141 |
|
| 142 |
Returns
|
| 143 |
-------
|
| 144 |
+
str | None
|
| 145 |
+
Path to the generated MP4 file (relative to Gradio working directory),
|
| 146 |
+
or ``None`` if either input is missing.
|
| 147 |
"""
|
| 148 |
if avatar_file is None or audio_file is None:
|
| 149 |
return None
|
| 150 |
|
| 151 |
+
def _copy_input_to_path(file_obj, dest_path: Path) -> None:
|
| 152 |
+
"""Copy the uploaded file into a destination path.
|
| 153 |
+
|
| 154 |
+
Parameters
|
| 155 |
+
----------
|
| 156 |
+
file_obj : Any
|
| 157 |
+
The object returned by Gradio's file component.
|
| 158 |
+
dest_path : Path
|
| 159 |
+
Destination path where the file should be written.
|
| 160 |
+
"""
|
| 161 |
+
# Case 1: file-like object (has .read attribute)
|
| 162 |
+
if hasattr(file_obj, "read"):
|
| 163 |
+
dest_path.write_bytes(file_obj.read())
|
| 164 |
+
return
|
| 165 |
+
# Case 2: file object implements .getvalue (e.g. io.BytesIO)
|
| 166 |
+
if hasattr(file_obj, "getvalue"):
|
| 167 |
+
dest_path.write_bytes(file_obj.getvalue())
|
| 168 |
+
return
|
| 169 |
+
# Case 3: NamedString or similar with a .name attribute (points to a temp file)
|
| 170 |
+
filename = None
|
| 171 |
+
if hasattr(file_obj, "name") and isinstance(getattr(file_obj, "name"), (str, bytes)):
|
| 172 |
+
filename = file_obj.name
|
| 173 |
+
elif hasattr(file_obj, "path") and isinstance(getattr(file_obj, "path"), (str, bytes)):
|
| 174 |
+
filename = file_obj.path
|
| 175 |
+
# Case 4: the input itself is a string/Path representing a path on disk
|
| 176 |
+
if filename is None and isinstance(file_obj, (str, os.PathLike)):
|
| 177 |
+
filename = str(file_obj)
|
| 178 |
+
if filename is not None:
|
| 179 |
+
# Copy the file from its existing location
|
| 180 |
+
shutil.copy(filename, dest_path)
|
| 181 |
+
else:
|
| 182 |
+
# Last resort: try to convert to bytes directly
|
| 183 |
+
try:
|
| 184 |
+
dest_path.write_bytes(bytes(file_obj))
|
| 185 |
+
except Exception:
|
| 186 |
+
raise gr.Error(f"Unsupported input type: {type(file_obj)}")
|
| 187 |
+
|
| 188 |
+
# Save uploaded files to a temporary directory
|
| 189 |
with tempfile.TemporaryDirectory() as tmpdir:
|
| 190 |
avatar_path = Path(tmpdir) / "avatar"
|
| 191 |
audio_path = Path(tmpdir) / "audio"
|
| 192 |
+
_copy_input_to_path(avatar_file, avatar_path)
|
| 193 |
+
_copy_input_to_path(audio_file, audio_path)
|
|
|
|
| 194 |
# Validate audio length
|
| 195 |
try:
|
| 196 |
validate_audio_length(str(audio_path))
|