Topallaj Denis commited on
Commit
aff0a09
·
1 Parent(s): 2009d4b
.gitignore ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ env/
126
+ venv/
127
+ ENV/
128
+ env.bak/
129
+ venv.bak/
130
+
131
+ # Spyder project settings
132
+ .spyderproject
133
+ .spyproject
134
+
135
+ # Rope project settings
136
+ .ropeproject
137
+
138
+ # mkdocs documentation
139
+ /site
140
+
141
+ # mypy
142
+ .mypy_cache/
143
+ .dmypy.json
144
+ dmypy.json
145
+
146
+ # Pyre type checker
147
+ .pyre/
148
+
149
+ # pytype static type analyzer
150
+ .pytype/
151
+
152
+ # Cython debug symbols
153
+ cython_debug/
154
+
155
+ # PyCharm
156
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
159
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160
+ #.idea/
161
+
162
+ Pipfile
163
+ Pipfile.lock
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
+ # you will also find guides on how best to write your Dockerfile
3
+
4
+ FROM python:3.11
5
+
6
+ WORKDIR /code
7
+
8
+ COPY ./requirements.txt /code/requirements.txt
9
+
10
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
11
+
12
+ RUN docker pull jrottenberg/ffmpeg
13
+
14
+ COPY . .
15
+
16
+ RUN useradd -m -u 1000 user
17
+
18
+ USER user
19
+
20
+ ENV HOME=/home/user \
21
+ PATH=/home/user/.local/bin:$PATH
22
+
23
+ WORKDIR $HOME/app
24
+
25
+ COPY --chown=user . $HOME/app
26
+
27
+ CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,10 +1,43 @@
1
  ---
2
  title: BrainRotTok
3
- emoji: 📊
4
- colorFrom: pink
5
- colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: BrainRotTok
3
+ emoji: 🧟
4
+ colorFrom: green
5
+ colorTo: pink
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
11
+
12
+ # BrainRotTok
13
+
14
+ ## What is BrainRotTok?
15
+
16
+ BrainRotTok allows you to create those iconic TikTok videos with subtitles. There are different types of videos you can create, you can find the list of them below in the "What can you do with this?" section.
17
+
18
+ The same project is also available on GitHub: [Detopall/BrainRotTok](https://github.com/Detopall/BrainRotTok)
19
+
20
+ ## What is brain rot?
21
+
22
+ Brain rot is a term used to describe the feeling of your brain rotting away after watching those highly addictive, short videos on TikTok. It is also used to describe the content that causes this feeling. Brain rot content is usually low effort, low quality, and highly addictive.
23
+
24
+ [Urban Dictionary](https://www.urbandictionary.com/define.php?term=Brainrot%20Content)
25
+
26
+ ## Why did I make this?
27
+
28
+ I kept seeing these videos on TikTok and I thought it would be fun to make a website that allows you to create these videos. I also wanted to use FFmpeg and OpenAI's Whisper API, so I thought this would be a good project to do that.
29
+
30
+ ## What can you do with this?
31
+
32
+ - **Basic**: a video with subtitles underneath
33
+ - **Subway Surfers**: a top and bottom video with subtitles for the bottom video and the top video is muted
34
+ - **Minecraft Reddit**: a background video with subtitles (provided by you) in the middle and the subtitles are spoken by a text-to-speech voice
35
+
36
+ ## What technologies are used?
37
+
38
+ - React (with TypeScript)
39
+ - Python
40
+ - FastAPI
41
+ - Whisper (OpenAI)
42
+ - FFmpeg
43
+ - ImageMagick
data/basic/audio.mp3 ADDED
Binary file (319 kB). View file
 
data/basic/subtitles.srt ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 1.1
2
+ 00:00:00,000 --> 00:00:00,020
3
+ I
4
+
5
+ 1.2
6
+ 00:00:00,020 --> 00:00:00,100
7
+ know
8
+
9
+ 1.3
10
+ 00:00:00,100 --> 00:00:00,280
11
+ what
12
+
13
+ 1.4
14
+ 00:00:00,280 --> 00:00:00,460
15
+ you
16
+
17
+ 1.5
18
+ 00:00:00,460 --> 00:00:00,800
19
+ want,
20
+
21
+ 1.6
22
+ 00:00:01,179 --> 00:00:01,480
23
+ at
24
+
25
+ 1.7
26
+ 00:00:01,480 --> 00:00:01,620
27
+ the
28
+
29
+ 1.8
30
+ 00:00:01,620 --> 00:00:01,840
31
+ meeting,
32
+
33
+ 1.9
34
+ 00:00:02,240 --> 00:00:02,439
35
+ if
36
+
37
+ 1.10
38
+ 00:00:02,439 --> 00:00:02,580
39
+ you
40
+
41
+ 1.11
42
+ 00:00:02,580 --> 00:00:02,819
43
+ don't
44
+
45
+ 1.12
46
+ 00:00:02,819 --> 00:00:02,980
47
+ have
48
+
49
+ 1.13
50
+ 00:00:02,980 --> 00:00:03,100
51
+ a
52
+
53
+ 1.14
54
+ 00:00:03,100 --> 00:00:03,299
55
+ game
56
+
57
+ 1.15
58
+ 00:00:03,299 --> 00:00:03,620
59
+ plan.
60
+
61
+ 2.1
62
+ 00:00:03,859 --> 00:00:03,980
63
+ He
64
+
65
+ 2.2
66
+ 00:00:03,980 --> 00:00:04,139
67
+ may
68
+
69
+ 2.3
70
+ 00:00:04,139 --> 00:00:04,280
71
+ have
72
+
73
+ 2.4
74
+ 00:00:04,280 --> 00:00:04,419
75
+ a
76
+
77
+ 2.5
78
+ 00:00:04,419 --> 00:00:04,599
79
+ game
80
+
81
+ 2.6
82
+ 00:00:04,599 --> 00:00:04,879
83
+ plan.
84
+
85
+ 3.1
86
+ 00:00:05,559 --> 00:00:05,740
87
+ He
88
+
89
+ 3.2
90
+ 00:00:05,740 --> 00:00:05,919
91
+ just
92
+
93
+ 3.3
94
+ 00:00:05,919 --> 00:00:06,400
95
+ hasn't
96
+
97
+ 3.4
98
+ 00:00:06,400 --> 00:00:06,740
99
+ shared
100
+
101
+ 3.5
102
+ 00:00:06,740 --> 00:00:06,919
103
+ it
104
+
105
+ 3.6
106
+ 00:00:06,919 --> 00:00:07,040
107
+ with
108
+
109
+ 3.7
110
+ 00:00:07,040 --> 00:00:07,320
111
+ me.
112
+
113
+ 4.1
114
+ 00:00:07,860 --> 00:00:08,039
115
+ But
116
+
117
+ 4.2
118
+ 00:00:08,039 --> 00:00:08,160
119
+ I
120
+
121
+ 4.3
122
+ 00:00:08,160 --> 00:00:08,339
123
+ tell
124
+
125
+ 4.4
126
+ 00:00:08,339 --> 00:00:08,500
127
+ you
128
+
129
+ 4.5
130
+ 00:00:08,500 --> 00:00:08,619
131
+ what,
132
+
133
+ 4.6
134
+ 00:00:08,699 --> 00:00:08,779
135
+ I
136
+
137
+ 4.7
138
+ 00:00:08,779 --> 00:00:08,900
139
+ don't
140
+
141
+ 4.8
142
+ 00:00:08,900 --> 00:00:08,980
143
+ know
144
+
145
+ 4.9
146
+ 00:00:08,980 --> 00:00:09,140
147
+ about
148
+
149
+ 4.10
150
+ 00:00:09,140 --> 00:00:09,460
151
+ you,
152
+
153
+ 4.11
154
+ 00:00:09,839 --> 00:00:10,199
155
+ but
156
+
157
+ 4.12
158
+ 00:00:10,199 --> 00:00:10,419
159
+ I'm
160
+
161
+ 4.13
162
+ 00:00:10,419 --> 00:00:10,599
163
+ going
164
+
165
+ 4.14
166
+ 00:00:10,599 --> 00:00:10,720
167
+ to
168
+
169
+ 4.15
170
+ 00:00:10,720 --> 00:00:10,859
171
+ go
172
+
173
+ 4.16
174
+ 00:00:10,859 --> 00:00:11,000
175
+ to
176
+
177
+ 4.17
178
+ 00:00:11,000 --> 00:00:11,220
179
+ bed.
180
+
data/minecraft/audio.mp3 ADDED
Binary file (621 kB). View file
 
data/minecraft/subtitles.srt ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 1.1
2
+ 00:00:00,080 --> 00:00:00,220
3
+ This
4
+
5
+ 1.2
6
+ 00:00:00,220 --> 00:00:00,380
7
+ loop
8
+
9
+ 1.3
10
+ 00:00:00,380 --> 00:00:00,740
11
+ iterates
12
+
13
+ 1.4
14
+ 00:00:00,740 --> 00:00:00,920
15
+ over
16
+
17
+ 1.5
18
+ 00:00:00,920 --> 00:00:01,080
19
+ the
20
+
21
+ 1.6
22
+ 00:00:01,080 --> 00:00:01,260
23
+ range
24
+
25
+ 1.7
26
+ 00:00:01,260 --> 00:00:01,379
27
+ of
28
+
29
+ 1.8
30
+ 00:00:01,379 --> 00:00:01,500
31
+ the
32
+
33
+ 1.9
34
+ 00:00:01,500 --> 00:00:01,639
35
+ length
36
+
37
+ 1.10
38
+ 00:00:01,639 --> 00:00:01,780
39
+ of
40
+
41
+ 1.11
42
+ 00:00:01,780 --> 00:00:01,980
43
+ input
44
+
45
+ 1.12
46
+ 00:00:01,980 --> 00:00:02,240
47
+ clips
48
+
49
+ 1.13
50
+ 00:00:02,240 --> 00:00:02,439
51
+ and
52
+
53
+ 1.14
54
+ 00:00:02,439 --> 00:00:02,700
55
+ attempts
56
+
57
+ 1.15
58
+ 00:00:02,700 --> 00:00:02,919
59
+ to
60
+
61
+ 1.16
62
+ 00:00:02,919 --> 00:00:03,180
63
+ access
64
+
65
+ 1.17
66
+ 00:00:03,180 --> 00:00:03,439
67
+ each
68
+
69
+ 1.18
70
+ 00:00:03,439 --> 00:00:03,640
71
+ item
72
+
73
+ 1.19
74
+ 00:00:03,640 --> 00:00:03,819
75
+ and
76
+
77
+ 1.20
78
+ 00:00:03,819 --> 00:00:04,019
79
+ input
80
+
81
+ 1.21
82
+ 00:00:04,019 --> 00:00:04,419
83
+ underscore
84
+
85
+ 1.22
86
+ 00:00:04,419 --> 00:00:04,660
87
+ clips
88
+
89
+ 1.23
90
+ 00:00:04,660 --> 00:00:04,919
91
+ using
92
+
93
+ 1.24
94
+ 00:00:04,919 --> 00:00:05,240
95
+ input
96
+
97
+ 1.25
98
+ 00:00:05,240 --> 00:00:05,559
99
+ underscore
100
+
101
+ 1.26
102
+ 00:00:05,559 --> 00:00:05,879
103
+ clips
104
+
105
+ 1.27
106
+ 00:00:05,879 --> 00:00:06,500
107
+ IDX.
108
+
109
+ 2.1
110
+ 00:00:07,240 --> 00:00:07,400
111
+ The
112
+
113
+ 2.2
114
+ 00:00:07,400 --> 00:00:07,580
115
+ error
116
+
117
+ 2.3
118
+ 00:00:07,580 --> 00:00:07,919
119
+ suggests
120
+
121
+ 2.4
122
+ 00:00:07,919 --> 00:00:08,220
123
+ that
124
+
125
+ 2.5
126
+ 00:00:08,220 --> 00:00:08,699
127
+ IDX
128
+
129
+ 2.6
130
+ 00:00:08,699 --> 00:00:08,900
131
+ goes
132
+
133
+ 2.7
134
+ 00:00:08,900 --> 00:00:09,199
135
+ beyond
136
+
137
+ 2.8
138
+ 00:00:09,199 --> 00:00:09,339
139
+ the
140
+
141
+ 2.9
142
+ 00:00:09,339 --> 00:00:09,519
143
+ length
144
+
145
+ 2.10
146
+ 00:00:09,519 --> 00:00:09,660
147
+ of
148
+
149
+ 2.11
150
+ 00:00:09,660 --> 00:00:09,859
151
+ input
152
+
153
+ 2.12
154
+ 00:00:09,859 --> 00:00:10,199
155
+ clips,
156
+
157
+ 2.13
158
+ 00:00:10,560 --> 00:00:10,919
159
+ indicating
160
+
161
+ 2.14
162
+ 00:00:10,919 --> 00:00:11,179
163
+ that
164
+
165
+ 2.15
166
+ 00:00:11,179 --> 00:00:11,300
167
+ there
168
+
169
+ 2.16
170
+ 00:00:11,300 --> 00:00:11,460
171
+ might
172
+
173
+ 2.17
174
+ 00:00:11,460 --> 00:00:11,619
175
+ be
176
+
177
+ 2.18
178
+ 00:00:11,619 --> 00:00:11,820
179
+ fewer
180
+
181
+ 2.19
182
+ 00:00:11,820 --> 00:00:12,080
183
+ items
184
+
185
+ 2.20
186
+ 00:00:12,080 --> 00:00:12,300
187
+ in
188
+
189
+ 2.21
190
+ 00:00:12,300 --> 00:00:12,519
191
+ input
192
+
193
+ 2.22
194
+ 00:00:12,519 --> 00:00:12,759
195
+ clips
196
+
197
+ 2.23
198
+ 00:00:12,759 --> 00:00:12,960
199
+ than
200
+
201
+ 2.24
202
+ 00:00:12,960 --> 00:00:13,320
203
+ expected.
204
+
data/subway/audio.mp3 ADDED
Binary file (236 kB). View file
 
data/subway/subtitles.srt ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 1.1
2
+ 00:00:00,000 --> 00:00:01,459
3
+ big
4
+
5
+ 1.2
6
+ 00:00:01,459 --> 00:00:01,899
7
+ data,
8
+
9
+ 1.3
10
+ 00:00:02,819 --> 00:00:03,439
11
+ machine
12
+
13
+ 1.4
14
+ 00:00:03,439 --> 00:00:03,879
15
+ learning,
16
+
17
+ 1.5
18
+ 00:00:04,660 --> 00:00:05,320
19
+ blockchain,
20
+
21
+ 1.6
22
+ 00:00:05,879 --> 00:00:06,740
23
+ artificial
24
+
25
+ 1.7
26
+ 00:00:06,740 --> 00:00:07,400
27
+ intelligence,
28
+
29
+ 1.8
30
+ 00:00:07,719 --> 00:00:07,980
31
+ digital
32
+
33
+ 1.9
34
+ 00:00:07,980 --> 00:00:08,599
35
+ manufacturing,
36
+
37
+ 1.10
38
+ 00:00:09,119 --> 00:00:09,380
39
+ big
40
+
41
+ 1.11
42
+ 00:00:09,380 --> 00:00:09,660
43
+ data
44
+
45
+ 2.1
46
+ 00:00:09,679 --> 00:00:10,140
47
+ analysis,
48
+
49
+ 2.2
50
+ 00:00:10,640 --> 00:00:11,060
51
+ quantum
52
+
53
+ 2.3
54
+ 00:00:11,060 --> 00:00:11,720
55
+ communication
56
+
57
+ 2.4
58
+ 00:00:11,720 --> 00:00:12,740
59
+ and
60
+
61
+ 2.5
62
+ 00:00:12,740 --> 00:00:13,660
63
+ internet
64
+
65
+ 2.6
66
+ 00:00:13,660 --> 00:00:13,900
67
+ of
68
+
69
+ 2.7
70
+ 00:00:13,900 --> 00:00:14,179
71
+ things,
72
+
73
+ 2.8
74
+ 00:00:14,259 --> 00:00:14,419
75
+ of
76
+
77
+ 2.9
78
+ 00:00:14,419 --> 00:00:14,679
79
+ things.
80
+
endpoints/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter
2
+
3
+ from .subway import router as subway_router
4
+ from .minecraft import router as minecraft_router
5
+ from .basic import router as basic_router
6
+ from .rumble import router as rumble_router
7
+ from .video_editor_cut import router as video_editor
endpoints/basic.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import UploadFile, File, Form, BackgroundTasks
2
+ from fastapi.responses import FileResponse
3
+ from fastapi import APIRouter
4
+ from generate_basic_subtitles import generate_basic_subtitles
5
+ from endpoints.utils.remove_content_from_dir import remove_content_from_dir
6
+ import os
7
+ import shutil
8
+
9
+ router = APIRouter()
10
+
11
+ @router.post("/basic")
12
+ async def create_basic_video(
13
+ video: UploadFile = File(...),
14
+ color: str = Form(...),
15
+ size: int = Form(...),
16
+ font: str = Form(...),
17
+ credit: str = Form(...),
18
+ credit_size: int = Form(...),
19
+ background_tasks: BackgroundTasks = BackgroundTasks()
20
+ ):
21
+ # Save the videos
22
+ basic_video_path = "./data/basic/videos/video.mp4"
23
+ with open(basic_video_path, "wb") as buffer:
24
+ shutil.copyfileobj(video.file, buffer)
25
+
26
+ options = {
27
+ "video": basic_video_path,
28
+ "font_color": color,
29
+ "font_size": size,
30
+ "font_family": font,
31
+ "border_size": 1,
32
+ "credit": credit,
33
+ "credit_size": credit_size,
34
+ }
35
+
36
+ result_video_path = generate_basic_subtitles(options)
37
+
38
+
39
+ if not os.path.exists(result_video_path):
40
+ return {"message": "Error generating subtitles!"}
41
+
42
+ background_tasks.add_task(remove_content_from_dir, "./data/basic/videos")
43
+
44
+ return FileResponse(result_video_path, media_type="video/mp4", filename="result.mp4")
endpoints/minecraft.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import UploadFile, File, Form, BackgroundTasks
2
+ from fastapi.responses import FileResponse
3
+ from fastapi import APIRouter
4
+ from generate_minecraft_subtitles import generate_minecraft_subtitles
5
+ from endpoints.utils.remove_content_from_dir import remove_content_from_dir
6
+ import os
7
+ import shutil
8
+
9
+ router = APIRouter()
10
+
11
+ @router.post("/minecraft")
12
+ async def create_minecraft_video(
13
+ video: UploadFile = File(...),
14
+ subtitles: str = Form(...),
15
+ color: str = Form(...),
16
+ size: int = Form(...),
17
+ font: str = Form(...),
18
+ credit: str = Form(...),
19
+ credit_size: int = Form(...),
20
+ background_tasks: BackgroundTasks = BackgroundTasks()
21
+ ):
22
+
23
+ background_video_path = "./data/minecraft/videos/background_video.mp4"
24
+ # Save the videos
25
+ with open(background_video_path, "wb") as buffer:
26
+ shutil.copyfileobj(video.file, buffer)
27
+
28
+ options = {
29
+ "background_video": background_video_path,
30
+ "font_color": color,
31
+ "font_size": size,
32
+ "font_family": font,
33
+ "border_size": 1,
34
+ "credit": credit,
35
+ "credit_size": credit_size,
36
+ }
37
+
38
+ result_video_path = generate_minecraft_subtitles(options, subtitles)
39
+
40
+ if not os.path.exists(result_video_path):
41
+ return {"message": "Error generating subtitles!"}
42
+
43
+ background_tasks.add_task(remove_content_from_dir, "./data/minecraft/videos")
44
+
45
+ return FileResponse(result_video_path, media_type="video/mp4", filename="result.mp4")
endpoints/subway.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import UploadFile, File, Form, BackgroundTasks
2
+ from fastapi.responses import FileResponse
3
+ from fastapi import APIRouter
4
+ from generate_subway_subtitles import generate_subway_subtitles
5
+ from endpoints.utils.remove_content_from_dir import remove_content_from_dir
6
+ import os
7
+ import shutil
8
+
9
+ router = APIRouter()
10
+
11
+ @router.post("/subway")
12
+ async def create_subway_video(
13
+ top_video: UploadFile = File(...),
14
+ bottom_video: UploadFile = File(...),
15
+ color: str = Form(...),
16
+ size: int = Form(...),
17
+ font: str = Form(...),
18
+ credit: str = Form(...),
19
+ credit_size: int = Form(...),
20
+ background_tasks: BackgroundTasks = BackgroundTasks()
21
+ ):
22
+ top_video_path = "./data/subway/videos/top_video.mp4"
23
+ bottom_video_path = "./data/subway/videos/bottom_video.mp4"
24
+ # Save the videos
25
+ with open(top_video_path, "wb") as buffer:
26
+ shutil.copyfileobj(top_video.file, buffer)
27
+ with open(bottom_video_path, "wb") as buffer:
28
+ shutil.copyfileobj(bottom_video.file, buffer)
29
+
30
+ options = {
31
+ "top_video": top_video_path,
32
+ "bottom_video": bottom_video_path,
33
+ "font_color": color,
34
+ "font_size": size,
35
+ "font_family": font,
36
+ "border_size": 1,
37
+ "credit": credit,
38
+ "credit_size": credit_size,
39
+ }
40
+
41
+ result_video_path = generate_subway_subtitles(options)
42
+
43
+ if not os.path.exists(result_video_path):
44
+ return {"message": "Error generating subtitles!"}
45
+
46
+ background_tasks.add_task(remove_content_from_dir, "./data/subway/videos")
47
+
48
+ return FileResponse(result_video_path, media_type="video/mp4", filename="result.mp4")
endpoints/utils/remove_content_from_dir.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+
4
+ def remove_content_from_dir(folder):
5
+ for filename in os.listdir(folder):
6
+ file_path = os.path.join(folder, filename)
7
+ try:
8
+ if os.path.isfile(file_path) or os.path.islink(file_path):
9
+ os.unlink(file_path)
10
+ elif os.path.isdir(file_path):
11
+ shutil.rmtree(file_path)
12
+ except Exception as e:
13
+ print('Failed to delete %s. Reason: %s' % (file_path, e))
endpoints/video_editor_cut.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)
generate_basic_subtitles.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from utils.create_subtitles import hex_to_ffmpeg_color, create_subtitles
2
+ from utils.transcribe_audio import transcribe_audio
3
+ from utils.video_to_audio import extract_audio
4
+ import subprocess
5
+ import os
6
+
7
+ def generate_basic_subtitles(customization_options):
8
+ customization_options['font_color'] = hex_to_ffmpeg_color(customization_options['font_color'])
9
+
10
+ video_path = customization_options['video']
11
+ audio_file_path = "./data/basic/audio.mp3"
12
+ subtitle_file = "./data/basic/subtitles.srt"
13
+ output_directory = "./data/basic/videos"
14
+
15
+ extract_audio(video_path, audio_file_path)
16
+ transcription = transcribe_audio(audio_file_path)
17
+ create_subtitles(transcription, subtitle_file)
18
+
19
+ subtitle_output_path = add_subtitle(video_path, subtitle_file, customization_options, output_directory)
20
+ result_output_path = add_text_to_video(subtitle_output_path, customization_options, output_directory)
21
+
22
+ return result_output_path
23
+
24
+
25
+ def add_subtitle(video_path, subtitle_file, customization_options, output_directory):
26
+ subtitle_video_path = os.path.join(output_directory, 'subtitle_video.mp4')
27
+
28
+ # Add the subtitle to the video
29
+ subtitle_cmd = [
30
+ "ffmpeg",
31
+ "-i", video_path,
32
+ "-vf", f"subtitles={subtitle_file}:force_style='Fontsize={customization_options['font_size']},PrimaryColour={customization_options['font_color']},Fontname={customization_options['font_family']},MarginV=50'",
33
+ "-c:a", "copy",
34
+ "-y", subtitle_video_path
35
+ ]
36
+
37
+ subprocess.run(subtitle_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
38
+
39
+ return subtitle_video_path
40
+
41
+
42
+ def add_text_to_video(subtitle_output_path, customization_options, output_directory):
43
+ result_video_path = os.path.join(output_directory, 'result.mp4')
44
+ cmd = [
45
+ 'ffmpeg',
46
+ '-i', subtitle_output_path,
47
+ '-vf', f'drawtext=text=\'{customization_options["credit"]}\':fontcolor=white:fontsize={customization_options["credit_size"]}:box=1:[email protected]:boxborderw=5:x=(w-text_w)/2:y=h-th-10',
48
+ '-codec:a', 'copy',
49
+ "-y", result_video_path
50
+ ]
51
+
52
+ subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
53
+
54
+ return result_video_path
generate_minecraft_subtitles.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from utils.create_subtitles import hex_to_ffmpeg_color, create_subtitles
2
+ from utils.transcribe_audio import transcribe_audio
3
+ from utils.text_to_speech import text_to_speech
4
+ import subprocess
5
+ import os
6
+
7
+ def generate_minecraft_subtitles(customization_options, subtitles):
8
+ customization_options['font_color'] = hex_to_ffmpeg_color(customization_options['font_color'])
9
+
10
+ audio_file_path = "./data/minecraft/audio.mp3"
11
+ subtitle_file = "./data/minecraft/subtitles.srt"
12
+
13
+ background_video_path = customization_options['background_video']
14
+ output_directory = "./data/minecraft/videos"
15
+
16
+ text_to_speech(subtitles, audio_file_path)
17
+ transcription = transcribe_audio(audio_file_path)
18
+ create_subtitles(transcription, subtitle_file)
19
+
20
+ audio_output_video_path = background_video_operations(background_video_path, audio_file_path, output_directory)
21
+ subtitle_output_path = add_subtitle(audio_output_video_path, subtitle_file, customization_options, output_directory)
22
+ result_output_path = add_text_to_video(subtitle_output_path, customization_options, output_directory)
23
+
24
+ return result_output_path
25
+
26
+
27
+
28
+ def background_video_operations(background_video_path, audio_file_path, output_directory):
29
+ muted_output_filepath = os.path.join(output_directory, 'video_muted.mp4')
30
+ trimmed_output_filepath = os.path.join(output_directory, 'video_muted_trimmed.mp4')
31
+ audio_output_video_path = os.path.join(output_directory, 'audio_background_video.mp4')
32
+
33
+ # Mute the background video
34
+ mute_background_cmd = [
35
+ "ffmpeg",
36
+ "-i", background_video_path,
37
+ "-af", "volume=0.0",
38
+ "-c:v", "copy",
39
+ "-y", muted_output_filepath
40
+ ]
41
+
42
+ subprocess.run(mute_background_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
43
+
44
+
45
+ # Audio duration
46
+ audio_duration_cmd = [
47
+ 'ffprobe',
48
+ '-i', audio_file_path,
49
+ '-show_entries',
50
+ 'format=duration',
51
+ '-v', 'quiet',
52
+ '-of', 'csv=p=0'
53
+ ]
54
+ audio_duration = float(subprocess.check_output(audio_duration_cmd).decode('utf-8').strip())
55
+
56
+ # Trim the video to match the duration of the audio
57
+ trim_video_cmd = [
58
+ 'ffmpeg',
59
+ '-i', muted_output_filepath,
60
+ '-t', str(audio_duration),
61
+ '-c', 'copy',
62
+ "-y", trimmed_output_filepath
63
+ ]
64
+ subprocess.run(trim_video_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
65
+
66
+ # Place the audio over the video
67
+ add_audio_cmd = [
68
+ 'ffmpeg',
69
+ '-i', trimmed_output_filepath,
70
+ '-i', audio_file_path,
71
+ '-c:v', 'copy',
72
+ '-c:a', 'aac',
73
+ '-map', '0:v:0',
74
+ '-map', '1:a:0',
75
+ '-shortest',
76
+ '-y', audio_output_video_path
77
+ ]
78
+
79
+ subprocess.run(add_audio_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
80
+
81
+ return audio_output_video_path
82
+
83
+
84
+ def add_subtitle(audio_output_video_path, subtitle_file, customization_options, output_directory):
85
+ subtitle_video_path = os.path.join(output_directory, 'subtitle_video.mp4')
86
+
87
+ subtitle_cmd = [
88
+ "ffmpeg",
89
+ "-i", audio_output_video_path,
90
+ "-vf", f"subtitles={subtitle_file}:force_style='Fontsize={customization_options['font_size']},Fontname={customization_options['font_family']},BorderColor=black@{customization_options['border_size']},PrimaryColour={customization_options['font_color']}'",
91
+ "-c:a", "aac",
92
+ "-c:v", "libx264",
93
+ "-y", subtitle_video_path,
94
+ ]
95
+
96
+ subprocess.run(subtitle_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
97
+
98
+ return subtitle_video_path
99
+
100
+
101
+ def add_text_to_video(subtitle_output_path, customization_options, output_directory):
102
+ result_video_path = os.path.join(output_directory, 'result.mp4')
103
+ cmd = [
104
+ 'ffmpeg',
105
+ '-i', subtitle_output_path,
106
+ '-vf', f'drawtext=text=\'{customization_options["credit"]}\':fontcolor=white:fontsize={customization_options["credit_size"]}:box=1:[email protected]:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2',
107
+ '-codec:a', 'copy',
108
+ "-y", result_video_path
109
+ ]
110
+
111
+ subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
112
+
113
+ return result_video_path
generate_subway_subtitles.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ from utils.video_to_audio import extract_audio
3
+ from utils.transcribe_audio import transcribe_audio
4
+ from utils.create_subtitles import create_subtitles, hex_to_ffmpeg_color
5
+ import os
6
+
7
+
8
+ def generate_subway_subtitles(customization_options):
9
+ customization_options['font_color'] = hex_to_ffmpeg_color(customization_options['font_color'])
10
+ audio_file = "./data/subway/audio.mp3"
11
+ subtitle_file = "./data/subway/subtitles.srt"
12
+
13
+ top_video_path = customization_options['top_video']
14
+ bottom_video_path = customization_options['bottom_video']
15
+ output_directory = "./data/subway/videos"
16
+
17
+ extract_audio(bottom_video_path, audio_file)
18
+ transcription = transcribe_audio(audio_file)
19
+ create_subtitles(transcription, subtitle_file)
20
+
21
+ trimmed_output_filepath = top_video_operations(top_video_path, bottom_video_path, output_directory)
22
+
23
+ bottom_video_width, bottom_video_height = bottom_video_operations(bottom_video_path)
24
+
25
+ combined_output_path = combination_video(trimmed_output_filepath, bottom_video_path, bottom_video_width, bottom_video_height, output_directory)
26
+
27
+ subtitle_output_filepath = add_subtitle(combined_output_path, subtitle_file, customization_options, output_directory)
28
+
29
+ result_video_path = add_text_to_video(subtitle_output_filepath, customization_options, output_directory)
30
+
31
+ return result_video_path
32
+
33
+
34
+
35
+ def top_video_operations(top_video_path, bottom_video_path, output_directory):
36
+ muted_output_filepath = os.path.join(output_directory, 'top_video_muted.mp4')
37
+
38
+ trimmed_output_filepath = os.path.join(output_directory, 'top_video_muted_trimmed.mp4')
39
+
40
+ mute_top_cmd = [
41
+ "ffmpeg",
42
+ "-i", top_video_path,
43
+ "-af", "volume=0.0",
44
+ "-c:v", "copy",
45
+ "-y", muted_output_filepath
46
+ ]
47
+ subprocess.run(mute_top_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
48
+
49
+ get_duration_cmd = [
50
+ "ffprobe",
51
+ "-v", "error",
52
+ "-show_entries", "format=duration",
53
+ "-of", "default=noprint_wrappers=1:nokey=1",
54
+ bottom_video_path
55
+ ]
56
+ duration = float(subprocess.check_output(get_duration_cmd, universal_newlines=True).strip())
57
+
58
+ trim_top_cmd = [
59
+ "ffmpeg",
60
+ "-i", muted_output_filepath,
61
+ "-t", str(duration),
62
+ "-c:v", "copy",
63
+ "-y", trimmed_output_filepath
64
+ ]
65
+
66
+ subprocess.run(trim_top_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
67
+
68
+ return trimmed_output_filepath
69
+
70
+
71
+ def bottom_video_operations(bottom_video_path):
72
+ bottom_video_height = int(subprocess.check_output([
73
+ "ffprobe",
74
+ "-v", "error",
75
+ "-select_streams", "v:0",
76
+ "-show_entries", "stream=height",
77
+ "-of", "csv=s=x:p=0",
78
+ bottom_video_path
79
+ ], universal_newlines=True).strip())
80
+
81
+ bottom_video_width = int(subprocess.check_output([
82
+ "ffprobe",
83
+ "-v", "error",
84
+ "-select_streams", "v:0",
85
+ "-show_entries", "stream=width",
86
+ "-of", "csv=s=x:p=0",
87
+ bottom_video_path
88
+ ], universal_newlines=True).strip())
89
+
90
+ return bottom_video_width, bottom_video_height
91
+
92
+ def combination_video(trimmed_top_video_path, bottom_video_path, bottom_video_width, bottom_video_height, output_directory):
93
+ combined_output_filepath = os.path.join(output_directory, 'top_video_muted_trimmed_combined.mp4')
94
+
95
+ combine_cmd = [
96
+ "ffmpeg",
97
+ "-i", trimmed_top_video_path,
98
+ "-i", bottom_video_path,
99
+ "-filter_complex", f"[0:v]scale={bottom_video_width}:{bottom_video_height},setsar=1[main];[1:v]scale={bottom_video_width}:{bottom_video_height},setsar=1[bottom_scaled];[main][bottom_scaled]vstack=inputs=2[v];[0:a][1:a]amix=inputs=2[a]",
100
+ "-map", "[v]",
101
+ "-map", "[a]",
102
+ "-y", combined_output_filepath
103
+ ]
104
+
105
+ subprocess.run(combine_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
106
+
107
+ return combined_output_filepath
108
+
109
+
110
+ def add_subtitle(combined_video_path, subtitle_file, customization_options, output_directory):
111
+ subtitle_output_filepath = os.path.join(output_directory, 'combined_subtitle.mp4')
112
+
113
+ subtitle_cmd = [
114
+ "ffmpeg",
115
+ "-i", combined_video_path,
116
+ "-vf", f"subtitles={subtitle_file}:force_style='Fontsize={customization_options['font_size']},Fontname={customization_options['font_family']},BorderColor=black@{customization_options['border_size']},PrimaryColour={customization_options['font_color']}'",
117
+ "-c:a", "aac",
118
+ "-c:v", "libx264",
119
+ "-y", subtitle_output_filepath,
120
+ ]
121
+
122
+ subprocess.run(subtitle_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
123
+
124
+ return subtitle_output_filepath
125
+
126
+ def add_text_to_video(subtitle_output_filepath, customization_options, output_directory):
127
+ result_video_path = os.path.join(output_directory, 'combined_subtitle_text.mp4')
128
+
129
+ cmd = [
130
+ 'ffmpeg',
131
+ '-i', subtitle_output_filepath,
132
+ '-vf', f'drawtext=text=\'{customization_options["credit"]}\':fontcolor=white:fontsize={customization_options["credit_size"]}:box=1:[email protected]:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2',
133
+ '-codec:a', 'copy',
134
+ "-y", result_video_path
135
+ ]
136
+
137
+ subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
138
+
139
+ return result_video_path
140
+
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ openai-whisper
2
+ moviepy
3
+ setuptools
4
+ torch
5
+ imageio["ffmpeg"]
6
+ uvicorn
7
+ fastapi
8
+ pydantic
9
+ python-multipart
10
+ whisper-timestamped
11
+ pyttsx3
12
+ requests
13
+ beautifulsoup4
14
+ httpx
server.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ import uvicorn
4
+ from endpoints import subway_router, minecraft_router, basic_router, rumble_router, video_editor
5
+
6
+ app = FastAPI()
7
+
8
+ # Add CORS middleware
9
+ app.add_middleware(
10
+ CORSMiddleware,
11
+ allow_origins=["*"],
12
+ allow_methods=["*"],
13
+ allow_headers=["*"]
14
+ )
15
+
16
+ app.include_router(subway_router, prefix="/generate-subtitles")
17
+ app.include_router(minecraft_router, prefix="/generate-subtitles")
18
+ app.include_router(basic_router, prefix="/generate-subtitles")
19
+ app.include_router(rumble_router, prefix="/generate-subtitles")
20
+ app.include_router(video_editor, prefix="/video-editor")
21
+
22
+ if __name__ == "__main__":
23
+ uvicorn.run("server:app", host="localhost", port=8000, reload=True)
utils/create_subtitles.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def create_subtitles(transcription, subtitle_file_path):
2
+ def convert_to_srt_time(time):
3
+ milliseconds = int((time - int(time)) * 1000)
4
+ minutes, seconds = divmod(int(time), 60)
5
+ hours, minutes = divmod(minutes, 60)
6
+ return f"{hours:02d}:{minutes:02d}:{seconds:02d},{milliseconds:03d}"
7
+
8
+
9
+ with open(subtitle_file_path, "w") as subtitle_file:
10
+ for i, segments in enumerate(transcription['segments'], start=1):
11
+ for j, words in enumerate(segments['words'], start=1):
12
+ start_time = convert_to_srt_time(words['start'])
13
+ end_time = convert_to_srt_time(words['end'])
14
+
15
+ subtitle_line = f"{i}.{j}\n{start_time} --> {end_time}\n{words['text']}\n\n"
16
+
17
+ subtitle_file.write(subtitle_line)
18
+
19
+ def hex_to_ffmpeg_color(hex_color):
20
+ # Remove '#' if present
21
+ hex_color = hex_color.lstrip('#')
22
+
23
+ # Convert hex to FFmpeg color format
24
+ ffmpeg_color = int(hex_color, 16)
25
+ return f'0x{ffmpeg_color:06x}'
utils/text_to_speech.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import pyttsx3
2
+
3
+ def text_to_speech(text, audio_file_path):
4
+ engine = pyttsx3.init()
5
+ engine.save_to_file(text, audio_file_path)
6
+ engine.runAndWait()
utils/transcribe_audio.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import whisper_timestamped as whisper
2
+
3
+ def transcribe_audio(audio_file):
4
+ audio = whisper.load_audio(audio_file)
5
+ model = whisper.load_model("base")
6
+ result = whisper.transcribe(model, audio)
7
+ return result
utils/video_to_audio.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from moviepy.editor import VideoFileClip
2
+
3
+ def extract_audio(input_video, output_audio):
4
+ try:
5
+ video_clip = VideoFileClip(input_video)
6
+ audio_clip = video_clip.audio
7
+
8
+ if audio_clip:
9
+ audio_clip.write_audiofile(output_audio)
10
+
11
+ video_clip.close()
12
+ audio_clip.close()
13
+ except Exception as e:
14
+ print(f"Error extracting audio: {e}")