File size: 9,247 Bytes
44e02bf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41d4942
44e02bf
 
 
41d4942
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44e02bf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
#!/usr/bin/env python3
import os
import sys
import logging
import tempfile
from flask import Flask, request, jsonify
from transformers import HfArgumentParser

# Add src directory to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from predict import predict
from preprocess import parse_srt_file
from segment import SegmentationArguments
from shared import GeneralArguments
from model import get_model_tokenizer_classifier, InferenceArguments

# Configure logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# Initialize Flask app
app = Flask(__name__)

# Global variables to store model, tokenizer, and classifier
model = None
tokenizer = None
classifier = None
segmentation_args = None
inference_args = None


def parse_srt_content(srt_content):
    """Parse SRT content from string and return words with timestamps"""
    import re
    
    words = []
    
    # Handle different line endings
    srt_content = srt_content.replace('\r\n', '\n').replace('\r', '\n')
    
    # Split by double newline to get individual subtitle blocks
    # Also handle cases where there might be extra whitespace
    blocks = re.split(r'\n\s*\n', srt_content.strip())
    
    logger.debug(f"Found {len(blocks)} subtitle blocks")
    
    for i, block in enumerate(blocks):
        lines = block.strip().split('\n')
        logger.debug(f"Block {i}: {len(lines)} lines")
        
        if len(lines) >= 3:
            # Skip the index line (first line)
            # Parse timestamp line (second line)
            timestamp_line = lines[1].strip()
            logger.debug(f"Timestamp line: {timestamp_line}")
            
            match = re.match(r'(\d{2}):(\d{2}):(\d{2}),(\d{3})\s*-->\s*(\d{2}):(\d{2}):(\d{2}),(\d{3})', timestamp_line)
            if match:
                # Convert to seconds
                start_h, start_m, start_s, start_ms = map(int, match.groups()[:4])
                end_h, end_m, end_s, end_ms = map(int, match.groups()[4:])
                
                start_time = start_h * 3600 + start_m * 60 + start_s + start_ms / 1000
                end_time = end_h * 3600 + end_m * 60 + end_s + end_ms / 1000
                
                # Join all text lines (there might be multiple lines of text)
                text = ' '.join(line.strip() for line in lines[2:] if line.strip())
                logger.debug(f"Text: {text}")
                
                # Split text into words
                text_words = text.split()
                if text_words:
                    # Distribute time equally among words
                    duration = end_time - start_time
                    time_per_word = duration / len(text_words) if len(text_words) > 0 else 0
                    
                    for i, word in enumerate(text_words):
                        word_start = start_time + i * time_per_word
                        word_end = start_time + (i + 1) * time_per_word
                        words.append({
                            'text': word,
                            'start': round(word_start, 3),
                            'end': round(word_end, 3)
                        })
            else:
                logger.debug(f"No match for timestamp: {timestamp_line}")
    
    logger.debug(f"Total words parsed: {len(words)}")
    return words


def initialize_model():
    """Initialize model, tokenizer, and classifier"""
    global model, tokenizer, classifier, segmentation_args, inference_args
    
    logger.info("Initializing model...")
    
    # Parse arguments with defaults
    hf_parser = HfArgumentParser((
        InferenceArguments,
        SegmentationArguments,
        GeneralArguments
    ))

    # Create default arguments
    inference_args = InferenceArguments()
    segmentation_args = SegmentationArguments()
    general_args = GeneralArguments()
    
    # Get model, tokenizer, and classifier
    model, tokenizer, classifier = get_model_tokenizer_classifier(
        inference_args, general_args)
    
    logger.info("Model initialized successfully")


def _extract_srt_and_probability():
    """Extract SRT input and optional min_probability from request payload."""
    srt_content = None
    min_probability = request.args.get('min_probability', type=float)

    if request.content_type == 'text/plain':
        srt_content = request.data.decode('utf-8')
        return srt_content, min_probability

    if request.content_type and 'multipart/form-data' in request.content_type:
        if 'file' in request.files:
            file = request.files['file']
            srt_content = file.read().decode('utf-8')
        elif 'srt' in request.form:
            srt_content = request.form['srt']
        return srt_content, min_probability

    if request.content_type == 'application/json':
        data = request.get_json(silent=True) or {}

        if isinstance(data.get('srt'), str):
            srt_content = data['srt']
        elif isinstance(data.get('inputs'), str):
            # Hugging Face style payload: {"inputs": "<srt text>"}
            srt_content = data['inputs']
        elif isinstance(data.get('inputs'), dict) and isinstance(data['inputs'].get('srt'), str):
            # Alternate HF style payload: {"inputs": {"srt": "..."}}
            srt_content = data['inputs']['srt']

        params = data.get('parameters')
        if min_probability is None and isinstance(params, dict):
            min_probability = params.get('min_probability')

    return srt_content, min_probability


def _run_prediction_request():
    srt_content, min_probability = _extract_srt_and_probability()
    if min_probability is None and inference_args is not None:
        min_probability = inference_args.min_probability

    if not srt_content:
        return jsonify({
            'error': 'No SRT content provided. Send text/plain, multipart/form-data with "file" or "srt", or JSON with "srt"/"inputs".'
        }), 400

    logger.debug(f"Received SRT content (first 200 chars): {srt_content[:200]}")

    words = parse_srt_content(srt_content)
    logger.debug(f"Parsed {len(words)} words from SRT")
    if words:
        logger.debug(f"First word: {words[0]}")
        logger.debug(f"Last word: {words[-1]}")

    if not words:
        return jsonify({'error': 'No words found in SRT content'}), 400

    predictions = predict(
        'srt_input',
        model,
        tokenizer,
        segmentation_args,
        words=words,
        classifier=classifier,
        min_probability=min_probability
    )

    output = {'predictions': []}
    for prediction in predictions:
        pred_data = {
            'time_start': prediction['start'],
            'time_end': prediction['end'],
            'category': prediction.get('category')
        }
        if 'probability' in prediction:
            pred_data['probability'] = prediction['probability']
        output['predictions'].append(pred_data)

    return jsonify(output)


@app.route('/predict', methods=['POST'])
def predict_endpoint():
    """Primary prediction endpoint."""
    try:
        return _run_prediction_request()
    except Exception as e:
        logger.error(f"Error processing request: {str(e)}")
        return jsonify({'error': str(e)}), 500


@app.route('/', methods=['GET', 'POST'])
def root_predict_endpoint():
    """HF-compatible root endpoint for custom-image Inference Endpoints."""
    try:
        if request.method == 'GET':
            return jsonify({
                'name': 'sponsorblock-ml',
                'status': 'ok',
                'usage': {
                    'predict': {
                        'method': 'POST',
                        'path': '/',
                        'content_types': [
                            'application/json',
                            'text/plain',
                            'multipart/form-data'
                        ],
                        'examples': [
                            {'inputs': '<srt text>'},
                            {'srt': '<srt text>'},
                            {'inputs': {'srt': '<srt text>'}}
                        ]
                    },
                    'health': {
                        'method': 'GET',
                        'path': '/health'
                    }
                }
            })
        return _run_prediction_request()
    except Exception as e:
        logger.error(f"Error processing request: {str(e)}")
        return jsonify({'error': str(e)}), 500


@app.route('/health', methods=['GET'])
def health_check():
    """Health check endpoint"""
    return jsonify({
        'status': 'healthy',
        'model_loaded': model is not None
    })


# Initialize model when module is imported (for Gunicorn)
initialize_model()

if __name__ == '__main__':
    # Only run Flask dev server if running directly
    # For production, use: gunicorn -b 0.0.0.0:5000 src.predict_server:app
    
    # Get port from environment variable or use default
    port = int(os.environ.get('PORT', 5000))
    
    # Run server
    logger.info(f"Starting development server on port {port}")
    app.run(host='0.0.0.0', port=port, debug=False)