cutted 0.3.2__py3-none-any.whl → 0.3.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cutted/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "0.3.2"
1
+ __version__ = "0.3.3"
2
2
  __author__ = "simon0302010"
cutted/app.py CHANGED
@@ -25,6 +25,8 @@ class CuttedApp:
25
25
  self.canvas = None
26
26
  self.cursor_line = None
27
27
  self.last_slider_update = 0
28
+ self.slider_value = 0
29
+ self.playback_start_time = 0
28
30
  self.is_playing = False
29
31
  self.last_states = []
30
32
  self.setup_ui()
@@ -113,7 +115,7 @@ class CuttedApp:
113
115
  self.canvas = FigureCanvasTkAgg(fig, master=self.plot_frame)
114
116
  self.canvas.draw()
115
117
 
116
- self.audio_lenght = int(round(self.AudioProcessor.get_lenght()))
118
+ self.audio_lenght = int(round(self.AudioProcessor.get_length()))
117
119
 
118
120
  slider_width = self.root.winfo_width() - 40
119
121
  self.slider = customtkinter.CTkSlider(
@@ -153,11 +155,16 @@ class CuttedApp:
153
155
  return
154
156
 
155
157
  start_time = self.slider.get() if hasattr(self, 'slider') else 0
158
+ self.playback_start_time = start_time
156
159
  self.AudioProcessor.play_audio(start_time)
157
160
 
158
161
  def stop_audio(self):
159
- self.AudioProcessor.stop_audio()
162
+ rel_pos = self.AudioProcessor.stop_audio()
160
163
  self.is_playing = False
164
+ abs_pos = self.playback_start_time + rel_pos
165
+ self.slider.set(abs_pos)
166
+ self.set_cursor(abs_pos)
167
+ print_info(f"Absolute position in audio: {abs_pos:.2f}s")
161
168
 
162
169
  def export_audio(self):
163
170
  if not hasattr(self.AudioProcessor, "audio") or self.AudioProcessor.audio is None:
@@ -184,6 +191,7 @@ class CuttedApp:
184
191
  print_success(f"Audio exported to {save_path}")
185
192
 
186
193
  def send_prompt(self):
194
+ print(self.AudioProcessor.get_waveform_summary())
187
195
  self.save_state()
188
196
 
189
197
  if not hasattr(self.AudioProcessor, "audio") or self.AudioProcessor.audio is None:
@@ -192,14 +200,18 @@ class CuttedApp:
192
200
 
193
201
  text = self.entry.get()
194
202
  if text.strip():
195
- full_prompt = f"You are a audio editing AI. You are controllable via natural language and editing a audio file. The audio file is {round(self.AudioProcessor.get_lenght())}s long."
203
+ full_prompt = f"You are a audio editing AI. You are controllable via natural language and editing a audio file. The audio file is {round(self.AudioProcessor.get_length())}s long. The cursor of the user is currently at {self.slider_value}s."
204
+ full_prompt += "\nHere is a the waveform samples of the audio. You can use them to determine silent parts, loud parts, silences, beats and much more.\nYou are forced to used these if the user requires you to cut out silent of quiet parts for example."
205
+ full_prompt += "\nAll of your tools should be enough to fullfill almost every task.\nNEVER ASK FOR CONFIRMATION FROM THE USER. DO EVERYTHING!"
206
+ full_prompt += f"\n{self.AudioProcessor.get_waveform_summary()}\n"
196
207
  if whisper_support:
197
208
  if self.use_transcript_checkbox.get():
198
209
  if not self.whisper:
199
- messagebox.showinfo("Info", "Loading Whisper model. This may take a few minutes depending on your internet connection. See the progress in your command line. If this window appears to be frozen, the transcription is running.")
210
+ messagebox.showinfo("Info", "Loading Whisper model. This may take a few minutes depending on your internet connection. See the progress in your command line. If this window appears to be frozen, the transcription is running. Press OK to continue.")
200
211
  self.whisper = transcribe.Whisper()
201
212
  transcript = self.whisper.transcribe(self.AudioProcessor.audio_path)
202
213
  full_prompt += f"\nThis is a transcript with per word timestamps of the audio:\n{transcript}"
214
+ full_prompt += "\nThe transcript likely has issues. If you need infos about some words they might just be misspelled in the audio."
203
215
  full_prompt += f"\n\nUser Prompt: {text}"
204
216
  self.entry.delete(0, "end")
205
217
 
@@ -220,6 +232,7 @@ class CuttedApp:
220
232
  elif text_result:
221
233
  messagebox.showerror("Error", text_result.strip())
222
234
  else:
235
+ messagebox.showerror("Error", "Gemini returned no data")
223
236
  print_fail("Gemini returned no data")
224
237
 
225
238
  def save_state(self):
@@ -62,7 +62,21 @@ class AudioProcessor:
62
62
 
63
63
  return fig
64
64
 
65
- def get_lenght(self):
65
+ def get_waveform_summary(self):
66
+ num_samples = round(self.get_length())
67
+ if self.audio is None:
68
+ return "No audio loaded."
69
+ samples = np.array(self.audio.get_array_of_samples())
70
+ if self.audio.channels == 2:
71
+ samples = samples.reshape((-1, 2))
72
+ samples = samples.mean(axis=1)
73
+ samples = samples / np.max(np.abs(samples))
74
+ indices = np.linspace(0, len(samples)-1, num_samples).astype(int)
75
+ summary = samples[indices]
76
+ return f"Waveform samples (normalized, {num_samples} points):\n" + \
77
+ " ".join(f"{x:.2f}" for x in summary)
78
+
79
+ def get_length(self):
66
80
  self.duration = self.audio.duration_seconds
67
81
  self.duration = round(self.duration, 2)
68
82
  return self.duration
@@ -70,17 +84,25 @@ class AudioProcessor:
70
84
  def cut(self, start, end):
71
85
  if len(start) == len(end):
72
86
  if len(start) == 1:
73
- print_info(f"Cutting from {start[0]} to {end[0]}")
74
- start_ms = round(start[0] * 1000)
75
- end_ms = round(end[0] * 1000)
87
+ single_start = max(0, start[0])
88
+ single_end = max(0, end[0])
89
+ if single_end <= single_start:
90
+ print_fail("End time must be greater than start time.")
91
+ return False
92
+ print_info(f"Cutting from {single_start} to {single_end}")
93
+ start_ms = round(single_start * 1000)
94
+ end_ms = round(single_end * 1000)
76
95
  self.audio = self.audio[:start_ms] + self.audio[end_ms:]
77
96
  return True
78
97
  else:
79
98
  time_sets = list(zip(start, end))
80
99
  subtract_time = 0
81
100
  for single_start, single_end in time_sets:
82
- single_start = single_start - subtract_time
83
- single_end = single_end - subtract_time
101
+ single_start = max(0, single_start - subtract_time)
102
+ single_end = max(0, single_end - subtract_time)
103
+ if single_end <= single_start:
104
+ print_fail("End time must be greater than start time.")
105
+ continue
84
106
  print_info(f"Cutting from {single_start} to {single_end}")
85
107
  start_ms = round(single_start * 1000)
86
108
  end_ms = round(single_end * 1000)
@@ -123,11 +145,15 @@ class AudioProcessor:
123
145
  def stop_audio(self):
124
146
  try:
125
147
  if pygame.mixer.get_init():
148
+ pos_ms = pygame.mixer.music.get_pos()
149
+ pos_sec = pos_ms / 1000 if pos_ms >= 0 else 0
126
150
  pygame.mixer.music.stop()
127
151
  self.is_playing_var = False
128
- print_info("Audio playback stopped")
152
+ print_info(f"Audio playback stopped at {pos_sec:.2f}s")
153
+ return pos_sec
129
154
  except Exception as e:
130
155
  print_warn(f"Error stopping audio: {e}")
156
+ return 0
131
157
 
132
158
  def is_playing(self):
133
159
  try:
@@ -142,7 +168,7 @@ class AudioProcessor:
142
168
  return None
143
169
 
144
170
  return {
145
- "duration": self.get_lenght(),
171
+ "duration": self.get_length(),
146
172
  "channels": self.audio.channels,
147
173
  "frame_rate": self.audio.frame_rate,
148
174
  "sample_width": self.audio.sample_width
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cutted
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: AI-powered audio editor controllable via natural language.
5
5
  Author-email: simon0302010 <simon0302010@gmail.com>
6
6
  License-Expression: GPL-3.0
@@ -0,0 +1,12 @@
1
+ cutted/__init__.py,sha256=F1mzO6qI2gD6d_DQsjZItLuIfNfa1Te5KwCMThWDQT4,49
2
+ cutted/__main__.py,sha256=lYGLgtIZ_vGZIJmWG6ZQoqOdyOJnaWEA4NBn5Rc7Q8E,61
3
+ cutted/app.py,sha256=HaJ3yu8-WVDGKZmFcTVG4CK8q13eHyUQaIyOWMZWV68,10764
4
+ cutted/core/audio_processor.py,sha256=gKBJ1wpdrX2IozmXDzW7MoFVp9uq0Pb3ezz9R3Ahmnw,6691
5
+ cutted/core/gemini.py,sha256=yHsQXk4tDHcW5qJBuL2LpPvdChimxlmEbu76BvsoeY4,3108
6
+ cutted/core/logger.py,sha256=AjqrgW2LV9HdPkPQ8oOmyd9lWzVSIg46r74ILR7mVHo,585
7
+ cutted/core/transcribe.py,sha256=0e7aCva4y6D-gKe1xw5HT9VoFgbvHGgV6utn12r8wXA,986
8
+ cutted-0.3.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
9
+ cutted-0.3.3.dist-info/METADATA,sha256=ehEIEm5qz3wVqBY74Itpwooe4SYOFpXISXUMZO7LVh4,1503
10
+ cutted-0.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ cutted-0.3.3.dist-info/top_level.txt,sha256=PL6glZvzRyKWCDn5aoYI9uH8HlEA5Qd_XFJowJKARYI,7
12
+ cutted-0.3.3.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- cutted/__init__.py,sha256=ttFLenYuOpXQTaR9nB0dF-3zFB_PeksXf9R4r_TB8S8,49
2
- cutted/__main__.py,sha256=lYGLgtIZ_vGZIJmWG6ZQoqOdyOJnaWEA4NBn5Rc7Q8E,61
3
- cutted/app.py,sha256=Ay_yVrPt1TQAE3lqmrII88lFoGZb5Mh5cDwediM9ZG8,9592
4
- cutted/core/audio_processor.py,sha256=JdPeWO_jAIn_uZFeZYQJX3RC0Vy8GClKrX7xGk4pXR4,5426
5
- cutted/core/gemini.py,sha256=yHsQXk4tDHcW5qJBuL2LpPvdChimxlmEbu76BvsoeY4,3108
6
- cutted/core/logger.py,sha256=AjqrgW2LV9HdPkPQ8oOmyd9lWzVSIg46r74ILR7mVHo,585
7
- cutted/core/transcribe.py,sha256=0e7aCva4y6D-gKe1xw5HT9VoFgbvHGgV6utn12r8wXA,986
8
- cutted-0.3.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
9
- cutted-0.3.2.dist-info/METADATA,sha256=MPuSW4_LjNch0PXeFrGtRW1C4CuJ7unNv1ZZhvFHmSM,1503
10
- cutted-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
- cutted-0.3.2.dist-info/top_level.txt,sha256=PL6glZvzRyKWCDn5aoYI9uH8HlEA5Qd_XFJowJKARYI,7
12
- cutted-0.3.2.dist-info/RECORD,,
File without changes