Spaces:
Running on Zero
Running on Zero
Sync from Jahmori-R/PureVoiceAI-HG via hub-sync
Browse files- .github/workflows/publish.yml +0 -23
- .gitignore +0 -28
- README.md +7 -9
- app.py +33 -45
- pretrained_models/resepformer-librimix2spk/decoder.ckpt +0 -1
- pretrained_models/resepformer-librimix2spk/encoder.ckpt +0 -1
- pretrained_models/resepformer-librimix2spk/hyperparams.yaml +0 -1
- pretrained_models/resepformer-librimix2spk/masknet.ckpt +0 -1
- requirements.txt +2 -2
.github/workflows/publish.yml
DELETED
|
@@ -1,23 +0,0 @@
|
|
| 1 |
-
name: Sync to Hugging Face hub
|
| 2 |
-
on:
|
| 3 |
-
push:
|
| 4 |
-
branches: [main]
|
| 5 |
-
|
| 6 |
-
# to run this workflow manually from the Actions tab
|
| 7 |
-
workflow_dispatch:
|
| 8 |
-
|
| 9 |
-
jobs:
|
| 10 |
-
sync-to-hub:
|
| 11 |
-
runs-on: ubuntu-latest
|
| 12 |
-
steps:
|
| 13 |
-
- uses: actions/checkout@v2
|
| 14 |
-
with:
|
| 15 |
-
fetch-depth: 0
|
| 16 |
-
- name: Add remote
|
| 17 |
-
env:
|
| 18 |
-
HF: ${{ secrets.HF }}
|
| 19 |
-
run: git remote add space https://Jahmori-R:$HF@huggingface.co/spaces/Jahmori-R/PureVoiceAISpace
|
| 20 |
-
- name: Push to hub
|
| 21 |
-
env:
|
| 22 |
-
HF: ${{ secrets.HF }}
|
| 23 |
-
run: git push --force https://Jahmori-R:$HF@huggingface.co/spaces/Jahmori-R/PureVoiceAISpace main
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
DELETED
|
@@ -1,28 +0,0 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
# Virtual Environments
|
| 5 |
-
.env
|
| 6 |
-
.venv
|
| 7 |
-
env/
|
| 8 |
-
venv/
|
| 9 |
-
ENV/
|
| 10 |
-
|
| 11 |
-
#pyenv versions control
|
| 12 |
-
.python-version
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
# Ignore specific output directories
|
| 16 |
-
uploads
|
| 17 |
-
pretrained_models
|
| 18 |
-
local
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
# Audio formats from processing
|
| 22 |
-
*.wav
|
| 23 |
-
*.flac
|
| 24 |
-
*.mp3
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
# Configs
|
| 28 |
-
pyrightconfig.json
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -9,26 +9,24 @@ python_version: 3.12.12
|
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
license: apache-2.0
|
| 12 |
-
short_description:
|
| 13 |
---
|
| 14 |
|
| 15 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 16 |
-
|
| 17 |
# PureVoiceAI
|
| 18 |
|
| 19 |
-
An
|
| 20 |
|
| 21 |
## Key Features
|
| 22 |
|
| 23 |
-
- **Advanced Source Separation:** Utilizes
|
| 24 |
-
- **
|
| 25 |
-
- **Speech Transcription:** Converts the cleaned, separated audio streams into highly accurate text.
|
| 26 |
-
- **
|
| 27 |
|
| 28 |
## Tech Stack
|
| 29 |
|
| 30 |
- **Language:** Python
|
| 31 |
-
- **Package Management:**
|
| 32 |
- **Audio Frameworks:** SpeechBrain
|
| 33 |
- **Core ML Models:** RE-SepFormer, pyAnnote, OpenAI Whisper
|
| 34 |
|
|
|
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
license: apache-2.0
|
| 12 |
+
short_description: AI speech logging transcription pipeline
|
| 13 |
---
|
| 14 |
|
|
|
|
|
|
|
| 15 |
# PureVoiceAI
|
| 16 |
|
| 17 |
+
An end-to-end audio processing pipeline designed to improve the quality of transcription by addressing the speech overlap problem. PureVoiceAI integrates machine learning models to perform audio source separation, voice activity detection, and speech transcription, turning noisy, overlapping audio into clean, attributed text.
|
| 18 |
|
| 19 |
## Key Features
|
| 20 |
|
| 21 |
+
- **Advanced Source Separation:** Utilizes custom trained model **RE-SepFormer** to isolate individual voices from noisy or overlapping audio tracks.
|
| 22 |
+
- **Voice Activity Detection:** Accurately determines "who spoke when," ensuring that transcribed text is correctly attributed to individual speakers.
|
| 23 |
+
- **Speech Transcription:** Converts the cleaned, separated audio streams into highly accurate text given voice activity timestamps.
|
| 24 |
+
- **Toolkits:** Built on top of audio processing toolkit [SpeechBrain](https://speechbrain.github.io/).
|
| 25 |
|
| 26 |
## Tech Stack
|
| 27 |
|
| 28 |
- **Language:** Python
|
| 29 |
+
- **Package Management:** pip
|
| 30 |
- **Audio Frameworks:** SpeechBrain
|
| 31 |
- **Core ML Models:** RE-SepFormer, pyAnnote, OpenAI Whisper
|
| 32 |
|
app.py
CHANGED
|
@@ -14,20 +14,22 @@ from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline
|
|
| 14 |
from speechbrain.inference.separation import SepformerSeparation
|
| 15 |
from pydub import AudioSegment
|
| 16 |
|
| 17 |
-
#
|
| 18 |
torch.backends.cuda.matmul.allow_tf32 = False
|
| 19 |
torch.backends.cudnn.allow_tf32 = False
|
| 20 |
|
| 21 |
-
|
| 22 |
|
| 23 |
-
|
| 24 |
-
token = os.environ.get("HF_TOKEN")
|
| 25 |
temp_dir = tempfile.gettempdir()
|
| 26 |
-
|
| 27 |
|
| 28 |
@spaces.GPU
|
| 29 |
def process_audio(mixture):
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
# Name of separated file folder
|
| 33 |
sources = Path(temp_dir) / "sources"
|
|
@@ -36,10 +38,12 @@ def process_audio(mixture):
|
|
| 36 |
Path.mkdir(sources, exist_ok=True)
|
| 37 |
|
| 38 |
# Downloads RE-SepFormer model from huggingface buckets to space local directory
|
| 39 |
-
sync_bucket("hf://buckets/Jahmori-R/PureVoiceAISpace-storage", "./local")
|
|
|
|
|
|
|
| 40 |
|
| 41 |
# ---------------------------------------------------------
|
| 42 |
-
# INITIALIZE MODELS
|
| 43 |
# ---------------------------------------------------------
|
| 44 |
|
| 45 |
print("Intializing models...")
|
|
@@ -47,28 +51,22 @@ def process_audio(mixture):
|
|
| 47 |
# Loading Resepformer Model
|
| 48 |
try:
|
| 49 |
separator = SepformerSeparation.from_hparams(
|
| 50 |
-
source=f"
|
| 51 |
hparams_file="hyperparams.yaml",
|
| 52 |
-
run_opts={"device":
|
| 53 |
)
|
| 54 |
-
|
| 55 |
-
# separator = SepformerSeparation.from_hparams(
|
| 56 |
-
# source="Jahmori-R/resepformer-librimix2spk",
|
| 57 |
-
# savedir=f"./pretrained_models/resepformer-librimix2spk",
|
| 58 |
-
# # run_opts={"device": "cuda"},
|
| 59 |
-
# # fetch_config=FetchConfig(token=True)
|
| 60 |
-
# )
|
| 61 |
-
|
| 62 |
print("RE-SepFormer model successfully loaded")
|
| 63 |
except Exception as e:
|
| 64 |
print(f"Failed to load RE-SepFormer model. Error: {e}")
|
| 65 |
raise SystemExit()
|
| 66 |
|
| 67 |
-
#
|
|
|
|
| 68 |
try:
|
| 69 |
diarize = Pipeline.from_pretrained(
|
| 70 |
"pyannote/speaker-diarization-community-1", token=token
|
| 71 |
)
|
|
|
|
| 72 |
diarize.to(torch.device("cuda"))
|
| 73 |
print("Pyannote model successfully loaded")
|
| 74 |
except Exception as e:
|
|
@@ -76,8 +74,7 @@ def process_audio(mixture):
|
|
| 76 |
raise SystemExit()
|
| 77 |
|
| 78 |
# Loading Whisper Model
|
| 79 |
-
# Prevents WhisperAI logging in cmd
|
| 80 |
-
transformers.logging.set_verbosity_error()
|
| 81 |
try:
|
| 82 |
transcription_model_id = "openai/whisper-large-v3-turbo"
|
| 83 |
torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32
|
|
@@ -108,15 +105,16 @@ def process_audio(mixture):
|
|
| 108 |
# LOAD & SEPARATE AUDIO
|
| 109 |
# ---------------------------------------------------------
|
| 110 |
|
| 111 |
-
# Importing mixed audio source
|
| 112 |
|
| 113 |
-
# Calling ReSepformer model onto audio source
|
| 114 |
assert separator is not None, "Failed to load the Sepformer model."
|
| 115 |
|
|
|
|
| 116 |
print("Separating Audio Mixture")
|
| 117 |
est_sources = separator.separate_file(mixture)
|
| 118 |
print("Mixture separated")
|
| 119 |
|
|
|
|
| 120 |
torchaudio.save(
|
| 121 |
f"{sources}/speaker_1.wav", est_sources[:, :, 0].detach().cpu(), 16000
|
| 122 |
)
|
|
@@ -125,17 +123,17 @@ def process_audio(mixture):
|
|
| 125 |
)
|
| 126 |
|
| 127 |
# ---------------------------------------------------------
|
| 128 |
-
#
|
| 129 |
# ---------------------------------------------------------
|
| 130 |
|
| 131 |
# Dictionary to store speaker information
|
| 132 |
unfiltered_log = [[], []]
|
| 133 |
-
overlap_durations = []
|
| 134 |
|
| 135 |
# Storing both speakers
|
| 136 |
file_paths = sorted(
|
|
|
|
| 137 |
glob.glob(f"{sources}/speaker_*.wav")
|
| 138 |
-
)
|
| 139 |
|
| 140 |
spk_1 = file_paths[0]
|
| 141 |
spk_2 = file_paths[1]
|
|
@@ -148,7 +146,7 @@ def process_audio(mixture):
|
|
| 148 |
print(f"Diarizing {speaker_key}")
|
| 149 |
diarization = diarize(path, num_speakers=1)
|
| 150 |
|
| 151 |
-
# Storing speaker voice activity
|
| 152 |
for turn, _ in diarization.speaker_diarization:
|
| 153 |
unfiltered_log[i].append(
|
| 154 |
{
|
|
@@ -158,6 +156,8 @@ def process_audio(mixture):
|
|
| 158 |
}
|
| 159 |
)
|
| 160 |
|
|
|
|
|
|
|
| 161 |
audio_seg = AudioSegment.from_file(path)
|
| 162 |
|
| 163 |
print("Transcribing Chunks...")
|
|
@@ -168,10 +168,11 @@ def process_audio(mixture):
|
|
| 168 |
end_ms = segment["end"] * 1000
|
| 169 |
chunk = audio_seg[start_ms:end_ms]
|
| 170 |
|
| 171 |
-
# Converting pydub AudioSegment to numpy array
|
| 172 |
samples = np.array(chunk.get_array_of_samples())
|
| 173 |
samples = samples.astype(np.float32) / 32768.0
|
| 174 |
|
|
|
|
| 175 |
transcription = transcribe(
|
| 176 |
samples,
|
| 177 |
generate_kwargs={
|
|
@@ -179,6 +180,7 @@ def process_audio(mixture):
|
|
| 179 |
"condition_on_prev_tokens": True,
|
| 180 |
},
|
| 181 |
)
|
|
|
|
| 182 |
segment["transcription"] = transcription
|
| 183 |
|
| 184 |
# Combined both speaker logs
|
|
@@ -187,26 +189,11 @@ def process_audio(mixture):
|
|
| 187 |
# Sorting by start time of timestamps
|
| 188 |
filtered_log = sorted(combined_log, key=lambda x: x["start"])
|
| 189 |
|
| 190 |
-
# Calculating the overlap between each segment to assign appropriate speaker to segment
|
| 191 |
-
# for log, log_next in zip(filtered_log, filtered_log[1:]):
|
| 192 |
-
# current_start = log["start"]
|
| 193 |
-
# current_end = log["end"]
|
| 194 |
-
|
| 195 |
-
# next_start = log_next["start"]
|
| 196 |
-
# next_end = log_next["end"]
|
| 197 |
-
|
| 198 |
-
# overlap_start = max(current_start, next_start)
|
| 199 |
-
# overlap_end = min(current_end, next_end)
|
| 200 |
-
# overlap = max(0.0, overlap_end - overlap_start)
|
| 201 |
-
# overlap_durations.append(overlap)
|
| 202 |
-
|
| 203 |
-
# Overlkap formula = Min(end 1, end 2) - Max(start 1, start 2)
|
| 204 |
-
|
| 205 |
print("Task Finished")
|
| 206 |
return (spk_1, spk_2, filtered_log)
|
| 207 |
|
| 208 |
|
| 209 |
-
# css for
|
| 210 |
css = """
|
| 211 |
|
| 212 |
#centered-row {
|
|
@@ -215,6 +202,7 @@ css = """
|
|
| 215 |
}
|
| 216 |
"""
|
| 217 |
|
|
|
|
| 218 |
with gr.Blocks(css=css) as demo:
|
| 219 |
gr.Markdown("PureVoiceAI")
|
| 220 |
with gr.Row(variant="panel"):
|
|
@@ -242,4 +230,4 @@ with gr.Blocks(css=css) as demo:
|
|
| 242 |
)
|
| 243 |
|
| 244 |
if __name__ == "__main__":
|
| 245 |
-
|
|
|
|
| 14 |
from speechbrain.inference.separation import SepformerSeparation
|
| 15 |
from pydub import AudioSegment
|
| 16 |
|
| 17 |
+
# Disables TF32 for to prevent lower accuracy in pyannote
|
| 18 |
torch.backends.cuda.matmul.allow_tf32 = False
|
| 19 |
torch.backends.cudnn.allow_tf32 = False
|
| 20 |
|
| 21 |
+
# Temporary Directory Intializing
|
| 22 |
|
|
|
|
|
|
|
| 23 |
temp_dir = tempfile.gettempdir()
|
| 24 |
+
print(f"temp directory created: {temp_dir}")
|
| 25 |
|
| 26 |
@spaces.GPU
|
| 27 |
def process_audio(mixture):
|
| 28 |
+
# GPU processing availability check
|
| 29 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 30 |
+
|
| 31 |
+
# Hugging Face authentication token grab
|
| 32 |
+
token = os.environ.get("HF_TOKEN")
|
| 33 |
|
| 34 |
# Name of separated file folder
|
| 35 |
sources = Path(temp_dir) / "sources"
|
|
|
|
| 38 |
Path.mkdir(sources, exist_ok=True)
|
| 39 |
|
| 40 |
# Downloads RE-SepFormer model from huggingface buckets to space local directory
|
| 41 |
+
# sync_bucket("hf://buckets/Jahmori-R/PureVoiceAISpace-storage", "./local")
|
| 42 |
+
|
| 43 |
+
|
| 44 |
|
| 45 |
# ---------------------------------------------------------
|
| 46 |
+
# 1. INITIALIZE MODELS
|
| 47 |
# ---------------------------------------------------------
|
| 48 |
|
| 49 |
print("Intializing models...")
|
|
|
|
| 51 |
# Loading Resepformer Model
|
| 52 |
try:
|
| 53 |
separator = SepformerSeparation.from_hparams(
|
| 54 |
+
source=f"Jahmori-R/resepformer-librimix2spk",
|
| 55 |
hparams_file="hyperparams.yaml",
|
| 56 |
+
run_opts={"device": device},
|
| 57 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
print("RE-SepFormer model successfully loaded")
|
| 59 |
except Exception as e:
|
| 60 |
print(f"Failed to load RE-SepFormer model. Error: {e}")
|
| 61 |
raise SystemExit()
|
| 62 |
|
| 63 |
+
# Loading Pyannote Model
|
| 64 |
+
|
| 65 |
try:
|
| 66 |
diarize = Pipeline.from_pretrained(
|
| 67 |
"pyannote/speaker-diarization-community-1", token=token
|
| 68 |
)
|
| 69 |
+
# Assigning GPU processing to model
|
| 70 |
diarize.to(torch.device("cuda"))
|
| 71 |
print("Pyannote model successfully loaded")
|
| 72 |
except Exception as e:
|
|
|
|
| 74 |
raise SystemExit()
|
| 75 |
|
| 76 |
# Loading Whisper Model
|
| 77 |
+
transformers.logging.set_verbosity_error() # Prevents WhisperAI logging in cmd line
|
|
|
|
| 78 |
try:
|
| 79 |
transcription_model_id = "openai/whisper-large-v3-turbo"
|
| 80 |
torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32
|
|
|
|
| 105 |
# LOAD & SEPARATE AUDIO
|
| 106 |
# ---------------------------------------------------------
|
| 107 |
|
|
|
|
| 108 |
|
| 109 |
+
# Calling ReSepformer model onto audio source
|
| 110 |
assert separator is not None, "Failed to load the Sepformer model."
|
| 111 |
|
| 112 |
+
# Running mixed audio onto separation model
|
| 113 |
print("Separating Audio Mixture")
|
| 114 |
est_sources = separator.separate_file(mixture)
|
| 115 |
print("Mixture separated")
|
| 116 |
|
| 117 |
+
# Saving separated sources to directory
|
| 118 |
torchaudio.save(
|
| 119 |
f"{sources}/speaker_1.wav", est_sources[:, :, 0].detach().cpu(), 16000
|
| 120 |
)
|
|
|
|
| 123 |
)
|
| 124 |
|
| 125 |
# ---------------------------------------------------------
|
| 126 |
+
# VAD + TRANSCRIPTION ON SOURCES
|
| 127 |
# ---------------------------------------------------------
|
| 128 |
|
| 129 |
# Dictionary to store speaker information
|
| 130 |
unfiltered_log = [[], []]
|
|
|
|
| 131 |
|
| 132 |
# Storing both speakers
|
| 133 |
file_paths = sorted(
|
| 134 |
+
# Glob finds all similar files
|
| 135 |
glob.glob(f"{sources}/speaker_*.wav")
|
| 136 |
+
)
|
| 137 |
|
| 138 |
spk_1 = file_paths[0]
|
| 139 |
spk_2 = file_paths[1]
|
|
|
|
| 146 |
print(f"Diarizing {speaker_key}")
|
| 147 |
diarization = diarize(path, num_speakers=1)
|
| 148 |
|
| 149 |
+
# Storing speaker voice activity timestamps to dictionary
|
| 150 |
for turn, _ in diarization.speaker_diarization:
|
| 151 |
unfiltered_log[i].append(
|
| 152 |
{
|
|
|
|
| 156 |
}
|
| 157 |
)
|
| 158 |
|
| 159 |
+
|
| 160 |
+
# Creating audio segment from detected voice activity
|
| 161 |
audio_seg = AudioSegment.from_file(path)
|
| 162 |
|
| 163 |
print("Transcribing Chunks...")
|
|
|
|
| 168 |
end_ms = segment["end"] * 1000
|
| 169 |
chunk = audio_seg[start_ms:end_ms]
|
| 170 |
|
| 171 |
+
# Converting pydub AudioSegment to numpy array for whisper model
|
| 172 |
samples = np.array(chunk.get_array_of_samples())
|
| 173 |
samples = samples.astype(np.float32) / 32768.0
|
| 174 |
|
| 175 |
+
# Generating transcription given chunk segment
|
| 176 |
transcription = transcribe(
|
| 177 |
samples,
|
| 178 |
generate_kwargs={
|
|
|
|
| 180 |
"condition_on_prev_tokens": True,
|
| 181 |
},
|
| 182 |
)
|
| 183 |
+
# Appending each segment transcription to dictionary
|
| 184 |
segment["transcription"] = transcription
|
| 185 |
|
| 186 |
# Combined both speaker logs
|
|
|
|
| 189 |
# Sorting by start time of timestamps
|
| 190 |
filtered_log = sorted(combined_log, key=lambda x: x["start"])
|
| 191 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
print("Task Finished")
|
| 193 |
return (spk_1, spk_2, filtered_log)
|
| 194 |
|
| 195 |
|
| 196 |
+
# Custom css for gradio interface
|
| 197 |
css = """
|
| 198 |
|
| 199 |
#centered-row {
|
|
|
|
| 202 |
}
|
| 203 |
"""
|
| 204 |
|
| 205 |
+
# Gradio layout configuration
|
| 206 |
with gr.Blocks(css=css) as demo:
|
| 207 |
gr.Markdown("PureVoiceAI")
|
| 208 |
with gr.Row(variant="panel"):
|
|
|
|
| 230 |
)
|
| 231 |
|
| 232 |
if __name__ == "__main__":
|
| 233 |
+
demo.launch(allowed_paths=[temp_dir], theme=gr.themes.Soft())
|
pretrained_models/resepformer-librimix2spk/decoder.ckpt
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
/home/jahmori/.cache/huggingface/hub/models--Jahmori-R--resepformer-librimix2spk/snapshots/b1986fdc97f0dcfd2f738fb8083c7acb661ab783/decoder.ckpt
|
|
|
|
|
|
pretrained_models/resepformer-librimix2spk/encoder.ckpt
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
/home/jahmori/.cache/huggingface/hub/models--Jahmori-R--resepformer-librimix2spk/snapshots/b1986fdc97f0dcfd2f738fb8083c7acb661ab783/encoder.ckpt
|
|
|
|
|
|
pretrained_models/resepformer-librimix2spk/hyperparams.yaml
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
/home/jahmori/.cache/huggingface/hub/models--Jahmori-R--resepformer-librimix2spk/snapshots/b1986fdc97f0dcfd2f738fb8083c7acb661ab783/hyperparams.yaml
|
|
|
|
|
|
pretrained_models/resepformer-librimix2spk/masknet.ckpt
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
/home/jahmori/.cache/huggingface/hub/models--Jahmori-R--resepformer-librimix2spk/snapshots/b1986fdc97f0dcfd2f738fb8083c7acb661ab783/masknet.ckpt
|
|
|
|
|
|
requirements.txt
CHANGED
|
@@ -37,8 +37,8 @@ frozenlist==1.8.0
|
|
| 37 |
fsspec==2026.7.0
|
| 38 |
future==1.0.0
|
| 39 |
googleapis-common-protos==1.75.3
|
| 40 |
-
gradio==6.
|
| 41 |
-
gradio_client==2.
|
| 42 |
greenlet==3.5.6
|
| 43 |
groovy==0.1.2
|
| 44 |
grpcio==1.84.0
|
|
|
|
| 37 |
fsspec==2026.7.0
|
| 38 |
future==1.0.0
|
| 39 |
googleapis-common-protos==1.75.3
|
| 40 |
+
gradio==6.26.0
|
| 41 |
+
gradio_client==2.6.1
|
| 42 |
greenlet==3.5.6
|
| 43 |
groovy==0.1.2
|
| 44 |
grpcio==1.84.0
|