zcodex commited on
Commit
407092d
·
verified ·
1 Parent(s): 820ca4f

subtitle-tool2

Browse files
Files changed (3) hide show
  1. packages.txt +1 -0
  2. requirements.txt +3 -0
  3. subtitle_tool.py +215 -0
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ ffmpeg
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ flask
2
+ moviepy==1.0.3
3
+ werkzeug
subtitle_tool.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import subprocess
3
+ from flask import Flask, request, redirect, url_for, send_from_directory, flash, render_template_string
4
+ from werkzeug.utils import secure_filename
5
+ from moviepy.editor import VideoFileClip, TextClip, CompositeVideoClip
6
+ from moviepy.video.tools.subtitles import SubtitlesClip
7
+
8
+ # --- Configuration ---
9
+ UPLOAD_FOLDER = 'uploads'
10
+ ALLOWED_EXTENSIONS = {'mp4', 'mkv', 'avi', 'srt'}
11
+
12
+ app = Flask(__name__)
13
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
14
+ app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 * 1024 # 1 GB max upload size
15
+ app.secret_key = 'super secret key' # Needed for flashing messages
16
+
17
+ # --- HTML Template ---
18
+ # The entire web interface is defined in this string.
19
+ HTML_TEMPLATE = """
20
+ <!DOCTYPE html>
21
+ <html lang="en">
22
+ <head>
23
+ <meta charset="UTF-8">
24
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
25
+ <title>Web Subtitle Tool</title>
26
+ <style>
27
+ body { font-family: 'Helvetica', sans-serif; background-color: #2c3e50; color: #ecf0f1; margin: 0; padding: 40px; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
28
+ .container { background-color: #34495e; padding: 30px; border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); width: 100%; max-width: 700px; }
29
+ h1 { color: #ecf0f1; text-align: center; margin-bottom: 10px; }
30
+ p { color: #bdc3c7; text-align: center; margin-bottom: 30px; }
31
+ form { display: flex; flex-direction: column; gap: 20px; }
32
+ .form-group { display: flex; flex-direction: column; }
33
+ label { margin-bottom: 8px; font-weight: bold; color: #bdc3c7; }
34
+ input[type="file"] { background-color: #2c3e50; color: #ecf0f1; border: 2px dashed #4a627a; border-radius: 5px; padding: 15px; cursor: pointer; transition: background-color 0.3s; }
35
+ input[type="file"]:hover { background-color: #4a627a; }
36
+ .button-group { display: flex; justify-content: center; gap: 20px; margin-top: 10px; }
37
+ button { color: white; font-weight: bold; font-size: 16px; border: none; border-radius: 5px; padding: 15px 30px; cursor: pointer; transition: background-color 0.3s; }
38
+ .btn-extract { background-color: #27ae60; }
39
+ .btn-extract:hover { background-color: #2ecc71; }
40
+ .btn-add { background-color: #e67e22; }
41
+ .btn-add:hover { background-color: #f39c12; }
42
+ .flash-messages { list-style: none; padding: 0; margin-top: 20px; }
43
+ .flash-messages li { padding: 15px; border-radius: 5px; margin-bottom: 10px; text-align: center; }
44
+ .flash-error { background-color: #c0392b; color: white; }
45
+ .flash-success { background-color: #27ae60; color: white; }
46
+ </style>
47
+ </head>
48
+ <body>
49
+ <div class="container">
50
+ <h1>Subtitle Extractor & Adder</h1>
51
+ <p>Upload a video, and optionally an SRT file, to begin.</p>
52
+
53
+ {% with messages = get_flashed_messages(with_categories=true) %}
54
+ {% if messages %}
55
+ <ul class="flash-messages">
56
+ {% for category, message in messages %}
57
+ <li class="flash-{{ category }}">{{ message }}</li>
58
+ {% endfor %}
59
+ </ul>
60
+ {% endif %}
61
+ {% endwith %}
62
+
63
+ <form method="post" enctype="multipart/form-data">
64
+ <div class="form-group">
65
+ <label for="video_file">1. Select Video File (.mp4, .mkv, .avi)</label>
66
+ <input type="file" name="video_file" id="video_file" required>
67
+ </div>
68
+ <div class="form-group">
69
+ <label for="srt_file">2. Select Subtitle File (for adding subtitles)</label>
70
+ <input type="file" name="srt_file" id="srt_file">
71
+ </div>
72
+ <div class="button-group">
73
+ <button type="submit" name="action" value="extract" class="btn-extract">Extract Subtitles</button>
74
+ <button type="submit" name="action" value="add" class="btn-add">Add Subtitles</button>
75
+ </div>
76
+ </form>
77
+ </div>
78
+ </body>
79
+ </html>
80
+ """
81
+
82
+ def allowed_file(filename):
83
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
84
+
85
+ # --- Core Subtitle Functions (Adapted for Web) ---
86
+
87
+ def extract_subtitles_web(video_path):
88
+ """ Extracts subtitles and returns the path to the resulting SRT file. """
89
+ try:
90
+ output_filename = os.path.basename(os.path.splitext(video_path)[0]) + ".srt"
91
+ output_srt_path = os.path.join(app.config['UPLOAD_FOLDER'], output_filename)
92
+
93
+ command = f'ffmpeg -y -i "{video_path}" -map 0:s:0 -c:s srt "{output_srt_path}"'
94
+
95
+ # Use subprocess for better error handling
96
+ result = subprocess.run(command, shell=True, capture_output=True, text=True)
97
+
98
+ if result.returncode != 0:
99
+ if "Subtitle stream" not in result.stderr:
100
+ raise Exception("No subtitle stream found in the video file.")
101
+ else:
102
+ raise Exception(f"FFmpeg command failed. Error: {result.stderr}")
103
+
104
+ return output_filename
105
+ except Exception as e:
106
+ # To see the error in the Flask console
107
+ print(f"Extraction error: {e}")
108
+ return str(e)
109
+
110
+
111
+ def add_subtitles_web(video_path, srt_path):
112
+ """ Adds subtitles and returns the path to the new video file. """
113
+ try:
114
+ video = VideoFileClip(video_path)
115
+ generator = lambda txt: TextClip(txt, font='Arial', fontsize=24, color='white', stroke_color='black', stroke_width=1)
116
+ subtitles = SubtitlesClip(srt_path, generator)
117
+
118
+ final_video = CompositeVideoClip([video, subtitles.set_position(('center', 'bottom'))])
119
+
120
+ output_filename = os.path.basename(os.path.splitext(video_path)[0]) + "_subtitled.mp4"
121
+ output_video_path = os.path.join(app.config['UPLOAD_FOLDER'], output_filename)
122
+
123
+ final_video.write_videofile(output_video_path, codec='libx264', audio_codec='aac')
124
+
125
+ return output_filename
126
+ except Exception as e:
127
+ print(f"Add subtitle error: {e}")
128
+ return str(e)
129
+
130
+
131
+ # --- Flask Routes ---
132
+
133
+ @app.route('/', methods=['GET', 'POST'])
134
+ def upload_file():
135
+ if request.method == 'POST':
136
+ # Check if files are part of the request
137
+ if 'video_file' not in request.files:
138
+ flash('No video file part in the request.', 'error')
139
+ return redirect(request.url)
140
+
141
+ video_file = request.files['video_file']
142
+
143
+ if video_file.filename == '':
144
+ flash('No video file selected.', 'error')
145
+ return redirect(request.url)
146
+
147
+ if video_file and allowed_file(video_file.filename):
148
+ video_filename = secure_filename(video_file.filename)
149
+ video_path = os.path.join(app.config['UPLOAD_FOLDER'], video_filename)
150
+ video_file.save(video_path)
151
+
152
+ action = request.form.get('action')
153
+
154
+ if action == 'extract':
155
+ flash('Extracting subtitles... please wait.', 'success')
156
+ result_file = extract_subtitles_web(video_path)
157
+ if result_file and not "Error" in result_file and not "failed" in result_file and not "found" in result_file:
158
+ return redirect(url_for('download_file', filename=result_file))
159
+ else:
160
+ flash(f'Extraction Failed: {result_file}', 'error')
161
+ return redirect(request.url)
162
+
163
+ elif action == 'add':
164
+ if 'srt_file' not in request.files or request.files['srt_file'].filename == '':
165
+ flash('You must select an SRT subtitle file to add.', 'error')
166
+ return redirect(request.url)
167
+
168
+ srt_file = request.files['srt_file']
169
+ if srt_file and allowed_file(srt_file.filename):
170
+ srt_filename = secure_filename(srt_file.filename)
171
+ srt_path = os.path.join(app.config['UPLOAD_FOLDER'], srt_filename)
172
+ srt_file.save(srt_path)
173
+
174
+ flash('Adding subtitles... this can take a very long time.', 'success')
175
+ result_file = add_subtitles_web(video_path, srt_path)
176
+
177
+ if result_file and not "Error" in result_file:
178
+ return redirect(url_for('download_file', filename=result_file))
179
+ else:
180
+ flash(f'Adding Subtitles Failed: {result_file}', 'error')
181
+ return redirect(request.url)
182
+ else:
183
+ flash('Invalid subtitle file type.', 'error')
184
+ return redirect(request.url)
185
+
186
+ return render_template_string(HTML_TEMPLATE)
187
+
188
+
189
+ @app.route('/uploads/<filename>')
190
+ def download_file(filename):
191
+ return send_from_directory(app.config['UPLOAD_FOLDER'], filename, as_attachment=True)
192
+
193
+
194
+ # --- Main execution block ---
195
+
196
+ if __name__ == '__main__':
197
+ # Create the upload folder if it doesn't exist
198
+ if not os.path.exists(UPLOAD_FOLDER):
199
+ os.makedirs(UPLOAD_FOLDER)
200
+
201
+ # Check for FFmpeg - this is not needed for Hugging Face deployment
202
+ # as we handle it in packages.txt, but good for local testing.
203
+ try:
204
+ subprocess.run(['ffmpeg', '-version'], check=True, capture_output=True)
205
+ print("FFmpeg found. Starting server...")
206
+ # When running on Hugging Face, they control the host and port.
207
+ # For local running, we use the old command.
208
+ # The 'app.run' command is often not needed in production servers.
209
+ # However, for simple Hugging Face deployment, this works.
210
+ app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 7860)))
211
+ except (subprocess.CalledProcessError, FileNotFoundError):
212
+ print("\n--- FATAL ERROR ---")
213
+ print("FFmpeg could not be found. Please install it and ensure it's in your system's PATH.")
214
+ print("You can download it from ffmpeg.org.")
215
+ input("Press Enter to exit.")