AI‑Powered Video Clip Generator Clone

A minimal Python‑SQLite clone of AutoClip in 45 minutes

Welcome to another official UVF IT guide! In this quick‑fire tutorial we’ll build a tiny version of AutoClip – an AI‑assisted video clip generator that extracts highlights from long videos using subtitles.

You’ll end up with a Flask app that accepts a video file and an optional SRT, runs a simple keyword‑density model to pick highlight timestamps, and serves the clipped MP4s – all on your local machine, no paid APIs required.

Before you start

Clone Architecture
LOCAL RESOURCES stores metadata calls for clipping Flask API + UI SQLite DB (clips metadata) ffmpeg (video processing)
1

Phase 1: Scaffold the project

Create a fresh virtual environment so our dependencies stay isolated from any system Python packages. Then install Flask, which will serve the web UI and API, and the SQLite driver (the built‑in sqlite3 module works, but we also install pysqlite3 for a nicer API). Finally, lay out a minimal folder tree: a place for the Flask app, a models.py for the database schema, and a static folder for uploaded videos.

The scaffold gives us a clean starting point. All later code will live inside the src package, making imports straightforward and keeping the repository tidy.

python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install Flask pysqlite3
mkdir -p src/static src/templates
touch src/__init__.py src/app.py src/models.py
2

Phase 2: Data model – SQLite clip table

We need a single table to remember each generated clip: the original video filename, start and end seconds, and a short AI‑generated title. Using pysqlite3 we can create the table if it doesn’t exist and expose a tiny helper to insert new rows.

Keeping the schema in models.py means the rest of the code can import get_db() and add_clip() without worrying about raw SQL strings. This keeps the project modular and easy to extend later.

src/models.py

import os
import sqlite3
from pathlib import Path

DB_PATH = Path(__file__).parent / "clips.db"

def get_db():
    """Return a connection to the SQLite DB, creating the file if needed."""
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn

def init_db():
    """Create the clips table if it doesn't already exist."""
    with get_db() as conn:
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS clips (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                video_filename TEXT NOT NULL,
                start_sec REAL NOT NULL,
                end_sec REAL NOT NULL,
                title TEXT NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
            """
        )
        conn.commit()

def add_clip(video_filename: str, start_sec: float, end_sec: float, title: str) -> int:
    """Insert a new clip record and return its new ID."""
    with get_db() as conn:
        cur = conn.execute(
            "INSERT INTO clips (video_filename, start_sec, end_sec, title) VALUES (?,?,?,?)",
            (video_filename, start_sec, end_sec, title),
        )
        conn.commit()
        return cur.lastrowid

# Initialise the DB when this module is first imported
if not DB_PATH.exists():
    init_db()
3

Phase 3: Core logic – simple highlight extractor

In this step we turn raw subtitles into actionable highlight windows. We read an optional SRT file, split it into 10‑second buckets, and count how often each word appears inside each bucket. The two buckets with the highest word‑frequency scores become our highlight candidates – a lightweight TF‑IDF‑like heuristic that works without any heavy ML libraries.

The extractor returns a list of (start_seconds, end_seconds) tuples that the rest of the pipeline can feed into ffmpeg. Keeping the logic pure Python makes it easy to test and swap out later if you want a smarter model.

extractor.py

import re
import os
from collections import Counter, defaultdict

def _load_srt(srt_path):
    """Read an SRT file and return a list of (start, end, text) tuples in seconds."""
    if not os.path.isfile(srt_path):
        return []
    pattern = re.compile(r"(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})\n(.+?)(?=\n\n|\Z)", re.DOTALL)
    def _ts(t):
        h,m,s = t.split(':')
        s,ms = s.split(',')
        return int(h)*3600 + int(m)*60 + int(s) + int(ms)/1000.0
    entries = []
    with open(srt_path, 'r', encoding='utf-8') as f:
        content = f.read().strip()
    for match in pattern.finditer(content):
        start = _ts(match.group(1))
        end = _ts(match.group(2))
        text = match.group(3).replace('\n', ' ')
        entries.append((start, end, text))
    return entries

def _tokenize(text):
    return re.findall(r"\b\w+\b", text.lower())

def extract_highlights(video_path, srt_path=None, window=10, top_n=2):
    """Return a list of (start, end) seconds for the most "interesting" windows.
    If no SRT is supplied we fall back to the first `top_n` windows of the video.
    """
    # Load subtitles; if missing, create empty placeholder list
    subs = _load_srt(srt_path) if srt_path else []
    # Determine video duration (fallback to 60s if unknown)
    try:
        import subprocess, json
        probe = subprocess.check_output([
            'ffprobe', '-v', 'error', '-show_entries', 'format=duration',
            '-of', 'json', video_path
        ])
        duration = float(json.loads(probe)['format']['duration'])
    except Exception:
        duration = 60.0
    # Build buckets
    buckets = defaultdict(list)  # start_sec -> list of words
    for start, end, text in subs:
        bucket_start = int(start // window) * window
        buckets[bucket_start].extend(_tokenize(text))
    # Score each bucket by raw word frequency
    scores = []
    for b_start in range(0, int(duration), window):
        words = buckets.get(b_start, [])
        freq = Counter(words)
        score = sum(freq.values())  # simple count
        scores.append((score, b_start))
    # Pick top windows
    top = sorted(scores, reverse=True)[:top_n]
    highlights = []
    for _, b_start in top:
        highlights.append((b_start, min(b_start + window, duration)))
    return highlights
4

Phase 4: API & UI – upload, process, serve clips

Now we glue everything together with a tiny Flask app. The root route shows a simple HTML form where you can drop a video and an optional SRT file. When the form is submitted we store the uploads, call extract_highlights to get the interesting segments, and invoke ffmpeg to slice those segments into separate MP4 files.

Each generated clip is recorded in a SQLite database (created earlier in the scaffold) so the results page can list them with download links. The UI stays minimal – just an unordered list – but it proves the end‑to‑end flow works from upload to playback.

app.py

import os
import sqlite3
from flask import Flask, request, redirect, url_for, send_from_directory, render_template_string
from werkzeug.utils import secure_filename
from extractor import extract_highlights
import subprocess

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['CLIP_FOLDER'] = 'clips'
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs(app.config['CLIP_FOLDER'], exist_ok=True)
DB_PATH = 'clips.db'

# Simple DB helper
def init_db():
    with sqlite3.connect(DB_PATH) as conn:
        conn.execute('''CREATE TABLE IF NOT EXISTS clips (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            video TEXT,
            start REAL,
            end REAL,
            clip_path TEXT
        )''')
init_db()

HTML_FORM = '''
<!doctype html><title>AutoClip Clone</title>
<h1>Upload video (and optional .srt)</h1>
<form method=post enctype=multipart/form-data>
  <input type=file name=video required><br><br>
  <input type=file name=srt><br><br>
  <input type=submit value=Upload>
</form>
{% if clips %}<h2>Generated Clips</h2><ul>{% for c in clips %}<li><a href="{{url_for('download_clip', filename=c['clip_name'])}}">Clip {{c['id']}} ({{c['start']}}‑{{c['end']}}s)</a></li>{% endfor %}</ul>{% endif %}
''' 

@app.route('/', methods=['GET', 'POST'])
def upload():
    clips = []
    if request.method == 'POST':
        video_file = request.files['video']
        srt_file = request.files.get('srt')
        if not video_file:
            return 'No video uploaded', 400
        video_name = secure_filename(video_file.filename)
        video_path = os.path.join(app.config['UPLOAD_FOLDER'], video_name)
        video_file.save(video_path)
        srt_path = None
        if srt_file and srt_file.filename:
            srt_name = secure_filename(srt_file.filename)
            srt_path = os.path.join(app.config['UPLOAD_FOLDER'], srt_name)
            srt_file.save(srt_path)
        # Extract highlight windows
        highlights = extract_highlights(video_path, srt_path)
        # Generate clips via ffmpeg
        for start, end in highlights:
            clip_name = f"clip_{int(start)}_{int(end)}.mp4"
            clip_path = os.path.join(app.config['CLIP_FOLDER'], clip_name)
            subprocess.run([
                'ffmpeg', '-y', '-i', video_path,
                '-ss', str(start), '-to', str(end),
                '-c', 'copy', clip_path
            ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            # Store metadata
            with sqlite3.connect(DB_PATH) as conn:
                cur = conn.execute('INSERT INTO clips (video, start, end, clip_path) VALUES (?,?,?,?)',
                                   (video_name, start, end, clip_name))
                clip_id = cur.lastrowid
            clips.append({'id': clip_id, 'clip_name': clip_name, 'start': start, 'end': end})
    else:
        # GET – show any existing clips for demo purposes
        with sqlite3.connect(DB_PATH) as conn:
            rows = conn.execute('SELECT id, clip_path, start, end FROM clips').fetchall()
        clips = [{'id': r[0], 'clip_name': r[1], 'start': r[2], 'end': r[3]} for r in rows]
    return render_template_string(HTML_FORM, clips=clips)

@app.route('/clips/<filename>')
def download_clip(filename):
    return send_from_directory(app.config['CLIP_FOLDER'], filename, as_attachment=True)

if __name__ == '__main__':
    app.run(debug=True)
5

Phase 5: Run & verify locally

With the code in place, start the Flask development server and point your browser at the root URL. The upload page will appear; choose a short MP4 (and an SRT if you have one) and hit Upload. After a few seconds ffmpeg will slice the two highlight clips and the page will refresh showing download links.

You can click each link to confirm the MP4 files were created and play correctly. This completes the minimal clone – you now have a working pipeline from raw video to AI‑style highlights without any external ML services.

# Ensure dependencies are installed
pip install flask werkzeug
# ffmpeg must be on PATH – on Ubuntu you can install with:
# sudo apt-get install ffmpeg
# Start the app
python app.py

Verifying the Stack

CheckCommand / Why it matters
Server starts without errorObserve "Running on http://127.0.0.1:5000/" in terminal
Upload page loads in browserNavigate to http://127.0.0.1:5000/ and see the upload form
Clips appear after processingAfter submitting a video, the results page lists downloadable MP4 links

You did it!

Your minimal AutoClip clone is up and running. Experiment by tweaking the window size or swapping the TF‑IDF scorer for a small transformer model – the sky’s the limit!