IZERE HIRWA Roger commited on
Commit
242a970
·
1 Parent(s): 134c911
Files changed (4) hide show
  1. Dockerfile +7 -1
  2. app.py +51 -25
  3. static/js/scripts.js +72 -33
  4. templates/index.html +56 -24
Dockerfile CHANGED
@@ -30,10 +30,16 @@ RUN wget -P /app/SadTalker/checkpoints \
30
  # Copy application files
31
  COPY . .
32
 
33
- # Create necessary directories
34
  RUN mkdir -p /app/static/uploads && chmod -R 777 /app/static/uploads
 
 
35
  RUN mkdir -p /app/templates && chmod -R 777 /app/templates
36
 
 
 
 
 
37
  # Add SadTalker to Python path
38
  ENV PYTHONPATH="${PYTHONPATH}:/app/SadTalker/src"
39
 
 
30
  # Copy application files
31
  COPY . .
32
 
33
+ # Create necessary directories with proper structure
34
  RUN mkdir -p /app/static/uploads && chmod -R 777 /app/static/uploads
35
+ RUN mkdir -p /app/static/css && chmod -R 777 /app/static/css
36
+ RUN mkdir -p /app/static/js && chmod -R 777 /app/static/js
37
  RUN mkdir -p /app/templates && chmod -R 777 /app/templates
38
 
39
+ # Ensure static files are copied correctly
40
+ COPY static/ /app/static/
41
+ COPY templates/ /app/templates/
42
+
43
  # Add SadTalker to Python path
44
  ENV PYTHONPATH="${PYTHONPATH}:/app/SadTalker/src"
45
 
app.py CHANGED
@@ -2,9 +2,9 @@ import os
2
  import sys
3
  sys.path.append('/app/SadTalker/src')
4
 
5
- from flask import Flask, render_template, request, jsonify
6
 
7
- app = Flask(__name__)
8
 
9
  # Initialize SadTalker with proper import
10
  try:
@@ -30,31 +30,57 @@ def generate():
30
  image = request.files['image']
31
  text = request.form.get('text', '')
32
 
33
- # Save files
34
- img_path = os.path.join('static/uploads', image.filename)
35
- audio_path = os.path.join('static/uploads', 'audio.wav')
36
- output_path = os.path.join('static/uploads', 'output.mp4')
37
 
38
- image.save(img_path)
 
39
 
40
- # Text-to-Speech (using gTTS)
41
- from gtts import gTTS
42
- tts = gTTS(text=text, lang='en')
43
- tts.save(audio_path)
44
-
45
- # Generate video (CPU optimized )
46
- sadtalker.generate(
47
- source_image=img_path,
48
- driven_audio=audio_path,
49
- result_dir='static/uploads',
50
- still=True,
51
- preprocess='crop',
52
- enhancer='none' # Disable for CPU
53
- )
54
-
55
- return jsonify({
56
- "video": f"/static/uploads/{os.path.basename(output_path)}"
57
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
  if __name__ == '__main__':
60
  os.makedirs('static/uploads', exist_ok=True)
 
2
  import sys
3
  sys.path.append('/app/SadTalker/src')
4
 
5
+ from flask import Flask, render_template, request, jsonify, send_from_directory
6
 
7
+ app = Flask(__name__, static_folder='static', static_url_path='/static')
8
 
9
  # Initialize SadTalker with proper import
10
  try:
 
30
  image = request.files['image']
31
  text = request.form.get('text', '')
32
 
33
+ if not text.strip():
34
+ return jsonify({"error": "No text provided"}), 400
 
 
35
 
36
+ if not image.filename:
37
+ return jsonify({"error": "No image selected"}), 400
38
 
39
+ try:
40
+ # Save files
41
+ img_path = os.path.join('static/uploads', image.filename)
42
+ audio_path = os.path.join('static/uploads', 'audio.wav')
43
+ output_path = os.path.join('static/uploads', 'output.mp4')
44
+
45
+ image.save(img_path)
46
+
47
+ # Text-to-Speech (using gTTS)
48
+ from gtts import gTTS
49
+ tts = gTTS(text=text, lang='en')
50
+ tts.save(audio_path)
51
+
52
+ # Generate video (CPU optimized)
53
+ if sadtalker:
54
+ sadtalker.generate(
55
+ source_image=img_path,
56
+ driven_audio=audio_path,
57
+ result_dir='static/uploads',
58
+ still=True,
59
+ preprocess='crop',
60
+ enhancer='none' # Disable for CPU
61
+ )
62
+ else:
63
+ return jsonify({"error": "SadTalker not initialized"}), 500
64
+
65
+ return jsonify({
66
+ "video": f"/static/uploads/{os.path.basename(output_path)}"
67
+ })
68
+ except Exception as e:
69
+ return jsonify({"error": str(e)}), 500
70
+
71
+ # Debug route to check static files
72
+ @app.route('/debug/static')
73
+ def debug_static():
74
+ static_files = []
75
+ for root, dirs, files in os.walk('static'):
76
+ for file in files:
77
+ static_files.append(os.path.join(root, file))
78
+ return jsonify({"static_files": static_files})
79
+
80
+ # Explicit static file route for debugging
81
+ @app.route('/static/<path:filename>')
82
+ def static_files(filename):
83
+ return send_from_directory(app.static_folder, filename)
84
 
85
  if __name__ == '__main__':
86
  os.makedirs('static/uploads', exist_ok=True)
static/js/scripts.js CHANGED
@@ -1,38 +1,77 @@
1
- document.getElementById('avatarForm').addEventListener('submit', async (e) => {
2
- e.preventDefault();
3
-
4
- const formData = new FormData();
5
- formData.append('image', document.getElementById('imageInput').files[0]);
6
- formData.append('text', document.getElementById('textInput').value);
7
 
8
- // Show loading state
9
- const btn = document.querySelector('button[type="submit"]');
10
- btn.disabled = true;
11
- btn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status"></span> Processing...';
 
 
 
 
12
 
13
- try {
14
- const response = await fetch('/generate', {
15
- method: 'POST',
16
- body: formData
17
- });
18
- const data = await response.json();
19
 
20
- // Display result
21
- const video = document.getElementById('outputVideo');
22
- video.src = data.video;
23
- document.getElementById('result').classList.remove('d-none');
24
 
25
- // Set up download
26
- document.getElementById('downloadBtn').onclick = () => {
27
- const a = document.createElement('a');
28
- a.href = data.video;
29
- a.download = 'talking_avatar.mp4';
30
- a.click();
31
- };
32
- } catch (error) {
33
- alert('Error: ' + error.message);
34
- } finally {
35
- btn.disabled = false;
36
- btn.innerHTML = 'Generate Video';
37
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  });
 
1
+ // Debug: Check if script is loaded
2
+ console.log('Scripts.js loaded successfully');
 
 
 
 
3
 
4
+ document.addEventListener('DOMContentLoaded', function() {
5
+ console.log('DOM loaded, initializing form handler');
6
+
7
+ const form = document.getElementById('avatarForm');
8
+ if (!form) {
9
+ console.error('Avatar form not found!');
10
+ return;
11
+ }
12
 
13
+ form.addEventListener('submit', async (e) => {
14
+ e.preventDefault();
15
+ console.log('Form submitted');
 
 
 
16
 
17
+ const imageInput = document.getElementById('imageInput');
18
+ const textInput = document.getElementById('textInput');
 
 
19
 
20
+ if (!imageInput.files[0]) {
21
+ alert('Please select an image');
22
+ return;
23
+ }
24
+
25
+ if (!textInput.value.trim()) {
26
+ alert('Please enter some text');
27
+ return;
28
+ }
29
+
30
+ const formData = new FormData();
31
+ formData.append('image', imageInput.files[0]);
32
+ formData.append('text', textInput.value);
33
+
34
+ // Show loading state
35
+ const btn = document.querySelector('button[type="submit"]');
36
+ btn.disabled = true;
37
+ btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status"></span> Processing...';
38
+
39
+ try {
40
+ console.log('Sending request to /generate');
41
+ const response = await fetch('/generate', {
42
+ method: 'POST',
43
+ body: formData
44
+ });
45
+
46
+ if (!response.ok) {
47
+ throw new Error(`HTTP error! status: ${response.status}`);
48
+ }
49
+
50
+ const data = await response.json();
51
+ console.log('Response received:', data);
52
+
53
+ if (data.error) {
54
+ throw new Error(data.error);
55
+ }
56
+
57
+ // Display result
58
+ const video = document.getElementById('outputVideo');
59
+ video.src = data.video;
60
+ document.getElementById('result').classList.remove('d-none');
61
+
62
+ // Set up download
63
+ document.getElementById('downloadBtn').onclick = () => {
64
+ const a = document.createElement('a');
65
+ a.href = data.video;
66
+ a.download = 'talking_avatar.mp4';
67
+ a.click();
68
+ };
69
+ } catch (error) {
70
+ console.error('Error:', error);
71
+ alert('Error: ' + error.message);
72
+ } finally {
73
+ btn.disabled = false;
74
+ btn.innerHTML = '<i class="fas fa-magic me-2"></i>Generate Video';
75
+ }
76
+ });
77
  });
templates/index.html CHANGED
@@ -2,41 +2,73 @@
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Talking Avatar</title>
7
- <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
8
- <link href="../static/css/styles.css" rel="stylesheet">
 
 
 
 
 
 
9
  </head>
10
  <body>
11
- <div class="container mt-5">
12
- <div class="row">
13
- <div class="col-md-6 mx-auto">
14
- <div class="card shadow">
15
- <div class="card-header bg-primary text-white">
16
- <h3 class="text-center">Make Your Image Talk</h3>
17
  </div>
18
  <div class="card-body">
19
- <form id="avatarForm">
20
- <div class="mb-3">
21
- <label class="form-label">Upload Image</label>
22
- <input type="file" class="form-control" id="imageInput" accept="image/*">
 
 
 
 
 
 
23
  </div>
24
- <div class="mb-3">
25
- <label class="form-label">Text to Speak</label>
26
- <textarea class="form-control" id="textInput" rows="3"></textarea>
 
 
 
27
  </div>
28
- <button type="submit" class="btn btn-primary w-100">Generate Video</button>
 
 
 
 
29
  </form>
30
- <div id="result" class="mt-4 text-center d-none">
31
- <video id="outputVideo" controls class="w-100"></video>
32
- <a id="downloadBtn" class="btn btn-success mt-2">Download Video</a>
33
- </div>
 
 
 
 
 
 
 
 
 
34
  </div>
35
  </div>
36
  </div>
37
  </div>
38
  </div>
39
- <script src="../static/js/script.js"></script>
40
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
 
 
 
 
41
  </body>
42
  </html>
 
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI Talking Avatar Generator</title>
7
+
8
+ <!-- Bootstrap CSS -->
9
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
10
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
11
+ <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@200;300;400;600;700;800;900&display=swap" rel="stylesheet">
12
+
13
+ <!-- Custom CSS -->
14
+ <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
15
  </head>
16
  <body>
17
+ <div class="container py-5">
18
+ <div class="row justify-content-center">
19
+ <div class="col-lg-8">
20
+ <div class="card">
21
+ <div class="card-header text-white">
22
+ <h4 class="mb-0"><i class="fas fa-video me-2"></i>AI Talking Avatar Generator</h4>
23
  </div>
24
  <div class="card-body">
25
+ <form id="avatarForm" enctype="multipart/form-data">
26
+ <!-- Image Upload -->
27
+ <div class="mb-4">
28
+ <label class="form-label">Upload Photo</label>
29
+ <div class="upload-area" onclick="document.getElementById('imageInput').click()">
30
+ <i class="fas fa-cloud-upload-alt"></i>
31
+ <p class="mb-0">Click to upload an image</p>
32
+ <small class="text-muted">Supports JPG, PNG formats</small>
33
+ </div>
34
+ <input type="file" id="imageInput" class="d-none" accept="image/*" required>
35
  </div>
36
+
37
+ <!-- Text Input -->
38
+ <div class="mb-4">
39
+ <label for="textInput" class="form-label">Enter Text</label>
40
+ <textarea id="textInput" class="form-control" rows="4"
41
+ placeholder="Enter the text you want the avatar to speak..." required></textarea>
42
  </div>
43
+
44
+ <!-- Submit Button -->
45
+ <button type="submit" class="btn btn-primary btn-lg w-100">
46
+ <i class="fas fa-magic me-2"></i>Generate Video
47
+ </button>
48
  </form>
49
+ </div>
50
+ </div>
51
+
52
+ <!-- Result Section -->
53
+ <div id="result" class="card mt-4 d-none">
54
+ <div class="card-header">
55
+ <h5 class="mb-0"><i class="fas fa-play-circle me-2"></i>Generated Video</h5>
56
+ </div>
57
+ <div class="card-body text-center">
58
+ <video id="outputVideo" controls class="w-100 mb-3" style="max-height: 400px;"></video>
59
+ <button id="downloadBtn" class="btn btn-success">
60
+ <i class="fas fa-download me-2"></i>Download Video
61
+ </button>
62
  </div>
63
  </div>
64
  </div>
65
  </div>
66
  </div>
67
+
68
+ <!-- Bootstrap JS -->
69
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
70
+
71
+ <!-- Custom JS -->
72
+ <script src="{{ url_for('static', filename='js/scripts.js') }}"></script>
73
  </body>
74
  </html>