avans06 commited on
Commit
fc8a81e
Β·
1 Parent(s): aca5bd7

initial add app.py

Browse files
Files changed (5) hide show
  1. .gitignore +12 -0
  2. README.md +1 -1
  3. app.py +305 -0
  4. requirements.txt +3 -0
  5. webui.bat +162 -0
.gitignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .vs
2
+ .vscode
3
+ # Byte-compiled / optimized / DLL files
4
+ __pycache__/
5
+
6
+ venv/
7
+ tmp/
8
+ sf2/
9
+ models/
10
+ output/
11
+ rendered_midi/
12
+ transcribed_/
README.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
  title: Audio To CUE Generator
3
- emoji: πŸ‘
4
  colorFrom: green
5
  colorTo: blue
6
  sdk: gradio
 
1
  ---
2
  title: Audio To CUE Generator
3
+ emoji: πŸ“
4
  colorFrom: green
5
  colorTo: blue
6
  sdk: gradio
app.py ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import librosa
4
+ import gradio as gr
5
+
6
+ # --- Helper Functions ---
7
+
8
+ def seconds_to_cue_time(t):
9
+ """Converts a time in seconds to the CUE sheet format (MM:SS:FF)."""
10
+ t = max(0, t)
11
+ minutes = int(t // 60)
12
+ seconds = int(t % 60)
13
+ frames = int((t - minutes * 60 - seconds) * 75)
14
+ return f'{minutes:02d}:{seconds:02d}:{frames:02d}'
15
+
16
+ def parse_cue_time_to_seconds(time_str):
17
+ """Parses MM:SS:FF into seconds. Returns None on failure."""
18
+ if not time_str:
19
+ return None
20
+ match = re.match(r'(\d+):(\d{1,2}):(\d{1,2})', time_str)
21
+ if match:
22
+ m, s, f = map(int, match.groups())
23
+ return m * 60 + s + f / 75.0
24
+ return None
25
+
26
+ def format_cue_text(times, audio_filename="CDImage.wav"):
27
+ """Generates the final CUE sheet string from a list of times."""
28
+ if not times:
29
+ return ""
30
+ filename_no_ext = os.path.splitext(audio_filename)[0]
31
+ cue_text = f'PERFORMER "Unknown Artist"\n'
32
+ cue_text += f'TITLE "{filename_no_ext}"\n'
33
+ cue_text += f'FILE "{audio_filename}" WAVE\n'
34
+
35
+ # Always sort times before formatting to handle out-of-order additions from splitting
36
+ sorted_times = sorted(list(set(times)))
37
+ for idx, t in enumerate(sorted_times):
38
+ cue_time_str = seconds_to_cue_time(t)
39
+ cue_text += f' TRACK {idx+1:02d} AUDIO\n'
40
+ cue_text += f' TITLE "Track {idx+1:02d}"\n'
41
+ cue_text += f' INDEX 01 {cue_time_str}\n'
42
+ return cue_text
43
+
44
+ def generate_track_labels(times, audio_duration):
45
+ """Creates descriptive labels for the checklist, including track length."""
46
+ if not times:
47
+ return []
48
+ sorted_times = sorted(list(set(times)))
49
+ track_choices = []
50
+ for i, t in enumerate(sorted_times):
51
+ track_length = (sorted_times[i+1] - t) if i < len(sorted_times) - 1 else (audio_duration - t)
52
+ label = f"Track {i+1:02d} (Starts: {seconds_to_cue_time(t)}) [Length: {seconds_to_cue_time(track_length)}]"
53
+ track_choices.append(label)
54
+ return track_choices
55
+
56
+ # --- Core Gradio Functions ---
57
+ def analyze_audio_to_cue(audio_file, top_db, min_segment_len, merge_threshold, merge_protection_len):
58
+ """Workflow 1: Analyzes an uploaded audio file to generate the initial CUE text."""
59
+ if not audio_file:
60
+ raise gr.Error("Please upload an audio file first.")
61
+
62
+ # --- 1. Load Audio File ---
63
+ try:
64
+ y, sr = librosa.load(audio_file, sr=None)
65
+ audio_duration = librosa.get_duration(y=y, sr=sr)
66
+ except Exception as e:
67
+ raise gr.Error(f"Could not load audio file: {e}")
68
+
69
+ # --- 2. Detect Segments using Silence Detection ---
70
+ intervals = librosa.effects.split(y, top_db=top_db)
71
+
72
+ # Corrected way to check if NumPy array is empty
73
+ times = [iv[0] / sr for iv in intervals if (iv[1] - iv[0]) / sr >= min_segment_len] if intervals.size > 0 else []
74
+
75
+ # --- 3. Post-process Tracks (Add Start, Auto-Merge) ---
76
+ if not times or times[0] > 0.5:
77
+ times.insert(0, 0.0)
78
+
79
+ # Auto-merging logic
80
+ if len(times) > 1:
81
+ final_times = [times[0]]
82
+ i = 0
83
+ while i < len(times) - 1:
84
+ track_length = times[i+1] - times[i]
85
+
86
+ # Merge if track is shorter than threshold AND not longer than protection length
87
+ if (track_length < merge_threshold) and (track_length <= merge_protection_len):
88
+ # Condition to MERGE is met. Skip adding the next timestamp.
89
+ pass
90
+ else:
91
+ # Condition to KEEP is met.
92
+ final_times.append(times[i+1])
93
+
94
+ i += 1
95
+
96
+ if len(final_times) > 1 and (audio_duration - final_times[-1]) < merge_threshold:
97
+ final_times.pop()
98
+ times = final_times
99
+
100
+ # --- 4. Prepare Outputs for Gradio ---
101
+ times = sorted(list(set(times)))
102
+ audio_filename = os.path.basename(audio_file)
103
+ initial_cue_text = format_cue_text(times, audio_filename)
104
+ track_labels = generate_track_labels(times, audio_duration)
105
+
106
+ # This function now returns everything needed to update the entire UI in one step.
107
+ return (
108
+ initial_cue_text, audio_filename, times, audio_duration,
109
+ gr.update(choices=track_labels, value=[]), gr.update(visible=True)
110
+ )
111
+
112
+ def parse_cue_and_update_ui(cue_text):
113
+ """Workflow 2: Parses pasted CUE text. NOW returns the text itself to populate the output box."""
114
+ if not cue_text or "INDEX 01" not in cue_text:
115
+ return cue_text, "CDImage.wav", None, 0, gr.update(choices=[], value=[]), gr.update(visible=False)
116
+
117
+ file_match = re.search(r'FILE\s+"([^"]+)"', cue_text, re.IGNORECASE)
118
+ audio_filename = file_match.group(1) if file_match else "CDImage.wav"
119
+
120
+ index_matches = re.findall(r'INDEX\s+\d+\s+([\d:]{7,8})', cue_text)
121
+ times = [parse_cue_time_to_seconds(t) for t in index_matches if parse_cue_time_to_seconds(t) is not None]
122
+
123
+ if not times:
124
+ return cue_text, audio_filename, None, 0, gr.update(choices=[], value=[]), gr.update(visible=False)
125
+
126
+ times = sorted(list(set(times)))
127
+ # Estimate duration for UI labels. It's the last track's start time.
128
+ # This is a limitation of text-only mode, but makes the tool usable.
129
+ audio_duration = times[-1] if times else 0
130
+ track_labels = generate_track_labels(times, audio_duration)
131
+
132
+ return cue_text, audio_filename, times, audio_duration, gr.update(choices=track_labels, value=[]), gr.update(visible=True)
133
+
134
+ def update_editing_tools(selected_tracks, current_times, audio_duration):
135
+ """Dynamically shows/hides Merge or Split tools based on selection count."""
136
+ num_selected = len(selected_tracks)
137
+
138
+ if num_selected == 1:
139
+ # Configure and show Split UI
140
+ # --- 1. Get track boundaries ---
141
+ track_idx = int(selected_tracks[0].split(' ')[1]) - 1
142
+ start_time = current_times[track_idx]
143
+ end_time = audio_duration if (track_idx + 1) >= len(current_times) else current_times[track_idx + 1]
144
+
145
+ # --- 2. [CORRECTION] Add padding to prevent splitting at the exact edges ---
146
+ # A CUE sheet frame is 1/75s (~0.013s). We use a slightly larger padding.
147
+ padding = 0.02
148
+
149
+ new_min_time = start_time + padding
150
+ new_max_time = end_time
151
+
152
+ # --- 3. [CORRECTION] Check if the track is too short to be split ---
153
+ if new_min_time >= new_max_time:
154
+ # If the track is too short, splitting is not possible. Hide the tools.
155
+ return (
156
+ gr.update(visible=False), # Hide Merge button
157
+ gr.update(visible=False), # Hide Split Group
158
+ None,
159
+ None
160
+ )
161
+
162
+ # --- 4. Configure and show the Split UI with the corrected range ---
163
+ mid_point = start_time + (end_time - start_time) / 2
164
+
165
+ return (
166
+ gr.update(visible=False), # Hide Merge button
167
+ gr.update(visible=True), # Show Split Group
168
+ # Use the new padded min/max values for the slider
169
+ gr.update(minimum=new_min_time, maximum=new_max_time, value=mid_point), # Configure Slider
170
+ gr.update(value=f"Split at: {seconds_to_cue_time(mid_point)}") # Update slider label
171
+ )
172
+
173
+ elif num_selected > 1:
174
+ # Show Merge UI
175
+ return gr.update(visible=True), gr.update(visible=False), None, None
176
+ else:
177
+ # Hide everything
178
+ return gr.update(visible=False), gr.update(visible=False), None, None
179
+
180
+ def perform_manual_merge(selected_tracks, original_times, audio_duration, audio_filename):
181
+ """Merges selected tracks. The internal logic is robust and unchanged."""
182
+
183
+ # --- 1. Identify which track start times to remove ---
184
+ indices_to_merge = {int(label.split(' ')[1]) - 1 for label in selected_tracks}
185
+
186
+ # --- 2. Create the new list of times ---
187
+ # --- This logic correctly handles all merge cases. ---
188
+ new_times = []
189
+ # We iterate through the original times and decide which ones to KEEP.
190
+ for i, time in enumerate(original_times):
191
+ is_selected = i in indices_to_merge
192
+
193
+ # Condition to KEEP a track's start time:
194
+ # 1. It was NOT selected.
195
+ # OR
196
+ # 2. It WAS selected, BUT it's the start of a merge block.
197
+ # (This means it's the very first track, OR the track before it was NOT selected).
198
+ if not is_selected or (i == 0) or ((i - 1) not in indices_to_merge):
199
+ new_times.append(time)
200
+
201
+ # --- 3. Prepare all the outputs to update the UI ---
202
+ # The new CUE text for the textbox
203
+ final_cue_text = format_cue_text(new_times, audio_filename)
204
+ new_track_labels = generate_track_labels(new_times, audio_duration)
205
+
206
+ # Return a tuple that will update the textbox, the state, and the checklist
207
+ return final_cue_text, new_times, gr.update(choices=new_track_labels, value=[])
208
+
209
+
210
+ def perform_manual_split(split_time_sec, original_times, audio_duration, audio_filename):
211
+ """Splits a track at the time specified by the slider."""
212
+ if split_time_sec in original_times:
213
+ raise gr.Error("This exact timestamp already exists.")
214
+
215
+ new_times = sorted(original_times + [split_time_sec])
216
+ final_cue_text = format_cue_text(new_times, audio_filename)
217
+ new_track_labels = generate_track_labels(new_times, audio_duration)
218
+ return final_cue_text, new_times, gr.update(choices=new_track_labels, value=[])
219
+
220
+
221
+ # --- Gradio User Interface Definition ---
222
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
223
+ gr.Markdown("# 🎡 Advanced CUE Sheet Generator")
224
+
225
+ # --- Hidden State Variables ---
226
+ track_times_state = gr.State([])
227
+ audio_duration_state = gr.State(0)
228
+ audio_filename_state = gr.State("CDImage.wav")
229
+
230
+ with gr.Tabs():
231
+ with gr.TabItem("Start with Audio File"):
232
+ gr.Markdown("Upload an audio file to automatically detect track points.")
233
+ audio_input = gr.Audio(type="filepath", label="Upload Audio File")
234
+ with gr.Accordion("Analysis Parameters", open=False):
235
+ threshold_slider = gr.Slider(10, 80, 40, step=1, label="Silence Threshold (dB)")
236
+ min_length_slider = gr.Slider(0.5, 30, 2, step=0.1, label="Min. Segment Length (s)")
237
+ merge_length_slider = gr.Slider(1, 60, 15, step=1, label="Auto-Merge Threshold (s)")
238
+ min_silence_length_slider = gr.Slider(0.5, 60, 5, step=0.1, label="Merge Protection Length (s)")
239
+ generate_button = gr.Button("Analyze Audio", variant="primary")
240
+
241
+ with gr.TabItem("Start with CUE Text"):
242
+ gr.Markdown("Or paste CUE text below and click outside the box. The editing tools will appear automatically.")
243
+ cue_text_input_for_paste = gr.Textbox(label="Paste CUE Text Here", lines=8)
244
+
245
+ # The main output textbox is now outside the tabs, serving as a central display.
246
+ output_text = gr.Textbox(label="CUE Sheet Output", lines=15, show_copy_button=True, interactive=True)
247
+
248
+ with gr.Group(visible=False) as manual_editing_group:
249
+ gr.Markdown("### Manual Editing Tools")
250
+ track_checkboxes = gr.CheckboxGroup(label="Select Tracks to Edit")
251
+
252
+ with gr.Row(visible=False) as merge_tools:
253
+ merge_button = gr.Button("Merge Selected Tracks", variant="secondary", size="lg")
254
+
255
+ with gr.Group(visible=False) as split_tools:
256
+ split_slider_label = gr.Textbox(label="Current Split Time", interactive=False)
257
+ split_slider = gr.Slider(label="Drag to select split point")
258
+ split_button = gr.Button("Split Track at Selected Time", variant="secondary")
259
+
260
+ # --- Event Wiring ---
261
+
262
+ # Workflow 1: Audio analysis button now updates everything, including the editing tools.
263
+ generate_button.click(
264
+ fn=analyze_audio_to_cue,
265
+ inputs=[audio_input, threshold_slider, min_length_slider, merge_length_slider, min_silence_length_slider],
266
+ outputs=[output_text, audio_filename_state, track_times_state, audio_duration_state, track_checkboxes, manual_editing_group]
267
+ )
268
+
269
+ # Workflow 2: Pasting text in the dedicated input box populates the main output and enables tools.
270
+ # The `.change` event now updates all necessary outputs in a single, direct step.
271
+ cue_text_input_for_paste.change(
272
+ fn=parse_cue_and_update_ui,
273
+ inputs=[cue_text_input_for_paste],
274
+ outputs=[output_text, audio_filename_state, track_times_state, audio_duration_state, track_checkboxes, manual_editing_group]
275
+ )
276
+
277
+ # Dynamic UI controller for showing/hiding Merge/Split tools
278
+ track_checkboxes.change(
279
+ fn=update_editing_tools,
280
+ inputs=[track_checkboxes, track_times_state, audio_duration_state],
281
+ outputs=[merge_tools, split_tools, split_slider, split_slider_label]
282
+ )
283
+
284
+ # Live update for the split slider's time display
285
+ split_slider.input(
286
+ fn=lambda t: f"Split at: {seconds_to_cue_time(t)}",
287
+ inputs=[split_slider],
288
+ outputs=[split_slider_label]
289
+ )
290
+
291
+ # Action buttons
292
+ merge_button.click(
293
+ fn=perform_manual_merge,
294
+ inputs=[track_checkboxes, track_times_state, audio_duration_state, audio_filename_state],
295
+ outputs=[output_text, track_times_state, track_checkboxes]
296
+ )
297
+
298
+ split_button.click(
299
+ fn=perform_manual_split,
300
+ inputs=[split_slider, track_times_state, audio_duration_state, audio_filename_state],
301
+ outputs=[output_text, track_times_state, track_checkboxes]
302
+ )
303
+
304
+ if __name__ == "__main__":
305
+ demo.launch(inbrowser=True)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ librosa
3
+ soundfile
webui.bat ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+
3
+ :: The original source of the webui.bat file is stable-diffusion-webui
4
+ :: Modified and enhanced by Gemini with features for venv management and requirements handling.
5
+
6
+ :: --------- Configuration ---------
7
+ set COMMANDLINE_ARGS=
8
+ :: Define the name of the Launch application
9
+ set APPLICATION_NAME=app.py
10
+ :: Define the name of the virtual environment directory
11
+ set VENV_NAME=venv
12
+ :: Set to 1 to always attempt to update packages from requirements.txt on every launch
13
+ set ALWAYS_UPDATE_REQS=0
14
+ :: ---------------------------------
15
+
16
+
17
+ :: Set PYTHON executable if not already defined
18
+ if not defined PYTHON (set PYTHON=python)
19
+ :: Set VENV_DIR using VENV_NAME if not already defined
20
+ if not defined VENV_DIR (set "VENV_DIR=%~dp0%VENV_NAME%")
21
+
22
+ mkdir tmp 2>NUL
23
+
24
+ :: Check if Python is callable
25
+ %PYTHON% -c "" >tmp/stdout.txt 2>tmp/stderr.txt
26
+ if %ERRORLEVEL% == 0 goto :check_pip
27
+ echo Couldn't launch python
28
+ goto :show_stdout_stderr
29
+
30
+ :check_pip
31
+ :: Check if pip is available
32
+ %PYTHON% -mpip --help >tmp/stdout.txt 2>tmp/stderr.txt
33
+ if %ERRORLEVEL% == 0 goto :start_venv
34
+ :: If pip is not available and PIP_INSTALLER_LOCATION is set, try to install pip
35
+ if "%PIP_INSTALLER_LOCATION%" == "" goto :show_stdout_stderr
36
+ %PYTHON% "%PIP_INSTALLER_LOCATION%" >tmp/stdout.txt 2>tmp/stderr.txt
37
+ if %ERRORLEVEL% == 0 goto :start_venv
38
+ echo Couldn't install pip
39
+ goto :show_stdout_stderr
40
+
41
+ :start_venv
42
+ :: Skip venv creation/activation if VENV_DIR is explicitly set to "-"
43
+ if ["%VENV_DIR%"] == ["-"] goto :skip_venv_entirely
44
+ :: Skip venv creation/activation if SKIP_VENV is set to "1"
45
+ if ["%SKIP_VENV%"] == ["1"] goto :skip_venv_entirely
46
+
47
+ :: Check if the venv already exists by looking for Python.exe in its Scripts directory
48
+ dir "%VENV_DIR%\Scripts\Python.exe" >tmp/stdout.txt 2>tmp/stderr.txt
49
+ if %ERRORLEVEL% == 0 goto :activate_venv_and_maybe_update
50
+
51
+ :: Venv does not exist, create it
52
+ echo Virtual environment not found in "%VENV_DIR%". Creating a new one.
53
+ for /f "delims=" %%i in ('CALL %PYTHON% -c "import sys; print(sys.executable)"') do set PYTHON_FULLNAME="%%i"
54
+ echo Creating venv in directory %VENV_DIR% using python %PYTHON_FULLNAME%
55
+ %PYTHON_FULLNAME% -m venv "%VENV_DIR%" >tmp/stdout.txt 2>tmp/stderr.txt
56
+ if %ERRORLEVEL% NEQ 0 (
57
+ echo Unable to create venv in directory "%VENV_DIR%"
58
+ goto :show_stdout_stderr
59
+ )
60
+ echo Venv created.
61
+
62
+ :: Install requirements for the first time if venv was just created
63
+ :: This section handles the initial installation of packages from requirements.txt
64
+ :: immediately after a new virtual environment is created.
65
+ echo Checking for requirements.txt for initial setup in %~dp0
66
+ if exist "%~dp0requirements.txt" (
67
+ echo Found requirements.txt, attempting to install for initial setup...
68
+ call "%VENV_DIR%\Scripts\activate.bat"
69
+ echo Installing packages from requirements.txt ^(initial setup^)...
70
+ "%VENV_DIR%\Scripts\python.exe" -m pip install -r "%~dp0requirements.txt"
71
+ if %ERRORLEVEL% NEQ 0 (
72
+ echo Failed to install requirements during initial setup. Please check the output above.
73
+ pause
74
+ goto :show_stdout_stderr_custom_pip_initial
75
+ )
76
+ echo Initial requirements installed successfully.
77
+ call "%VENV_DIR%\Scripts\deactivate.bat"
78
+ ) else (
79
+ echo No requirements.txt found for initial setup, skipping package installation.
80
+ )
81
+ goto :activate_venv_and_maybe_update
82
+
83
+
84
+ :activate_venv_and_maybe_update
85
+ :: This label is reached if the venv exists or was just created.
86
+ :: Set PYTHON to point to the venv's Python interpreter.
87
+ set PYTHON="%VENV_DIR%\Scripts\Python.exe"
88
+ echo Activating venv: %PYTHON%
89
+
90
+ :: Always update requirements if ALWAYS_UPDATE_REQS is 1
91
+ :: This section allows for updating packages from requirements.txt on every launch
92
+ :: if the ALWAYS_UPDATE_REQS variable is set to 1.
93
+ if defined ALWAYS_UPDATE_REQS (
94
+ if "%ALWAYS_UPDATE_REQS%"=="1" (
95
+ echo ALWAYS_UPDATE_REQS is enabled.
96
+ if exist "%~dp0requirements.txt" (
97
+ echo Attempting to update packages from requirements.txt...
98
+ REM No need to call activate.bat here again, PYTHON is already set to the venv's python
99
+ %PYTHON% -m pip install -r "%~dp0requirements.txt"
100
+ if %ERRORLEVEL% NEQ 0 (
101
+ echo Failed to update requirements. Please check the output above.
102
+ pause
103
+ goto :endofscript
104
+ )
105
+ echo Requirements updated successfully.
106
+ ) else (
107
+ echo ALWAYS_UPDATE_REQS is enabled, but no requirements.txt found. Skipping update.
108
+ )
109
+ ) else (
110
+ echo ALWAYS_UPDATE_REQS is not enabled or not set to 1. Skipping routine update.
111
+ )
112
+ )
113
+
114
+ goto :launch
115
+
116
+ :skip_venv_entirely
117
+ :: This label is reached if venv usage is explicitly skipped.
118
+ echo Skipping venv.
119
+ goto :launch
120
+
121
+ :launch
122
+ :: Launch the main application
123
+ echo Launching Web UI with arguments: %COMMANDLINE_ARGS% %*
124
+ %PYTHON% %APPLICATION_NAME% %COMMANDLINE_ARGS% %*
125
+ echo Launch finished.
126
+ pause
127
+ exit /b
128
+
129
+ :show_stdout_stderr_custom_pip_initial
130
+ :: Custom error handler for failures during the initial pip install process.
131
+ echo.
132
+ echo exit code ^(pip initial install^): %errorlevel%
133
+ echo Errors during initial pip install. See output above.
134
+ echo.
135
+ echo Launch unsuccessful. Exiting.
136
+ pause
137
+ exit /b
138
+
139
+
140
+ :show_stdout_stderr
141
+ :: General error handler: displays stdout and stderr from the tmp directory.
142
+ echo.
143
+ echo exit code: %errorlevel%
144
+
145
+ for /f %%i in ("tmp\stdout.txt") do set size=%%~zi
146
+ if %size% equ 0 goto :show_stderr
147
+ echo.
148
+ echo stdout:
149
+ type tmp\stdout.txt
150
+
151
+ :show_stderr
152
+ for /f %%i in ("tmp\stderr.txt") do set size=%%~zi
153
+ if %size% equ 0 goto :endofscript
154
+ echo.
155
+ echo stderr:
156
+ type tmp\stderr.txt
157
+
158
+ :endofscript
159
+ echo.
160
+ echo Launch unsuccessful. Exiting.
161
+ pause
162
+ exit /b