bened commited on
Commit
e3bf3d5
·
verified ·
1 Parent(s): 9ea1f4d

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +216 -0
  2. app_utils.py +136 -0
  3. docker-compose.yml +44 -0
Dockerfile ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Base image
2
+ FROM python:3.9-slim
3
+
4
+ # Install system dependencies, build tools, and libraries
5
+ RUN apt-get update && apt-get install -y --no-install-recommends \
6
+ ca-certificates \
7
+ wget \
8
+ tar \
9
+ xz-utils \
10
+ fonts-liberation \
11
+ fontconfig \
12
+ build-essential \
13
+ yasm \
14
+ cmake \
15
+ meson \
16
+ ninja-build \
17
+ nasm \
18
+ libssl-dev \
19
+ libvpx-dev \
20
+ libx264-dev \
21
+ libx265-dev \
22
+ libnuma-dev \
23
+ libmp3lame-dev \
24
+ libopus-dev \
25
+ libvorbis-dev \
26
+ libtheora-dev \
27
+ libspeex-dev \
28
+ libfreetype6-dev \
29
+ libfontconfig1-dev \
30
+ libgnutls28-dev \
31
+ libaom-dev \
32
+ libdav1d-dev \
33
+ librav1e-dev \
34
+ libsvtav1-dev \
35
+ libzimg-dev \
36
+ libwebp-dev \
37
+ git \
38
+ pkg-config \
39
+ autoconf \
40
+ automake \
41
+ libtool \
42
+ libfribidi-dev \
43
+ libharfbuzz-dev \
44
+ libnss3 \
45
+ libatk1.0-0 \
46
+ libatk-bridge2.0-0 \
47
+ libcups2 \
48
+ libxcomposite1 \
49
+ libxrandr2 \
50
+ libxdamage1 \
51
+ libgbm1 \
52
+ libasound2 \
53
+ libpangocairo-1.0-0 \
54
+ libpangoft2-1.0-0 \
55
+ libgtk-3-0 \
56
+ && rm -rf /var/lib/apt/lists/*
57
+
58
+ # Install SRT from source (latest version using cmake)
59
+ RUN git clone https://github.com/Haivision/srt.git && \
60
+ cd srt && \
61
+ mkdir build && cd build && \
62
+ cmake .. && \
63
+ make -j$(nproc) && \
64
+ make install && \
65
+ cd ../.. && rm -rf srt
66
+
67
+ # Install SVT-AV1 from source
68
+ RUN git clone https://gitlab.com/AOMediaCodec/SVT-AV1.git && \
69
+ cd SVT-AV1 && \
70
+ git checkout v0.9.0 && \
71
+ cd Build && \
72
+ cmake .. && \
73
+ make -j$(nproc) && \
74
+ make install && \
75
+ cd ../.. && rm -rf SVT-AV1
76
+
77
+ # Install libvmaf from source
78
+ RUN git clone https://github.com/Netflix/vmaf.git && \
79
+ cd vmaf/libvmaf && \
80
+ meson build --buildtype release && \
81
+ ninja -C build && \
82
+ ninja -C build install && \
83
+ cd ../.. && rm -rf vmaf && \
84
+ ldconfig # Update the dynamic linker cache
85
+
86
+ # Manually build and install fdk-aac (since it is not available via apt-get)
87
+ RUN git clone https://github.com/mstorsjo/fdk-aac && \
88
+ cd fdk-aac && \
89
+ autoreconf -fiv && \
90
+ ./configure && \
91
+ make -j$(nproc) && \
92
+ make install && \
93
+ cd .. && rm -rf fdk-aac
94
+
95
+ # Install libunibreak (required for ASS_FEATURE_WRAP_UNICODE)
96
+ RUN git clone https://github.com/adah1972/libunibreak.git && \
97
+ cd libunibreak && \
98
+ ./autogen.sh && \
99
+ ./configure && \
100
+ make -j$(nproc) && \
101
+ make install && \
102
+ ldconfig && \
103
+ cd .. && rm -rf libunibreak
104
+
105
+ # Build and install libass with libunibreak support and ASS_FEATURE_WRAP_UNICODE enabled
106
+ RUN git clone https://github.com/libass/libass.git && \
107
+ cd libass && \
108
+ autoreconf -i && \
109
+ ./configure --enable-libunibreak || { cat config.log; exit 1; } && \
110
+ mkdir -p /app && echo "Config log located at: /app/config.log" && cp config.log /app/config.log && \
111
+ make -j$(nproc) || { echo "Libass build failed"; exit 1; } && \
112
+ make install && \
113
+ ldconfig && \
114
+ cd .. && rm -rf libass
115
+
116
+ # Build and install FFmpeg with all required features
117
+ RUN git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg && \
118
+ cd ffmpeg && \
119
+ git checkout n7.0.2 && \
120
+ PKG_CONFIG_PATH="/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/local/lib/pkgconfig" \
121
+ CFLAGS="-I/usr/include/freetype2" \
122
+ LDFLAGS="-L/usr/lib/x86_64-linux-gnu" \
123
+ ./configure --prefix=/usr/local \
124
+ --enable-gpl \
125
+ --enable-pthreads \
126
+ --enable-neon \
127
+ --enable-libaom \
128
+ --enable-libdav1d \
129
+ --enable-librav1e \
130
+ --enable-libsvtav1 \
131
+ --enable-libvmaf \
132
+ --enable-libzimg \
133
+ --enable-libx264 \
134
+ --enable-libx265 \
135
+ --enable-libvpx \
136
+ --enable-libwebp \
137
+ --enable-libmp3lame \
138
+ --enable-libopus \
139
+ --enable-libvorbis \
140
+ --enable-libtheora \
141
+ --enable-libspeex \
142
+ --enable-libass \
143
+ --enable-libfreetype \
144
+ --enable-libharfbuzz \
145
+ --enable-fontconfig \
146
+ --enable-libsrt \
147
+ --enable-filter=drawtext \
148
+ --extra-cflags="-I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include" \
149
+ --extra-ldflags="-L/usr/lib/x86_64-linux-gnu -lfreetype -lfontconfig" \
150
+ --enable-gnutls \
151
+ && make -j$(nproc) && \
152
+ make install && \
153
+ cd .. && rm -rf ffmpeg
154
+
155
+ # Add /usr/local/bin to PATH (if not already included)
156
+ ENV PATH="/usr/local/bin:${PATH}"
157
+
158
+ # Copy fonts into the custom fonts directory
159
+ COPY ./fonts /usr/share/fonts/custom
160
+
161
+ # Rebuild the font cache so that fontconfig can see the custom fonts
162
+ RUN fc-cache -f -v
163
+
164
+ # Set work directory
165
+ WORKDIR /app
166
+
167
+ # Set environment variable for Whisper cache
168
+ ENV WHISPER_CACHE_DIR="/app/whisper_cache"
169
+
170
+ # Create cache directory (no need for chown here yet)
171
+ RUN mkdir -p ${WHISPER_CACHE_DIR}
172
+
173
+ # Copy the requirements file first to optimize caching
174
+ COPY requirements.txt .
175
+
176
+ # Install Python dependencies, upgrade pip
177
+ RUN pip install --no-cache-dir --upgrade pip && \
178
+ pip install --no-cache-dir -r requirements.txt && \
179
+ pip install openai-whisper && \
180
+ pip install playwright && \
181
+ pip install jsonschema
182
+
183
+ # Create the appuser
184
+ RUN useradd -m appuser
185
+
186
+ # Give appuser ownership of the /app directory (including whisper_cache)
187
+ RUN chown appuser:appuser /app
188
+
189
+ # Important: Switch to the appuser before downloading the model
190
+ USER appuser
191
+
192
+ RUN python -c "import os; print(os.environ.get('WHISPER_CACHE_DIR')); import whisper; whisper.load_model('base')"
193
+
194
+ # Install Playwright Chromium browser as appuser
195
+ RUN playwright install chromium
196
+
197
+ # Copy the rest of the application code
198
+ COPY . .
199
+
200
+ # Expose the port the app runs on
201
+ EXPOSE 8080
202
+
203
+ # Set environment variables
204
+ ENV PYTHONUNBUFFERED=1
205
+
206
+ RUN echo '#!/bin/bash\n\
207
+ gunicorn --bind 0.0.0.0:8080 \
208
+ --workers ${GUNICORN_WORKERS:-2} \
209
+ --timeout ${GUNICORN_TIMEOUT:-300} \
210
+ --worker-class sync \
211
+ --keep-alive 80 \
212
+ app:app' > /app/run_gunicorn.sh && \
213
+ chmod +x /app/run_gunicorn.sh
214
+
215
+ # Run the shell script
216
+ CMD ["/app/run_gunicorn.sh"]
app_utils.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2025 Stephen G. Pope
2
+ #
3
+ # This program is free software; you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation; either version 2 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License along
14
+ # with this program; if not, write to the Free Software Foundation, Inc.,
15
+ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16
+
17
+
18
+
19
+ from flask import request, jsonify, current_app
20
+ from functools import wraps
21
+ import jsonschema
22
+ import os
23
+ import json
24
+ import time
25
+ from config import LOCAL_STORAGE_PATH
26
+
27
+ def validate_payload(schema):
28
+ def decorator(f):
29
+ @wraps(f)
30
+ def decorated_function(*args, **kwargs):
31
+ if not request.json:
32
+ return jsonify({"message": "Missing JSON in request"}), 400
33
+ try:
34
+ jsonschema.validate(instance=request.json, schema=schema)
35
+ except jsonschema.exceptions.ValidationError as validation_error:
36
+ return jsonify({"message": f"Invalid payload: {validation_error.message}"}), 400
37
+
38
+ return f(*args, **kwargs)
39
+ return decorated_function
40
+ return decorator
41
+
42
+ def log_job_status(job_id, data):
43
+ """
44
+ Log job status to a file in the STORAGE_PATH/jobs folder
45
+
46
+ Args:
47
+ job_id (str): The unique job ID
48
+ data (dict): Data to write to the log file
49
+ """
50
+ jobs_dir = os.path.join(LOCAL_STORAGE_PATH, 'jobs')
51
+
52
+ # Create jobs directory if it doesn't exist
53
+ if not os.path.exists(jobs_dir):
54
+ os.makedirs(jobs_dir, exist_ok=True)
55
+
56
+ # Create or update the job log file
57
+ job_file = os.path.join(jobs_dir, f"{job_id}.json")
58
+
59
+ # Write data directly to file
60
+ with open(job_file, 'w') as f:
61
+ json.dump(data, f, indent=2)
62
+
63
+ def queue_task_wrapper(bypass_queue=False):
64
+ def decorator(f):
65
+ def wrapper(*args, **kwargs):
66
+ return current_app.queue_task(bypass_queue=bypass_queue)(f)(*args, **kwargs)
67
+ return wrapper
68
+ return decorator
69
+
70
+ def discover_and_register_blueprints(app, base_dir='routes'):
71
+ """
72
+ Dynamically discovers and registers all Flask blueprints in the routes directory.
73
+ Recursively searches all subdirectories for Python modules containing Blueprint instances.
74
+
75
+ Args:
76
+ app (Flask): The Flask application instance
77
+ base_dir (str): Base directory to start searching for blueprints (default: 'routes')
78
+ """
79
+ import importlib
80
+ import pkgutil
81
+ import inspect
82
+ import sys
83
+ import os
84
+ from flask import Blueprint
85
+ import logging
86
+ import glob
87
+
88
+ logger = logging.getLogger(__name__)
89
+ logger.info(f"Discovering blueprints in {base_dir}")
90
+
91
+ # Add the current working directory to sys.path if it's not already there
92
+ cwd = os.getcwd()
93
+ if cwd not in sys.path:
94
+ sys.path.insert(0, cwd)
95
+
96
+ # Get the absolute path to the base directory
97
+ if not os.path.isabs(base_dir):
98
+ base_dir = os.path.join(cwd, base_dir)
99
+
100
+ registered_blueprints = set()
101
+
102
+ # Find all Python files in the routes directory, including subdirectories
103
+ python_files = glob.glob(os.path.join(base_dir, '**', '*.py'), recursive=True)
104
+ logger.info(f"Found {len(python_files)} Python files in {base_dir}")
105
+
106
+ for file_path in python_files:
107
+ try:
108
+ # Convert file path to import path
109
+ rel_path = os.path.relpath(file_path, cwd)
110
+ # Remove .py extension
111
+ module_path = os.path.splitext(rel_path)[0]
112
+ # Convert path separators to dots for import
113
+ module_path = module_path.replace(os.path.sep, '.')
114
+
115
+ # Skip __init__.py files
116
+ if module_path.endswith('__init__'):
117
+ continue
118
+
119
+ #logger.info(f"Attempting to import module: {module_path}")
120
+
121
+ # Import the module
122
+ module = importlib.import_module(module_path)
123
+
124
+ # Find all Blueprint instances in the module
125
+ for name, obj in inspect.getmembers(module):
126
+ if isinstance(obj, Blueprint) and obj not in registered_blueprints:
127
+ pid = os.getpid()
128
+ logger.info(f"PID {pid} Registering: {module_path}")
129
+ app.register_blueprint(obj)
130
+ registered_blueprints.add(obj)
131
+
132
+ except Exception as e:
133
+ logger.error(f"Error importing module {module_path}: {str(e)}")
134
+
135
+ logger.info(f"PID {pid} Registered {len(registered_blueprints)} blueprints")
136
+ return registered_blueprints
docker-compose.yml ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ traefik:
3
+ image: "traefik"
4
+ restart: unless-stopped
5
+ command:
6
+ - "--api=true"
7
+ - "--api.insecure=true"
8
+ - "--providers.docker=true"
9
+ - "--providers.docker.exposedbydefault=false"
10
+ - "--entrypoints.web.address=:80"
11
+ - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
12
+ - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
13
+ - "--entrypoints.websecure.address=:443"
14
+ - "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true"
15
+ - "--certificatesresolvers.mytlschallenge.acme.email=${SSL_EMAIL}"
16
+ - "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json"
17
+ ports:
18
+ - "80:80"
19
+ - "443:443"
20
+ volumes:
21
+ - traefik_data:/letsencrypt
22
+ - /var/run/docker.sock:/var/run/docker.sock:ro
23
+ ncat:
24
+ image: stephengpope/no-code-architects-toolkit:latest
25
+ env_file:
26
+ - .env
27
+ labels:
28
+ - traefik.enable=true
29
+ - traefik.http.routers.ncat.rule=Host(`${APP_DOMAIN}`)
30
+ - traefik.http.routers.ncat.tls=true
31
+ - traefik.http.routers.ncat.entrypoints=web,websecure
32
+ - traefik.http.routers.ncat.tls.certresolver=mytlschallenge
33
+ volumes:
34
+ - storage:/var/www/html/storage/app
35
+ - logs:/var/www/html/storage/logs
36
+ restart: unless-stopped
37
+
38
+ volumes:
39
+ traefik_data:
40
+ driver: local
41
+ storage:
42
+ driver: local
43
+ logs:
44
+ driver: local