neuralworm commited on
Commit
79ee34b
·
verified ·
1 Parent(s): eb37fc2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +54 -111
app.py CHANGED
@@ -1,8 +1,8 @@
1
- #!/usr/bin/env python3
2
  # coding: utf-8
3
 
4
  """ Hugging Face Space (Gradio) App: Video -> Audio -> Whisper Transkript (+ Downloads SRT/TXT/VTT/JSON)
5
- LÖSUNG FÜR PATH-KONFLIKT IMPLEMENTIERT
6
  """
7
  import os
8
  import subprocess
@@ -14,7 +14,6 @@ import socket
14
  import urllib.request
15
  from urllib.parse import urlparse
16
  import sys
17
- import shutil
18
 
19
  import gradio as gr
20
 
@@ -29,42 +28,17 @@ except ImportError:
29
  dns_resolver = None
30
 
31
  # ---------------------------------------------------------------------------
32
- # NEUE HELFER-FUNKTION: Finde den korrekten Pfad zur ausführbaren Datei
33
  # ---------------------------------------------------------------------------
34
- def get_executable_path(name):
35
- """Findet den vollen Pfad zu einer ausführbaren Datei, die mit pip installiert wurde."""
36
- # shutil.which ist der robusteste Weg, um den Pfad zu einer ausführbaren Datei zu finden
37
- path = shutil.which(name)
38
- if path:
39
- # Führen wir eine Versionsprüfung durch, um sicherzustellen, dass es nicht die alte Systemversion ist
40
- try:
41
- version_output = subprocess.check_output([path, '--version'], text=True)
42
- # Neuere Versionen haben oft ein Datum im Format YYYY.MM.DD
43
- if '.' in version_output and len(version_output.split('.')[0]) == 4:
44
- print(f"Found modern executable for '{name}' at: {path}")
45
- return path
46
- except Exception:
47
- pass # Konnte die Version nicht prüfen, fahre mit der Suche fort
48
-
49
- # Fallback für Umgebungen, in denen `which` nicht das Richtige findet
50
- # Versuche im Verzeichnis des Python-Interpreters
51
- py_executable_dir = Path(sys.executable).parent
52
- candidate = py_executable_dir / name
53
- if candidate.exists():
54
- print(f"Found executable '{name}' next to python interpreter: {candidate}")
55
- return str(candidate)
56
-
57
- print(f"Warning: Could not find a reliable path for '{name}'. Falling back to system PATH.")
58
- return name # Fallback auf den einfachen Namen, wenn alles andere fehlschlägt
59
-
60
- # Global die Pfade einmal beim Start ermitteln
61
- YT_DLP_PATH = get_executable_path("yt-dlp")
62
- FFMPEG_PATH = get_executable_path("ffmpeg")
63
 
64
  # ---------------------------------------------------------------------------
65
  # Helper: Shell
66
  # ---------------------------------------------------------------------------
67
-
68
  def run_capture(cmd):
69
  """Run a command and return stdout; raise RuntimeError with readable stderr on failure."""
70
  result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
@@ -73,127 +47,96 @@ def run_capture(cmd):
73
  tail = stderr_text[-2000:]
74
  raise RuntimeError("Command failed: " + " ".join(map(str, cmd)) + " " + tail)
75
  return result.stdout
76
-
77
  # ... (resolve_hostname_with_dns_python bleibt gleich)
78
  def resolve_hostname_with_dns_python(hostname):
79
- if not dns_resolver:
80
- print("Warning: dnspython not found. Falling back to system DNS.")
81
- return socket.gethostbyname(hostname)
82
  try:
83
- resolver = dns_resolver.Resolver()
84
- resolver.nameservers = ['8.8.8.8', '1.1.1.1']
85
  answers = resolver.resolve(hostname, 'A')
86
  return answers[0].to_text() if answers else None
87
- except Exception as e:
88
- print(f"DNS resolution with dnspython failed for {hostname}: {e}")
89
- try:
90
- return socket.gethostbyname(hostname)
91
- except Exception as se:
92
- raise se
93
- return None
94
 
95
  # ---------------------------------------------------------------------------
96
- # MODIFIZIERTE FUNKTION: Download & Audio (nutzt jetzt expliziten Pfad)
97
  # ---------------------------------------------------------------------------
98
-
99
  def download_video_with_ytdlp(url, out_dir, cookies_path=None, format_selector=None):
100
  out_template = str(Path(out_dir) / "%(title)s.%(ext)s")
101
- # KORREKTUR: Verwende den expliziten, vollen Pfad zu yt-dlp
102
- cmd = [YT_DLP_PATH, "-o", out_template]
103
 
104
  try:
105
- parsed_url = urlparse(url)
106
- hostname = parsed_url.hostname
107
  if hostname:
108
  ip_address = resolve_hostname_with_dns_python(hostname)
109
  if ip_address:
110
  resolve_arg = f"{hostname}:443:{ip_address}"
111
  cmd.extend(["--resolve", resolve_arg])
112
  except Exception as e:
113
- print(f"Could not perform custom DNS resolution, proceeding without it. Error: {e}")
114
 
115
- if format_selector:
116
- cmd += ["-f", format_selector]
117
- if cookies_path:
118
- cmd += ["--cookies", cookies_path]
119
  cmd.append(url)
120
 
121
  print(f"Running command: {' '.join(cmd)}")
122
  run_capture(cmd)
123
 
124
  files = sorted(Path(out_dir).glob("*"), key=lambda p: p.stat().st_mtime, reverse=True)
125
- if not files:
126
- raise FileNotFoundError("Download fehlgeschlagen — keine Datei gefunden.")
127
  return str(files[0])
128
 
129
-
130
  def extract_audio_ffmpeg(video_path, out_wav):
131
- # KORREKTUR: Verwende den expliziten, vollen Pfad zu ffmpeg
132
  cmd = [FFMPEG_PATH, "-y", "-i", video_path, "-vn", "-ac", "1", "-ar", "16000", "-f", "wav", out_wav]
133
  subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
134
  return out_wav
135
 
136
  # ... (Zeit- und Format-Helfer bleiben identisch)
137
- def seconds_to_timestamp(s): hours = int(s // 3600); minutes = int((s % 3600) // 60); seconds = int(s % 60); ms = int(round((s - int(s)) * 1000)); return f"{hours:02d}:{minutes:02d}:{seconds:02d},{ms:03d}"
138
- def format_timestamp_vtt(s): hours = int(s // 3600); minutes = int((s % 3600) // 60); seconds = int(s % 60); ms = int(round((s - int(s)) * 1000)); return f"{hours:02d}:{minutes:02d}:{seconds:02d}.{ms:03d}"
139
- def segments_to_srt(segments): parts = []; [parts.append(f"{i}\n{seconds_to_timestamp(s['start'])} --> {seconds_to_timestamp(s['end'])}\n{s['text'].strip()}") for i, s in enumerate(segments, 1)]; return "\n\n".join(parts) + "\n\n"
140
- def segments_to_vtt(segments): parts = ["WEBVTT\n"]; [parts.append(f"{format_timestamp_vtt(s['start'])} --> {format_timestamp_vtt(s['end'])}\n{s['text'].strip()}") for s in segments]; return "\n\n".join(parts)
141
  def segments_to_txt(segments): return "\n".join([f"[{seconds_to_timestamp(s['start'])}] {s['text'].strip()}" for s in segments])
142
- def segments_to_json(segments, language=None, metadata=None): data = {"language": language, "segments": segments}; [data.update({"metadata": metadata}) if metadata else None]; return json.dumps(data, ensure_ascii=False, indent=2)
143
 
144
  # ---------------------------------------------------------------------------
145
- # Kern-Pipeline: Transkription (bleibt logisch identisch)
146
  # ---------------------------------------------------------------------------
147
- def transcribe_pipeline(file_obj, url, model_size, keep_video=False, cookies_file=None, format_selector=None):
148
- if whisper is None: return "Fehler: whisper ist nicht installiert.", None, None, None, None, None
149
  tmpdir = tempfile.mkdtemp(prefix="whisper_space_");
150
  try:
151
- video_path = download_video_with_ytdlp(url, tmpdir, cookies_path=cookies_file, format_selector=format_selector) if url else file_obj.name
152
- if not video_path: return "Kein Video angegeben.", None, None, None, None, None
153
- audio_wav = str(Path(tmpdir) / "audio.wav"); extract_audio_ffmpeg(video_path, audio_wav)
154
- model = whisper.load_model(model_size); result = model.transcribe(audio_wav, verbose=False)
155
- segments = result.get("segments", []); language = result.get("language", "unknown")
156
- txt_text = segments_to_txt(segments); srt_text = segments_to_srt(segments); vtt_text = segments_to_vtt(segments); json_text = segments_to_json(segments, language, {"model": model_size})
157
- base = Path(video_path).stem; files = {}
158
- for ext, content in {"srt": srt_text, "vtt": vtt_text, "txt": txt_text, "json": json_text}.items(): p = Path(tmpdir) / f"{base}.{ext}"; p.write_text(content, encoding="utf-8"); files[ext] = str(p)
159
- if not keep_video and url: [os.remove(video_path) for _ in [1] if os.path.exists(video_path)]
160
- meta = f"Model: {model_size}, Sprache: {language}"; return txt_text, files["srt"], files["vtt"], files["txt"], files["json"], meta
161
- except Exception as e: return f"Fehler: {e}", None, None, None, None, None
162
 
163
  # ---------------------------------------------------------------------------
164
- # MODIFIZIERTE DIAGNOSE: Zeigt uns den Beweis!
165
  # ---------------------------------------------------------------------------
166
  def dns_internet_diag():
167
  lines = []
168
- lines.append("=== Python & PATH Info ===")
169
  lines.append(f"Python Executable: {sys.executable}")
170
- lines.append(f"System PATH: {os.environ.get('PATH', 'Nicht gefunden')}")
171
-
172
- lines.append("\n\n=== Executable Location & Version (Proof) ===")
173
- for name in ["yt-dlp", "ffmpeg"]:
174
- try:
175
- # Finde den Pfad, den unser Skript verwenden WÜRDE
176
- path_in_use = get_executable_path(name)
177
- lines.append(f"Path für '{name}' via get_executable_path(): {path_in_use}")
178
- # Finde den Pfad, den das SYSTEM verwenden würde
179
- system_path = shutil.which(name)
180
- lines.append(f"Path für '{name}' via shutil.which() (System PATH): {system_path}")
181
- # Prüfe die Version der Datei, die wir tatsächlich verwenden
182
- out = run_capture([path_in_use, "-version" if name == "ffmpeg" else "--version"])
183
- version_line = out.splitlines()[0] if out else "(keine Ausgabe)"
184
- lines.append(f"VERSION VON '{path_in_use}': {version_line.strip()}")
185
- except Exception as e:
186
- lines.append(f"Fehler bei der Diagnose von '{name}': {e}")
187
- # ... Rest der Diagnose bleibt gleich
188
- lines.append("\n\n=== DNS-Auflösung (System) ===")
189
- for host in ["huggingface.co", "www.google.com", "www.instagram.com", "youtube.com"]:
190
- try: ip = socket.gethostbyname(host); lines.append(f"{host} -> {ip} (OK)")
191
  except Exception as e: lines.append(f"{host} -> ERROR: {e}")
192
- if dns_resolver:
193
- lines.append("\n\n=== DNS-Auflösung (via dnspython mit 8.8.8.8) ===")
194
- for host in ["huggingface.co", "www.google.com", "www.instagram.com", "youtube.com"]:
195
- try: ip = resolve_hostname_with_dns_python(host); lines.append(f"{host} -> {ip} (OK)")
196
- except Exception as e: lines.append(f"{host} -> ERROR: {e}")
197
  return "\n".join(lines)
198
 
199
  # ---------------------------------------------------------------------------
@@ -205,10 +148,10 @@ with gr.Blocks() as demo:
205
  with gr.Row():
206
  with gr.Column(): url_in = gr.Textbox(label="Video URL", placeholder="https://..."); file_in = gr.File(label="Oder Videodatei hochladen"); cookies_in = gr.File(label="Cookies.txt (optional, für yt-dlp)"); fmt_in = gr.Textbox(label="Format (optional, yt-dlp -f)", placeholder="z.B. bestvideo+bestaudio/best"); model_sel = gr.Radio(["tiny", "base", "small", "medium", "large"], value="small", label="Whisper-Modell"); keep_chk = gr.Checkbox(label="Video behalten (bei URL-Download)", value=False); btn = gr.Button("Transkribieren"); status = gr.Textbox(label="Status / Meta", interactive=False)
207
  with gr.Column(): transcript = gr.Textbox(label="Transkript", lines=20); srt_dl = gr.File(label="SRT"); vtt_dl = gr.File(label="VTT"); txt_dl = gr.File(label="TXT"); json_dl = gr.File(label="JSON")
208
- def run_transcribe(f, u, m, k, c, fmt): cookies_path = c.name if c else None; display, srtf, vttf, txtf, jsonf, meta = transcribe_pipeline(f, u, m, k, cookies_file=cookies_path, format_selector=(fmt or None)); return (display, gr.update(value=srtf, visible=bool(srtf)), gr.update(value=vttf, visible=bool(vttf)), gr.update(value=txtf, visible=bool(txtf)), gr.update(value=jsonf, visible=bool(jsonf)), meta,)
209
  btn.click(run_transcribe, [file_in, url_in, model_sel, keep_chk, cookies_in, fmt_in], [transcript, srt_dl, vtt_dl, txt_dl, json_dl, status])
210
  with gr.Tab("Netzwerk / DNS Diagnose"):
211
- gr.Markdown("""Führt Tests durch, um Pfad-Konflikte und DNS-Probleme zu identifizieren."""); diag_btn = gr.Button("Diagnose starten"); diag_out = gr.Textbox(label="Diagnose-Ausgabe", lines=25)
212
  diag_btn.click(dns_internet_diag, inputs=[], outputs=[diag_out])
213
 
214
  if __name__ == "__main__":
 
1
+ #!/usr.bin/env python3
2
  # coding: utf-8
3
 
4
  """ Hugging Face Space (Gradio) App: Video -> Audio -> Whisper Transkript (+ Downloads SRT/TXT/VTT/JSON)
5
+ FINALE LÖSUNG: Führt yt-dlp als Python-Modul aus, um PATH-Konflikte und Systemschutz zu umgehen.
6
  """
7
  import os
8
  import subprocess
 
14
  import urllib.request
15
  from urllib.parse import urlparse
16
  import sys
 
17
 
18
  import gradio as gr
19
 
 
28
  dns_resolver = None
29
 
30
  # ---------------------------------------------------------------------------
31
+ # DEFINITIVE AUFRUFMETHODE: yt-dlp als Modul
32
  # ---------------------------------------------------------------------------
33
+ # sys.executable ist der volle Pfad zum aktuell laufenden Python-Interpreter.
34
+ # python -m yt_dlp nutzt Pythons Mechanismus, um das von pip installierte Paket zu finden.
35
+ # Dies ist die robusteste Methode und umgeht alle PATH-Probleme.
36
+ YT_DLP_COMMAND = [sys.executable, "-m", "yt_dlp"]
37
+ FFMPEG_PATH = "ffmpeg" # ffmpeg ist meist unproblematisch im System-PATH
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  # ---------------------------------------------------------------------------
40
  # Helper: Shell
41
  # ---------------------------------------------------------------------------
 
42
  def run_capture(cmd):
43
  """Run a command and return stdout; raise RuntimeError with readable stderr on failure."""
44
  result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
 
47
  tail = stderr_text[-2000:]
48
  raise RuntimeError("Command failed: " + " ".join(map(str, cmd)) + " " + tail)
49
  return result.stdout
50
+
51
  # ... (resolve_hostname_with_dns_python bleibt gleich)
52
  def resolve_hostname_with_dns_python(hostname):
53
+ if not dns_resolver: return socket.gethostbyname(hostname)
 
 
54
  try:
55
+ resolver = dns_resolver.Resolver(); resolver.nameservers = ['8.8.8.8', '1.1.1.1']
 
56
  answers = resolver.resolve(hostname, 'A')
57
  return answers[0].to_text() if answers else None
58
+ except Exception: return socket.gethostbyname(hostname)
 
 
 
 
 
 
59
 
60
  # ---------------------------------------------------------------------------
61
+ # MODIFIZIERTE FUNKTION: Download & Audio
62
  # ---------------------------------------------------------------------------
 
63
  def download_video_with_ytdlp(url, out_dir, cookies_path=None, format_selector=None):
64
  out_template = str(Path(out_dir) / "%(title)s.%(ext)s")
65
+ # KORREKTUR: Verwende den Modul-Aufruf
66
+ cmd = YT_DLP_COMMAND + ["-o", out_template]
67
 
68
  try:
69
+ hostname = urlparse(url).hostname
 
70
  if hostname:
71
  ip_address = resolve_hostname_with_dns_python(hostname)
72
  if ip_address:
73
  resolve_arg = f"{hostname}:443:{ip_address}"
74
  cmd.extend(["--resolve", resolve_arg])
75
  except Exception as e:
76
+ print(f"Custom DNS resolution failed, proceeding without it. Error: {e}")
77
 
78
+ if format_selector: cmd += ["-f", format_selector]
79
+ if cookies_path: cmd += ["--cookies", cookies_path]
 
 
80
  cmd.append(url)
81
 
82
  print(f"Running command: {' '.join(cmd)}")
83
  run_capture(cmd)
84
 
85
  files = sorted(Path(out_dir).glob("*"), key=lambda p: p.stat().st_mtime, reverse=True)
86
+ if not files: raise FileNotFoundError("Download fehlgeschlagen — keine Datei gefunden.")
 
87
  return str(files[0])
88
 
 
89
  def extract_audio_ffmpeg(video_path, out_wav):
 
90
  cmd = [FFMPEG_PATH, "-y", "-i", video_path, "-vn", "-ac", "1", "-ar", "16000", "-f", "wav", out_wav]
91
  subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
92
  return out_wav
93
 
94
  # ... (Zeit- und Format-Helfer bleiben identisch)
95
+ def seconds_to_timestamp(s): h, m, s, ms = int(s//3600), int((s%3600)//60), int(s%60), int(round((s-int(s))*1000)); return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
96
+ def format_timestamp_vtt(s): h, m, s, ms = int(s//3600), int((s%3600)//60), int(s%60), int(round((s-int(s))*1000)); return f"{h:02d}:{m:02d}:{s:02d}.{ms:03d}"
97
+ def segments_to_srt(segments): parts = [f"{i}\n{seconds_to_timestamp(s['start'])} --> {seconds_to_timestamp(s['end'])}\n{s['text'].strip()}" for i,s in enumerate(segments,1)]; return "\n\n".join(parts) + "\n\n"
98
+ def segments_to_vtt(segments): parts = ["WEBVTT\n"] + [f"{format_timestamp_vtt(s['start'])} --> {format_timestamp_vtt(s['end'])}\n{s['text'].strip()}" for s in segments]; return "\n\n".join(parts)
99
  def segments_to_txt(segments): return "\n".join([f"[{seconds_to_timestamp(s['start'])}] {s['text'].strip()}" for s in segments])
100
+ def segments_to_json(segments, lang=None, meta=None): data={"language":lang, "segments":segments}; [data.update({"metadata":meta}) if meta else None]; return json.dumps(data,ensure_ascii=False,indent=2)
101
 
102
  # ---------------------------------------------------------------------------
103
+ # Kern-Pipeline (bleibt identisch)
104
  # ---------------------------------------------------------------------------
105
+ def transcribe_pipeline(f, u, m, k, c, fmt):
106
+ if whisper is None: return "Fehler: whisper ist nicht installiert.",*[None]*5
107
  tmpdir = tempfile.mkdtemp(prefix="whisper_space_");
108
  try:
109
+ video_path = download_video_with_ytdlp(u, tmpdir, c, fmt) if u else f.name
110
+ if not video_path: return "Kein Video angegeben.",*[None]*5
111
+ audio_wav=str(Path(tmpdir)/"audio.wav"); extract_audio_ffmpeg(video_path,audio_wav)
112
+ model=whisper.load_model(m); result=model.transcribe(audio_wav,verbose=False)
113
+ segs=result.get("segments",[]); lang=result.get("language","unknown")
114
+ txt=segments_to_txt(segs); srt=segments_to_srt(segs); vtt=segments_to_vtt(segs); jsn=segments_to_json(segs,lang,{"model":m})
115
+ base=Path(video_path).stem; files={}
116
+ for ext, content in {"srt":srt, "vtt":vtt, "txt":txt, "json":jsn}.items(): p=Path(tmpdir)/f"{base}.{ext}"; p.write_text(content,encoding="utf-8"); files[ext]=str(p)
117
+ if not k and u: [os.remove(video_path) for _ in [1] if os.path.exists(video_path)]
118
+ meta=f"Model: {m}, Sprache: {lang}"; return txt,files["srt"],files["vtt"],files["txt"],files["json"],meta
119
+ except Exception as e: return f"Fehler: {e}",*[None]*5
120
 
121
  # ---------------------------------------------------------------------------
122
+ # MODIFIZIERTE DIAGNOSE
123
  # ---------------------------------------------------------------------------
124
  def dns_internet_diag():
125
  lines = []
126
+ lines.append("=== Python & Version Info ===")
127
  lines.append(f"Python Executable: {sys.executable}")
128
+ try:
129
+ # Führe yt-dlp als Modul aus, um die ECHTE, von pip installierte Version zu prüfen
130
+ cmd = YT_DLP_COMMAND + ["--version"]
131
+ version_out = run_capture(cmd).strip()
132
+ lines.append(f"Version via '{' '.join(cmd)}': {version_out} (SOLLTE NEU SEIN)")
133
+ except Exception as e:
134
+ lines.append(f"Fehler bei der Prüfung der yt-dlp Modul-Version: {e}")
135
+
136
+ lines.append("\n\n=== DNS-Auflösung (via dnspython mit 8.8.8.8) ===")
137
+ for host in ["huggingface.co", "www.instagram.com", "youtube.com"]:
138
+ try: ip = resolve_hostname_with_dns_python(host); lines.append(f"{host} -> {ip} (OK)")
 
 
 
 
 
 
 
 
 
 
139
  except Exception as e: lines.append(f"{host} -> ERROR: {e}")
 
 
 
 
 
140
  return "\n".join(lines)
141
 
142
  # ---------------------------------------------------------------------------
 
148
  with gr.Row():
149
  with gr.Column(): url_in = gr.Textbox(label="Video URL", placeholder="https://..."); file_in = gr.File(label="Oder Videodatei hochladen"); cookies_in = gr.File(label="Cookies.txt (optional, für yt-dlp)"); fmt_in = gr.Textbox(label="Format (optional, yt-dlp -f)", placeholder="z.B. bestvideo+bestaudio/best"); model_sel = gr.Radio(["tiny", "base", "small", "medium", "large"], value="small", label="Whisper-Modell"); keep_chk = gr.Checkbox(label="Video behalten (bei URL-Download)", value=False); btn = gr.Button("Transkribieren"); status = gr.Textbox(label="Status / Meta", interactive=False)
150
  with gr.Column(): transcript = gr.Textbox(label="Transkript", lines=20); srt_dl = gr.File(label="SRT"); vtt_dl = gr.File(label="VTT"); txt_dl = gr.File(label="TXT"); json_dl = gr.File(label="JSON")
151
+ def run_transcribe(f, u, m, k, c, fmt): cookies_path = c.name if c else None; d, s, v, t, j, meta = transcribe_pipeline(f, u, m, k, cookies_path, (fmt or None)); return (d, gr.update(value=s,visible=bool(s)), gr.update(value=v,visible=bool(v)), gr.update(value=t,visible=bool(t)), gr.update(value=j,visible=bool(j)), meta,)
152
  btn.click(run_transcribe, [file_in, url_in, model_sel, keep_chk, cookies_in, fmt_in], [transcript, srt_dl, vtt_dl, txt_dl, json_dl, status])
153
  with gr.Tab("Netzwerk / DNS Diagnose"):
154
+ gr.Markdown("""Prüft die Version von yt-dlp, wie sie von Python als Modul ausgeführt wird."""); diag_btn = gr.Button("Diagnose starten"); diag_out = gr.Textbox(label="Diagnose-Ausgabe", lines=25)
155
  diag_btn.click(dns_internet_diag, inputs=[], outputs=[diag_out])
156
 
157
  if __name__ == "__main__":