DenisT commited on
Commit
629e7a5
·
1 Parent(s): 558f996

modified video_editor, html/css/js

Browse files
endpoints/video_editor_cut.py CHANGED
@@ -1,70 +1,55 @@
1
- import json
2
  import shutil
3
  import subprocess
4
- from typing import List
5
  from fastapi import UploadFile, File, Form
6
  from fastapi.responses import FileResponse
7
  from fastapi import APIRouter, BackgroundTasks
8
  import os
 
 
9
 
10
  router = APIRouter()
11
 
 
12
  @router.post("/cut")
13
  async def cut_videos(
14
- videos: List[UploadFile] = File(...),
15
- timestamps: str = Form(...),
16
- background_tasks: BackgroundTasks = BackgroundTasks()
 
17
  ):
18
- timestamps_dict = json.loads(timestamps)
19
  temp_dir = "./data/video_editor/videos"
20
  if not os.path.exists(temp_dir):
21
  os.makedirs(temp_dir)
22
 
23
- save_and_cut(videos, temp_dir, timestamps_dict)
24
-
25
- clean_temp_dir = remove_temp_files(temp_dir)
26
-
27
- zip_filename = shutil.make_archive(f"{clean_temp_dir}", 'zip', "./data/video_editor/")
28
-
29
- background_tasks.add_task(remove_zip_folder, zip_filename)
30
 
31
- return FileResponse(zip_filename)
 
 
32
 
 
33
 
34
- def save_and_cut(videos, temp_dir, timestamps_dict):
35
- for index, video in enumerate(videos):
36
- temp_video_path = f"{temp_dir}/{video.filename}-{index}.mp4"
37
- with open(temp_video_path, "wb") as f:
38
- shutil.copyfileobj(video.file, f)
39
 
40
- cut_start = timestamps_dict[str(index)]["start"]
41
- cut_end = timestamps_dict[str(index)]["end"]
 
 
 
42
 
43
- # Remove the "-index" from the filename by removing everything after the last "-"
44
- filename_without_extension = os.path.splitext(temp_video_path)[0]
45
- filename_without_index = filename_without_extension.rsplit("-", 1)[0]
46
- output_video_path = f"{filename_without_index}_cut.mp4"
47
 
48
- subprocess.run([
49
- "ffmpeg",
50
- "-i", temp_video_path,
51
- "-ss", cut_start,
52
- "-t", cut_end,
53
- "-y", output_video_path
54
- ])
 
55
 
56
- # Remove the temporary video file
57
- os.remove(temp_video_path)
58
 
59
- def remove_temp_files(temp_dir):
60
- for filename in os.listdir(temp_dir):
61
- if "cut" not in filename:
62
- os.remove(f"{temp_dir}/{filename}")
63
- if "cut" in filename:
64
- os.rename(f"{temp_dir}/{filename}", f"{temp_dir}/{filename.replace('_cut.mp4', '')}")
65
- return temp_dir
66
 
67
- def remove_zip_folder(zip_filename):
68
- if os.path.exists(zip_filename):
69
- shutil.rmtree(zip_filename[:-4])
70
- os.remove(zip_filename)
 
 
1
  import shutil
2
  import subprocess
 
3
  from fastapi import UploadFile, File, Form
4
  from fastapi.responses import FileResponse
5
  from fastapi import APIRouter, BackgroundTasks
6
  import os
7
+ from endpoints.utils import get_file_name
8
+ from endpoints.utils import remove_content_from_dir
9
 
10
  router = APIRouter()
11
 
12
+
13
  @router.post("/cut")
14
  async def cut_videos(
15
+ video: UploadFile = File(...),
16
+ start_time: str = Form(...),
17
+ end_time: str = Form(...),
18
+ background_tasks: BackgroundTasks = BackgroundTasks()
19
  ):
 
20
  temp_dir = "./data/video_editor/videos"
21
  if not os.path.exists(temp_dir):
22
  os.makedirs(temp_dir)
23
 
24
+ output_video_path = save_and_cut(video, temp_dir, start_time, end_time)
 
 
 
 
 
 
25
 
26
+ # send video back
27
+ video_name = get_file_name("video_cut")
28
+ # background_tasks.add_task(remove_content_from_dir, temp_dir)
29
 
30
+ return FileResponse(output_video_path, media_type="video/mp4", filename=video_name)
31
 
 
 
 
 
 
32
 
33
+ def save_and_cut(video: UploadFile, temp_dir, start_time, end_time):
34
+ # Save uploaded video to temporary directory
35
+ video_path = os.path.join(temp_dir, video.filename)
36
+ with open(video_path, 'wb') as buffer:
37
+ shutil.copyfileobj(video.file, buffer)
38
 
39
+ # Output video path
40
+ output_video_path = os.path.join(temp_dir, "output.mp4")
 
 
41
 
42
+ # Command to cut video using ffmpeg
43
+ ffmpeg_command = [
44
+ "ffmpeg",
45
+ "-i", video_path,
46
+ "-ss", start_time,
47
+ "-to", end_time,
48
+ "-y", output_video_path
49
+ ]
50
 
51
+ # Execute ffmpeg command
52
+ subprocess.run(ffmpeg_command, check=True)
53
 
54
+ return output_video_path
 
 
 
 
 
 
55
 
 
 
 
 
static/index.html CHANGED
@@ -11,6 +11,9 @@
11
  </head>
12
  <body>
13
  <h1>BrainRotTok</h1>
 
 
 
14
  <div class="form-buttons">
15
  <ul>
16
  <li>
@@ -32,7 +35,6 @@
32
  <button
33
  class="show-form"
34
  data-form-id="video-cutter-form"
35
- disabled
36
  >
37
  VIDEO CUTTER
38
  </button>
@@ -51,7 +53,7 @@
51
  <div id="forms-container">
52
  <form
53
  id="basic-form"
54
- class="hidden form-container"
55
  action="/generate-subtitles/basic"
56
  method="post"
57
  enctype="multipart/form-data"
@@ -59,17 +61,17 @@
59
  <h2 class="form-title">BASIC</h2>
60
  <p>Generate subtitles for a video.</p>
61
  <label for="video">Video:</label>
62
- <input type="file" id="video" name="video" />
63
  <label for="color">Color:</label>
64
- <input type="text" id="color" name="color" data-coloris />
65
  <label for="size">Size:</label>
66
- <input type="number" id="size" name="size" min="0"/>
67
  <label for="font">Font:</label>
68
  <select id="font" name="font"></select>
69
  <label for="credit">Credit:</label>
70
- <input type="text" id="credit" name="credit" />
71
  <label for="credit_size">Credit Size:</label>
72
- <input type="number" id="credit_size" name="credit_size" min="0" />
73
  <button type="submit">Submit</button>
74
  </form>
75
  <form
@@ -86,19 +88,19 @@
86
  have its subtitles generated.
87
  </p>
88
  <label for="top_video">Top Video:</label>
89
- <input type="file" id="top_video" name="top_video" />
90
  <label for="bottom_video">Bottom Video:</label>
91
- <input type="file" id="bottom_video" name="bottom_video" />
92
  <label for="color">Color:</label>
93
- <input type="text" id="color" name="color" data-coloris />
94
  <label for="size">Size:</label>
95
- <input type="number" id="size" name="size" min="0"/>
96
  <label for="font">Font:</label>
97
  <select id="font" name="font"></select>
98
  <label for="credit">Credit:</label>
99
- <input type="text" id="credit" name="credit" />
100
  <label for="credit_size">Credit Size:</label>
101
- <input type="number" id="credit_size" name="credit_size" min="0" />
102
  <button type="submit">Submit</button>
103
  </form>
104
  <form
@@ -114,19 +116,19 @@
114
  an AI voice with subtitles.
115
  </p>
116
  <label for="video">Video:</label>
117
- <input type="file" id="video" name="video" />
118
  <label for="subtitles">Subtitles:</label>
119
- <input type="text" id="subtitles" name="subtitles" />
120
  <label for="color">Color:</label>
121
- <input type="text" id="color" name="color" data-coloris />
122
  <label for="size">Size:</label>
123
- <input type="number" id="size" name="size" min="0"/>
124
  <label for="font">Font:</label>
125
  <select id="font" name="font"></select>
126
  <label for="credit">Credit:</label>
127
- <input type="text" id="credit" name="credit" />
128
  <label for="credit_size">Credit Size:</label>
129
- <input type="number" id="credit_size" name="credit_size" min="0" />
130
  <button type="submit">Submit</button>
131
  </form>
132
  <form
@@ -139,17 +141,17 @@
139
  <h2 class="form-title">RUMBLE</h2>
140
  <p>Generates subtitles for Rumble videos.</p>
141
  <label for="video_url">Video Url:</label>
142
- <input type="text" id="video_url" name="video_url" />
143
  <label for="color">Color:</label>
144
- <input type="text" id="color" name="color" data-coloris />
145
  <label for="size">Size:</label>
146
- <input type="number" id="size" name="size" min="0"/>
147
  <label for="font">Font:</label>
148
  <select id="font" name="font"></select>
149
  <label for="credit">Credit:</label>
150
- <input type="text" id="credit" name="credit" />
151
  <label for="credit_size">Credit Size:</label>
152
- <input type="number" id="credit_size" name="credit_size" min="0" />
153
  <button type="submit">Submit</button>
154
  </form>
155
  <form
@@ -162,11 +164,11 @@
162
  <h2 class="form-title">VIDEO CUTTER</h2>
163
  <p>Cut a video from a start time to an end time.</p>
164
  <label for="video">Video:</label>
165
- <input type="file" id="video" name="video" />
166
- <label for="start_time">Start Time:</label>
167
- <input type="text" id="start_time" name="start_time" />
168
- <label for="end_time">End Time:</label>
169
- <input type="text" id="end_time" name="end_time" />
170
  <button type="submit">Submit</button>
171
  </form>
172
  </div>
 
11
  </head>
12
  <body>
13
  <h1>BrainRotTok</h1>
14
+ <div class="reference">
15
+ <a href="https://github.com/Detopall/BrainRotTok" target="_blank">GitHub</a>
16
+ </div>
17
  <div class="form-buttons">
18
  <ul>
19
  <li>
 
35
  <button
36
  class="show-form"
37
  data-form-id="video-cutter-form"
 
38
  >
39
  VIDEO CUTTER
40
  </button>
 
53
  <div id="forms-container">
54
  <form
55
  id="basic-form"
56
+ class="form-container"
57
  action="/generate-subtitles/basic"
58
  method="post"
59
  enctype="multipart/form-data"
 
61
  <h2 class="form-title">BASIC</h2>
62
  <p>Generate subtitles for a video.</p>
63
  <label for="video">Video:</label>
64
+ <input required type="file" id="video" name="video" />
65
  <label for="color">Color:</label>
66
+ <input required type="text" id="color" name="color" data-coloris />
67
  <label for="size">Size:</label>
68
+ <input required type="number" id="size" name="size" min="0"/>
69
  <label for="font">Font:</label>
70
  <select id="font" name="font"></select>
71
  <label for="credit">Credit:</label>
72
+ <input required type="text" id="credit" name="credit" />
73
  <label for="credit_size">Credit Size:</label>
74
+ <input required type="number" id="credit_size" name="credit_size" min="0" />
75
  <button type="submit">Submit</button>
76
  </form>
77
  <form
 
88
  have its subtitles generated.
89
  </p>
90
  <label for="top_video">Top Video:</label>
91
+ <input required type="file" id="top_video" name="top_video" />
92
  <label for="bottom_video">Bottom Video:</label>
93
+ <input required type="file" id="bottom_video" name="bottom_video" />
94
  <label for="color">Color:</label>
95
+ <input required type="text" id="color" name="color" data-coloris />
96
  <label for="size">Size:</label>
97
+ <input required type="number" id="size" name="size" min="0"/>
98
  <label for="font">Font:</label>
99
  <select id="font" name="font"></select>
100
  <label for="credit">Credit:</label>
101
+ <input required type="text" id="credit" name="credit" />
102
  <label for="credit_size">Credit Size:</label>
103
+ <input required type="number" id="credit_size" name="credit_size" min="0" />
104
  <button type="submit">Submit</button>
105
  </form>
106
  <form
 
116
  an AI voice with subtitles.
117
  </p>
118
  <label for="video">Video:</label>
119
+ <input required type="file" id="video" name="video" />
120
  <label for="subtitles">Subtitles:</label>
121
+ <input required type="text" id="subtitles" name="subtitles" />
122
  <label for="color">Color:</label>
123
+ <input required type="text" id="color" name="color" data-coloris />
124
  <label for="size">Size:</label>
125
+ <input required type="number" id="size" name="size" min="0"/>
126
  <label for="font">Font:</label>
127
  <select id="font" name="font"></select>
128
  <label for="credit">Credit:</label>
129
+ <input required type="text" id="credit" name="credit" />
130
  <label for="credit_size">Credit Size:</label>
131
+ <input required type="number" id="credit_size" name="credit_size" min="0" />
132
  <button type="submit">Submit</button>
133
  </form>
134
  <form
 
141
  <h2 class="form-title">RUMBLE</h2>
142
  <p>Generates subtitles for Rumble videos.</p>
143
  <label for="video_url">Video Url:</label>
144
+ <input required type="text" id="video_url" name="video_url" />
145
  <label for="color">Color:</label>
146
+ <input required type="text" id="color" name="color" data-coloris />
147
  <label for="size">Size:</label>
148
+ <input required type="number" id="size" name="size" min="0"/>
149
  <label for="font">Font:</label>
150
  <select id="font" name="font"></select>
151
  <label for="credit">Credit:</label>
152
+ <input required type="text" id="credit" name="credit" />
153
  <label for="credit_size">Credit Size:</label>
154
+ <input required type="number" id="credit_size" name="credit_size" min="0" />
155
  <button type="submit">Submit</button>
156
  </form>
157
  <form
 
164
  <h2 class="form-title">VIDEO CUTTER</h2>
165
  <p>Cut a video from a start time to an end time.</p>
166
  <label for="video">Video:</label>
167
+ <input required type="file" id="video" name="video" />
168
+ <label for="start_time">Start Time (HH:MM:SS):</label>
169
+ <input required type="text" id="start_time" name="start_time" placeholder="00:00:00"/>
170
+ <label for="end_time">End Time (HH:MM:SS):</label>
171
+ <input required type="text" id="end_time" name="end_time" placeholder="00:00:00"/>
172
  <button type="submit">Submit</button>
173
  </form>
174
  </div>
static/scripts.js CHANGED
@@ -1,65 +1,48 @@
1
- "use strict"
2
 
3
  const common_fonts = [
4
- "Arial",
5
- "Times New Roman",
6
- "Courier New",
7
- "Verdana",
8
- "Georgia",
9
- "Palatino",
10
- "Bookman Old Style",
11
- "Tahoma",
12
- "Century Gothic",
13
- "Lucida Console",
14
- "Monaco",
15
- "Bradley Hand",
16
- "Calibri",
17
- "Cambria",
18
- "Candara",
19
- "Consolas",
20
- "Constantia",
21
- "Corbel",
22
- "Franklin Gothic Medium",
23
- "Gabriola"
24
  ];
25
 
26
  document.addEventListener("DOMContentLoaded", () => {
27
- const forms = document.querySelectorAll("form");
28
- forms.forEach((form) => {
29
- form.style.display = "none";
 
 
30
  });
31
 
32
- const formsContainer = document.querySelector("#forms-container");
33
- const noChosenForm = document.createElement("span");
34
- noChosenForm.classList.add("error");
35
- noChosenForm.textContent = "No form chosen";
36
- formsContainer.appendChild(noChosenForm);
37
- });
38
 
39
- const fontsSelect = document.querySelectorAll("#font").forEach((fontSelect) => {
40
- common_fonts.forEach(font => {
41
- const option = document.createElement('option');
42
- option.value = font;
43
- option.text = font;
44
- fontSelect.add(option);
45
  });
46
- });
47
 
48
- document.querySelectorAll(".show-form").forEach((button) => {
49
- button.addEventListener("click", (event) => {
50
- showForm(event.target.dataset.formId);
51
  });
52
  });
53
 
54
  function showForm(formId) {
55
- const forms = document.querySelectorAll("#forms-container form");
56
- forms.forEach((form) => {
57
- if (form.id === formId) {
58
- form.style.display = "block";
59
- const noChosenForm = document.querySelector(".error");
60
- noChosenForm.style.display = "none";
61
- } else {
62
- form.style.display = "none";
63
- }
64
  });
65
  }
 
 
 
 
 
 
 
 
1
+ "use strict";
2
 
3
  const common_fonts = [
4
+ "Arial", "Times New Roman", "Courier New", "Verdana", "Georgia", "Palatino",
5
+ "Bookman Old Style", "Tahoma", "Century Gothic", "Lucida Console", "Monaco",
6
+ "Bradley Hand", "Calibri", "Cambria", "Candara", "Consolas", "Constantia",
7
+ "Corbel", "Franklin Gothic Medium", "Gabriola"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  ];
9
 
10
  document.addEventListener("DOMContentLoaded", () => {
11
+ const startTimeInput = document.querySelector("#start_time");
12
+ const endTimeInput = document.querySelector("#end_time");
13
+
14
+ [startTimeInput, endTimeInput].forEach(input => {
15
+ input.addEventListener("input", () => validateTimeFormat(input));
16
  });
17
 
18
+ document.querySelectorAll(".show-form").forEach(option => {
19
+ option.addEventListener("click", () => {
20
+ document.querySelectorAll(".show-form").forEach(opt => opt.classList.remove("active-option"));
21
+ option.classList.add("active-option");
22
+ });
23
+ });
24
 
25
+ document.querySelectorAll("#font").forEach(fontSelect => {
26
+ common_fonts.forEach(font => {
27
+ const option = new Option(font, font);
28
+ fontSelect.add(option);
29
+ });
 
30
  });
 
31
 
32
+ document.querySelectorAll(".show-form").forEach(button => {
33
+ button.addEventListener("click", event => showForm(event.target.dataset.formId));
 
34
  });
35
  });
36
 
37
  function showForm(formId) {
38
+ document.querySelectorAll("#forms-container form").forEach(form => {
39
+ form.style.display = form.id === formId ? "block" : "none";
 
 
 
 
 
 
 
40
  });
41
  }
42
+
43
+ function validateTimeFormat(input) {
44
+ const regex = /^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/;
45
+ const isValid = regex.test(input.value);
46
+ input.setCustomValidity(isValid ? "" : "Invalid time format. Please use HH:MM:SS");
47
+ input.style.borderColor = isValid ? "#ccc" : "red";
48
+ }
static/styles.css CHANGED
@@ -177,3 +177,31 @@ option:checked {
177
  background-color: #007bff;
178
  color: #fff;
179
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  background-color: #007bff;
178
  color: #fff;
179
  }
180
+
181
+ .active-option {
182
+ background-color: #1f5792;
183
+ color: #fff;
184
+ }
185
+
186
+ .clr-field {
187
+ margin-bottom: 15px;
188
+ }
189
+
190
+ .reference {
191
+ margin-top: 20px;
192
+ text-align: center;
193
+ }
194
+
195
+ .reference a {
196
+ color: #007bff;
197
+ font-weight: bold;
198
+ transition: color 0.3s ease;
199
+ }
200
+
201
+ .reference a:hover {
202
+ color: #0056b3;
203
+ }
204
+
205
+ .hidden {
206
+ display: none;
207
+ }