I have a flask videohosting server. I will tell you what i want from you.
This is the main directory:
app.cfg  data  db  main2.py  main.py  Procfile  readme.md  requirements.txt  run.sh  srv.sh  static  templates
this is the db:
db/hosting.sql
this is the templates folder:
about.html      admin_users.html  edit_profile.html  error.html     feed.html   login.html   register.html  upload_avatar.html  videoback.html
add_video.html  base.html         edit_video.html    favorite.html  index.html  people.html  search.html    user.html           video.html
this is the data folder:
__all_models.py  db_session.py         edit_profile_form.py  login_form.py  register_form.py           users_arg_parser.py  users_resource.py  videos.py
avatar_form.py   dislikes_resource.py  likes_resource.py     __pycache__    subscriptions_resource.py  users.py             video_form.py      videos_resource.py

This is my app2.py(i use it)
import os
import json
from passlib.context import CryptContext
from PIL import Image
from flask import Flask, render_template, url_for, redirect, request, send_from_directory, flash
from flask_login import LoginManager, login_user, current_user, logout_user
from flask_restful import Api
from data import db_session
from data.videos import Video
from data.users import User
from data.login_form import LoginForm
from data.register_form import RegisterForm
from data.video_form import VideoForm
from data.users_resource import UsersResource, UserListResource
from data.videos_resource import VideosResource, VideoListResource
from data.likes_resource import LikeResource, NotLikeResource
from data.subscriptions_resource import FollowResource, NotFollowResource
from werkzeug.utils import secure_filename
from data.avatar_form import AvatarForm
app = Flask(__name__)
app.config['SECRET_KEY'] = 'yandexlyceum_secret_key'

api = Api(app)

login_manager = LoginManager()
login_manager.init_app(app)

pwd_context = CryptContext(
    schemes=["pbkdf2_sha256"],
    default="pbkdf2_sha256",
    pbkdf2_sha256__default_rounds=30000
)


# Set the folder to store avatars
AVATAR_UPLOAD_FOLDER = os.path.join(app.root_path, 'static', 'avatars')
# Allowed extensions
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}

app.config['AVATAR_UPLOAD_FOLDER'] = AVATAR_UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 2MB max upload sizes

@app.route('/favicon.ico')
def favicon():
    """Возвращает кастомную иконку страницы в браузере"""
    return send_from_directory(os.path.join(app.root_path, 'static/img'),
                               'vhs.ico', mimetype='image/vnd.microsoft.icon')


def load_user(user_id):
    """Loads the user by ID."""
    return db_sess.query(User).get(user_id)

@app.route('/upload_avatar', methods=['GET', 'POST'])
def upload_avatar():
    """Allows authenticated users to upload their avatar."""
    form = AvatarForm()
    if form.validate_on_submit():
        avatar = form.avatar.data
        # Secure the filename
        filename = secure_filename(avatar.filename)
        # Create a unique filename using user ID
        file_ext = os.path.splitext(filename)[1].lower()
        avatar_filename = f"user_{current_user.id}{file_ext}"
        avatar_path = os.path.join(app.config['AVATAR_UPLOAD_FOLDER'], avatar_filename)

        # Save the avatar temporarily
        avatar.save(avatar_path)

        # Validate and process the image
        try:
            img = Image.open(avatar_path)
            img = img.convert('RGB')  # Ensure image is in RGB
            img.thumbnail((300, 300))  # Resize to 300x300 pixels
            img.save(avatar_path, optimize=True, quality=85)  # Optimize and save
        except Exception as e:
            os.remove(avatar_path)
            flash('Неправильный формат файла!', 'danger')
            return render_template('upload_avatar.html', form=form)

        # Update user's avatar in the database
        current_user.avatar = avatar_filename
        db_sess.commit()

        flash('Аватар загружен успешно!', 'success')
        return redirect(url_for('user', user_id=current_user.id))
    else:
        if form.errors:
            # Aggregate form errors
            error_messages = '; '.join([' '.join(err) for err in form.errors.values()])
            flash(error_messages, 'danger')
    return render_template('upload_avatar.html', form=form)



@app.errorhandler(403)
def forbidden(e):
    """Возвращает кастомную страницу ошибки 403"""
    params = {
        'title': 'Доступ запрещен',
        'message': 'Похоже, у вас нет доступа к этой странице ¯\\_(ツ)_/¯',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('error.html', **params), 403

def to_int(value):
    try:
        return int(value)
    except ValueError:
        return 0  # or handle as needed

app.jinja_env.filters['to_int'] = to_int

@app.errorhandler(404)
def page_not_found(e):
    """Возвращает кастомную страницу ошибки 404"""
    params = {
        'title': 'Страница не найдена',
        'message': 'Похоже, мы не можем найти нужную вам страницу ¯\\_(ツ)_/¯',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('error.html', **params), 404


@app.errorhandler(405)
def method_not_allowed(e):
    """Возвращает кастомную страницу ошибки 405"""
    params = {
        'title': 'Метод не разрешен',
        'message': 'Похоже, этот метод не разрешен ¯\\_(ツ)_/¯',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('error.html', **params), 405


@app.errorhandler(500)
def internal_server_error(e):
    """Возвращает кастомную страницу ошибки 500"""
    params = {
        'title': 'Ошибка сервера',
        'message': 'Похоже, на сервере произошла непредвиденная ошибка ¯\\_(ツ)_/¯',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('error.html', **params), 500


@app.route('/')
@app.route('/index')
def index():
    """Возвращает домашнюю страницу"""
    all_videos = db_sess.query(Video).all()
    if len(all_videos) > 10:
        last_videos = all_videos[len(all_videos) - 10:][::-1]
    else:
        last_videos = all_videos[::-1]
    best_videos = sorted(all_videos, key=lambda x: -x.likes)[:3]
    underrated_videos = list(filter(lambda x: x.likes == 0, all_videos))
    if len(underrated_videos) > 9:
        underrated_videos = underrated_videos[len(underrated_videos) - 9:][::-1]
    else:
        underrated_videos = underrated_videos[::-1]

    params = {
        'title': 'Video Hosting Service',
        'last_videos': last_videos,
        'best_videos': best_videos,
        'underrated_videos': underrated_videos,
        'user': user,
        'db_sess': db_sess,
        'User': User,
        'Video': Video,
        'authenticated': current_user.is_authenticated,
        'all_videos': all_videos,
        'current_user': current_user
    }
    return render_template('index.html', **params)


@login_manager.user_loader
def load_user(user_id):
    """Возвращает пользователя"""
    return db_sess.query(User).get(user_id)


@app.route('/login', methods=['GET', 'POST'])
def login():
    """Возвращает страницу авторизации и авторизует пользователя"""
    if current_user.is_authenticated:
        return forbidden('')
    form = LoginForm()
    params = {
        'title': 'Авторизация',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    if form.validate_on_submit():
        user = db_sess.query(User).filter(User.login == form.login.data).first()
        if user and pwd_context.verify(form.password.data, user.password):
            login_user(user, remember=form.remember_me.data)
            return redirect('/')
        return render_template('login.html',
                               message='Неправильный логин или пароль',
                               form=form, **params)
    return render_template('login.html', form=form, **params)


@app.route('/register', methods=['GET', 'POST'])
def register():
    """Возвращает страницу регистрации и регистрирует пользователя"""
    if current_user.is_authenticated:
        return forbidden('')
    form = RegisterForm()
    params = {
        'title': 'Регистрация',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    if form.validate_on_submit():
        user = db_sess.query(User).filter(User.login == form.login.data).all()
        if user:
            return render_template('register.html',
                                   message='Пользователь с таким логином уже существует',
                                   form=form, **params)
        if form.password.data != form.repeat_password.data:
            return render_template('register.html',
                                   message='Пароли не совпадают',
                                   form=form, **params)
        user = User()
        user.login = form.login.data
        user.password = pwd_context.hash(form.password.data)
        user.name = form.name.data
        user.surname = form.surname.data
        db_sess.add(user)
        db_sess.commit()
        login_user(user, remember=form.remember_me.data)
        return redirect('/')
    return render_template('register.html', form=form, **params)


@app.route('/logout')
def logout():
    """
    Производит выход пользователя из системы, возвращает домашнюю страницу.
    Если пользователь не авторизован, возвращает страницу ошибки 403
    """
    if current_user.is_authenticated:
        logout_user()
        return redirect('/')
    else:
        return forbidden('')


@app.route('/video/<int:video_id>')
def video(video_id):
    """Возвращает страницу с видео"""
    current_video = db_sess.query(Video).get(video_id)
    if user is None:
        return page_not_found('')
    all_videos = db_sess.query(Video).all()
    if len(all_videos) > 3:
        last_videos = all_videos[len(all_videos) - 3:][::-1]
    else:
        last_videos = all_videos[::-1]
    title = current_video.title
    author_id = current_video.author
    author = db_sess.query(User).get(author_id)
    like = False
    subscription = False
    if current_user.is_authenticated:
        likes = json.loads(current_user.likes)
        subscriptions = json.loads(current_user.subscriptions)
        if video_id in likes:
            like = True
        if author_id in subscriptions:
            subscription = True
    params = {
        'title': f'{title} - {author.name} {author.surname}',
        'current_video': current_video,
        'video_id': video_id,
        'video_src': url_for("static", filename=f'vid/{video_id}.mp4'),
        'author': author,
        'like': like,
        'subscription': subscription,
        'last_videos': last_videos,
        'db_sess': db_sess,
        'User': User,
        'user': user,
        'Video': Video,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user,
        'avatar_url': url_for('static', filename=f'avatars/{author.avatar}'),  # Pass the avatar URL
    }
    return render_template('video.html', **params)


@app.route('/add_video', methods=['GET', 'POST'])
def add_video():
    """
    Возвращает страницу добавления видео если пользователь авторизован,
    иначе доступ запрещен
    """
    if not current_user.is_authenticated:
        return forbidden('')
    form = VideoForm()
    params = {
        'title': 'Добавление видео',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    # Добавляем проверку на блокировку
    if current_user.is_blocked:
        flash('Ваш аккаунт заблокирован и вы не можете загружать видео.', 'danger')
        return render_template('add_video.html', form=form, **params)

    if form.validate_on_submit():
        video_file = request.files['video']
        if not video_file.filename.lower().endswith('.mp4'):
            return render_template('add_video.html',
                                   message='Данный формат видео не поддерживается. '
                                           'Пожалуйста, загрузите видео в формате mp4.',
                                   form=form, **params)
        preview_file = request.files['preview']
        if not preview_file.filename.lower().endswith('.jpg'):
            return render_template('add_video.html',
                                   message='Данный формат обложки не поддерживается. '
                                           'Пожалуйста, загрузите изображение в формате jpg.',
                                   form=form, **params)
        video = Video()
        video.title = form.title.data
        video.description = '<br>'.join(form.description.data.split('\n'))
        video.author = current_user.id
        db_sess.add(video)
        db_sess.commit()
        video_file.save(os.path.join('static/vid', f'{video.id}.mp4'))
        preview_file.save(os.path.join('static/pre', f'{video.id}.jpg'))
        with Image.open(f'static/pre/{video.id}.jpg') as preview_file:
            width, height = preview_file.size
            if width >= height:
                new_width = height * 16 // 9
                delta = (width - new_width) // 2
                preview_file = preview_file.crop((delta, 0, new_width + delta, height))
            else:
                new_height = width * 9 // 16
                delta = (height - new_height) // 2
                preview_file = preview_file.crop((0, delta, width, new_height + delta))
            preview_file.save(f'static/pre/{video.id}.jpg')
        return redirect('/')
    return render_template('add_video.html', form=form, **params)


@app.route('/edit_video/<int:video_id>', methods=['GET', 'POST'])
def edit_video(video_id):
    """
    Возвращает страницу редактирования видео
    если пользователь авторизован и является автором,
    иначе доступ запрщен
    """
    form = VideoForm()
    video = db_sess.query(Video).get(video_id)
    if not current_user.is_authenticated:
        return forbidden('')
    if not current_user.id != video.author:
        return forbidden('')

    params = {
        'title': 'Редактирование видео',
        'video': video,
        'description': '\n'.join(video.description.split('<br>')),
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    if form.validate_on_submit():
        video_file = request.files['video']
        if not video_file.filename.lower().endswith('.mp4'):
            return render_template('edit_video.html',
                                   message='Данный формат видео не поддерживается',
                                   form=form, **params)
        preview_file = request.files['preview']
        if not preview_file.filename.lower().endswith('.jpg'):
            return render_template('edit_video.html',
                                   message='Данный формат обложки не поддерживается',
                                   form=form, **params)
        video.title = form.title.data
        video.description = '<br>'.join(form.description.data.split('\n'))
        db_sess.commit()
        video_file.save(os.path.join('static/vid', f'{video.id}.mp4'))
        preview_file.save(os.path.join('static/pre', f'{video.id}.jpg'))
        with Image.open(f'static/pre/{video.id}.jpg') as preview_file:
            width, height = preview_file.size
            if width >= height:
                new_width = height * 16 // 9
                delta = (width - new_width) // 2
                preview_file = preview_file.crop((delta, 0, new_width + delta, height))
            else:
                new_height = width * 9 // 16
                delta = (height - new_height) // 2
                preview_file = preview_file.crop((0, delta, width, new_height + delta))
            preview_file.save(f'static/pre/{video.id}.jpg')
        return redirect('/')
    return render_template('edit_video.html', form=form, **params)


@app.route('/delete_video/<int:video_id>')
def delete_video(video_id):
    """
    Удаляет видео если пользователь авторизован и является автором,
    иначе доступ запрещен
    """
    video = db_sess.query(Video).get(video_id)
    if not int(current_user.id) == int(video.author):
        print(f"Current user ID: {current_user.id}, Video author ID: {video.author}")  # Debugging line
        return redirect('/')
    db_sess.delete(video)
    db_sess.commit()
    os.remove(os.path.join('static/vid', f'{video.id}.mp4'))
    os.remove(os.path.join('static/pre', f'{video.id}.jpg'))
    return redirect(f'/user/{current_user.id}')


@app.route('/user/<int:user_id>')
def user(user_id):
    """Возвращает страницу выбранного пользователя с его видео"""
    user = db_sess.query(User).get(user_id)
    if user is None:
        return page_not_found('')

    # Получаем все видео пользователя
    videos = db_sess.query(Video).filter(Video.author == user_id).all()[::-1]

    # Вычисляем количество видео
    video_count = len(videos)
    # Альтернативно, для оптимизации можно использовать:
    # video_count = db_sess.query(Video).filter(Video.author == user_id).count()

    subscription = False
    if current_user.is_authenticated:
        subscriptions = json.loads(current_user.subscriptions)
        if user_id in subscriptions:
            subscription = True

    # Пагинация видео (разбивка на группы по 3 видео)
    paginated_videos = [videos[i * 3:i * 3 + 3] for i in range(len(videos) // 3)] + [videos[(len(videos) // 3) * 3:]]

    # Добавляем все необходимые параметры, включая video_count
    params = {
        'title': f'{user.name} {user.surname}',
        'user': user,
        'videos': paginated_videos,
        'subscription': subscription,
        'empty': True if len(videos) == 0 else False,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user,
        'video_count': video_count  # Передаём количество видео
    }
    return render_template('user.html', **params)
def decline_subscription(number):
    """
    Возвращает правильную форму слова "подписчик" в зависимости от числа.
    Например:
        1 -> подписчик
        2 -> подписчика
        5 -> подписчиков
    """
    number = abs(number)  # Убираем знак, если число отрицательное
    last_two = number % 100
    last = number % 10

    if 11 <= last_two <= 14:
        return "подписчиков"
    if last == 1:
        return "подписчик"
    if 2 <= last <= 4:
        return "подписчика"
    return "подписчиков"

# Регистрация фильтра в Jinja2
app.jinja_env.filters['decline_subscription'] = decline_subscription
from data.users import User
from sqlalchemy import update

@app.route('/admin/users', methods=['GET', 'POST'])
def admin_users():
    """Административная страница для управления флажками, блокировками и статусом админа пользователей"""
    if not current_user.is_authenticated or not current_user.is_admin:
        return forbidden('')  # Только администратор имеет доступ

    users = db_sess.query(User).all()

    if request.method == 'POST':
        # Обработка изменений флажков, блокировок и статуса админа
        for user in users:
            if user.id == 1:
                continue  # Не изменять статус администратора

            # Обработка флажков is_checked
            is_checked = request.form.get(f'checked_{user.id}', 'off') == 'on'
            if user.is_checked != is_checked:
                user.is_checked = is_checked
                db_sess.add(user)

            # Обработка блокировок is_blocked
            is_blocked = request.form.get(f'blocked_{user.id}', 'off') == 'on'
            if user.is_blocked != is_blocked:
                user.is_blocked = is_blocked
                db_sess.add(user)

            # Обработка статуса администратора is_admin
            is_admin = request.form.get(f'is_admin_{user.id}', 'off') == 'on'
            if user.is_admin != is_admin:
                user.is_admin = is_admin
                db_sess.add(user)
        db_sess.commit()
        flash('Изменения успешно сохранены.', 'success')
        return redirect(url_for('admin_users'))

    params = {
        'title': 'Администрирование пользователей',
        'users': users,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('admin_users.html', **params)
@app.route('/feed')
def feed():
    """
    Возвращает страницу с лентой видео от людей,
    на которых подписан пользователь.
    Если пользователь не авторизован, доступ запрещен
    """
    if not current_user.is_authenticated:
        return forbidden('')
    subscriptions = json.loads(current_user.subscriptions)
    videos = []
    for i in subscriptions:
        videos += db_sess.query(Video).filter(Video.author == i).all()[::-1][:12]
    new_videos = sorted(videos, key=lambda x: x.datetime)[::-1]
    new_videos = [
        {
            'video': i,
            'author': db_sess.query(User).get(i.author)
        }
        for i in new_videos
    ]
    params = {
        'title': f'Лента',
        'new_videos': [new_videos[i * 3:i * 3 + 3] for i in range(len(new_videos) // 3)] +
                      [new_videos[(len(new_videos) // 3) * 3:]],
        'empty': True if len(new_videos) == 0 else False,
        'subscripted': True if len(subscriptions) != 0 else False,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('feed.html', **params)


@app.route('/favorite')
def favorite():
    """
    Возвращает страницу с любимыми видео пользователя.
    Если пользователь не авторизован, доступ запрещен
    """
    if not current_user.is_authenticated:
        return forbidden('')
    likes = json.loads(current_user.likes)[::-1]
    liked_videos = [
    {
        'video': video,
        'author': db_sess.query(User).get(video.author) if video.author else None
    }
    for i in likes
    if (video := db_sess.query(Video).get(i)) is not None
]
    params = {
        'title': f'Любимое',
        'liked_videos': [liked_videos[i * 3:i * 3 + 3] for i in range(len(liked_videos) // 3)] +
                        [liked_videos[(len(liked_videos) // 3) * 3:]],
        'empty': True if len(liked_videos) == 0 else False,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('favorite.html', **params)


@app.route('/people')
def people():
    """
    Возвращает страницу с пользователями, на которых подписан пользователь.
    Если пользователь не авторизован, доступ запрещен
    """
    if not current_user.is_authenticated:
        return forbidden('')
    subscriptions = json.loads(current_user.subscriptions)
    subscripted_users = [
        {
            'user': db_sess.query(User).get(i)
        }
        for i in subscriptions
    ]
    subscripted_users.sort(key=lambda x: x['user'].name + x['user'].surname + x['user'].login)
    params = {
        'title': f'Люди',
        'subscripted_users': subscripted_users,
        'empty': True if len(subscripted_users) == 0 else False,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('people.html', **params)
from data.edit_profile_form import EditProfileForm  # Импортируем форму

@app.route('/edit_profile', methods=['GET', 'POST'])
def edit_profile():
    """Страница редактирования профиля пользователя"""
    if not current_user.is_authenticated:
        return forbidden('')

    form = EditProfileForm(obj=current_user)  # Заполняем форму текущими данными пользователя
    params = {
        'title': 'Редактирование профиля',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }

    if form.validate_on_submit():
        # Проверяем, если пользователь изменяет логин, убедимся, что он уникален
        if form.login.data and form.login.data != current_user.login:
            existing_user = db_sess.query(User).filter(User.login == form.login.data).first()
            if existing_user:
                flash('Пользователь с таким логином уже существует.', 'danger')
                return render_template('edit_profile.html', form=form, **params)
            else:
                current_user.login = form.login.data

        # Обновляем имя и фамилию, если они были изменены
        if form.name.data:
            current_user.name = form.name.data
        if form.surname.data:
            current_user.surname = form.surname.data

        # Обновляем пароль, если введён новый
        if form.password.data:
            current_user.password = pwd_context.hash(form.password.data)

        db_sess.commit()
        flash('Профиль обновлён успешно!', 'success')
        return redirect(url_for('user', user_id=current_user.id))
    else:
        if form.errors:
            # Объединяем все ошибки формы в одно сообщение
            error_messages = '; '.join([' '.join(errors) for errors in form.errors.values()])
            flash(error_messages, 'danger')

    return render_template('edit_profile.html', form=form, **params)
@app.route('/about')
def about():
    params = {
        'title': f'Про сайт'

    }
    return render_template('about.html', **params)
from flask import request, render_template
from sqlalchemy import or_

from flask import request, render_template
from sqlalchemy import or_

@app.route('/search')
def search():
    """Возвращает страницу с результатами поиска видео"""
    query = request.args.get('search', '').strip()

    if query:
        # Use ilike for case-insensitive search. Ensure database supports case-insensitive collation.
        videos = db_sess.query(Video).filter(Video.title.ilike(f'%{query}%')).all()
    else:
        videos = []

    params = {
        'title': f'Поиск: {query}',
        'query': query,
        'videos': videos,
        'empty': len(videos) == 0,
        'db_sess': db_sess,
        'User': User,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('search.html', **params)


db_session.global_init("db/hosting.sql")
db_sess = db_session.create_session()
db_sess.rollback
api.add_resource(UsersResource, '/api/users/<int:user_id>')
api.add_resource(UserListResource, '/api/users')
api.add_resource(VideosResource, '/api/videos/<int:video_id>')
api.add_resource(VideoListResource, '/api/videos')
api.add_resource(LikeResource, '/api/like/<int:video_id>')
api.add_resource(NotLikeResource, '/api/not_like/<int:video_id>')
api.add_resource(FollowResource, '/api/follow/<int:user_id>')
api.add_resource(NotFollowResource, '/api/not_follow/<int:user_id>')

if __name__ == '__main__':
    from waitress import serve
    app.run(debug=True, port=8080, host='0.0.0.0')


This my htmls:
add_video.html:
{% extends "base.html" %}

{% block content %}
    <h2>Добавление видео</h2>
    <form action="" method="post" enctype="multipart/form-data">
        {{ form.hidden_tag() }}
        {% if message %}
            <div class="row mb-3" style="margin-top: 20px">
                <div class="alert alert-danger" role="alert">
                    {{ message }}
                </div>
            </div>
        {% endif %}
        <div class="row mb-3">
            <div class="col-sm-2 col-form-label">
                {{ form.title.label }}
            </div>
            <div class="col-sm-10">
                {{ form.title(class="form-control", type="title") }}
                {% for error in form.title.errors %}
                <div class="alert alert-danger" role="alert">
                    {{ error }}
                </div>
                {% endfor %}
            </div>
        </div>
        <div class="row mb-3">
            <div class="col-sm-2 col-form-label">
                {{ form.description.label }}
            </div>
            <div class="col-sm-10">
                {{ form.description(class="form-control", type="description") }}
                {% for error in form.description.errors %}
                <div class="alert alert-danger" role="alert">
                    {{ error }}
                </div>
                {% endfor %}
            </div>
        </div>
        <div class="row mb-3">
            <div class="col-sm-2 col-form-label">
                {{ form.video.label }}
            </div>
            <div class="col-sm-10">
                {{ form.video(class="form-control", type="file") }}
                {% for error in form.video.errors %}
                <div class="alert alert-danger" role="alert">
                    {{ error }}
                </div>
                {% endfor %}
            </div>
        </div>
        <div class="row mb-3">
            <div class="col-sm-2 col-form-label">
                {{ form.preview.label }}
            </div>
            <div class="col-sm-10">
                {{ form.preview(class="form-control", type="file") }}
                {% for error in form.preview.errors %}
                <div class="alert alert-danger" role="alert">
                    {{ error }}
                </div>
                {% endfor %}
            </div>
        </div>
        <div class="">
            {{ form.submit(type="submit", class="btn btn-primary") }}
        </div>
    </form>
{% endblock %}


user.html:
{% extends "base.html" %}

{% block content %}
    <div class="row justify-content-start">
        <div class="col-sm-auto">
            <h4 class="d-flex align-items-center">
    <img src="{{ url_for('static', filename='avatars/' ~ user.avatar) }}" alt="Avatar" style="width:160px; height:160px; border-radius:50%; margin-right: 15px;">
    <div>
    <h1 class="d-flex align-items-center">
        <div>{{ user.name }} {{ user.surname }}<a class="text-decoration-none text-dark" href="/user/{{ user.id }}">
        {% if user.is_checked %}
            <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0,0,101.18545,97.64837"><g transform="translate(-189.40728,-131.17581)"><g data-paper-data="{&quot;isPaintingLayer&quot;:true}" fill-rule="nonzero" stroke="#000000" stroke-width="2" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" style="mix-blend-mode: normal"><path d="M274.03437,153.53043c-4.488,-1.19999 -35.51812,46.27672 -35.51812,46.27672c0,0 -28.25723,-15.25502 -28.44397,-14.73781c-0.44212,1.22454 29.03348,22.54885 29.03348,22.54885c0,0 36.62511,-53.63415 34.9286,-54.08775z" fill="#000000" stroke-linecap="round"/><path d="M289.59272,180c0,26.41257 -22.20342,47.82419 -49.59272,47.82419c-27.3893,0 -49.59272,-21.41162 -49.59272,-47.82419c0,-26.41257 22.20342,-47.82419 49.59272,-47.82419c27.3893,0 49.59272,21.41162 49.59272,47.82419z" fill="none" stroke-linecap="butt"/></g></g></svg>
        {% endif %}
        {% if user.is_admin %}
                            <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="53" height="46" viewBox="0,0,83.85813,76.17309"><g transform="translate(-198.07094,-139.34503)"><g data-paper-data="{&quot;isPaintingLayer&quot;:true}" fill-rule="nonzero" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" style="mix-blend-mode: normal"><path d="M214.13515,215.51812l-16.06421,-71.03623l83.85813,0.29476l-18.86439,70.74148z" fill="#1b7aff" stroke="none" stroke-width="0" stroke-linecap="butt"/><path d="M233.53062,190.26421l6.14991,-18.42359l4.69129,18.25007l17.98845,-1.95989l-15.45821,10.53263l5.83317,14.33014l-13.65936,-9.67733l-13.06852,9.42804l4.62117,-14.73647l-14.32865,-10.32196z" fill="#000000" stroke="#000000" stroke-width="0.5" stroke-linecap="round"/><text transform="translate(211.24343,164.59503) scale(0.5,0.5)" font-size="40" xml:space="preserve" fill="#000000" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="Pixel" font-weight="normal" text-anchor="start" style="mix-blend-mode: normal"><tspan x="0" dy="0">admin</tspan></text></g></g></svg>
                        {% endif %}
    </a>{% if user.is_blocked %}
                        <span class="badge bg-danger">Заблокирован</span>
                    {% endif %}
    </div>
        <h4 class="d-flex align-items-center">
        <div class="text-muted">@{{ user.login }} • {{ user.followers }} {{ user.followers | decline_subscription }} • {{ video_count }} видео </div>
        <button onclick="follow()" class="btn btn-outline-danger ms-auto {% if subscription %} active {% endif %}" type="button" name="follow" id="follow" {% if not authenticated or current_user.id == user.id %} disabled {% endif %}>
        <!-- Иконка -->
        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-plus-fill" viewBox="0 0 16 16">
            <path d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
            <path fill-rule="evenodd" d="M13.5 5a.5.5 0 0 1 .5.5V7h1.5a.5.5 0 0 1 0 1H14v1.5a.5.5 0 0 1-1 0V8h-1.5a.5.5 0 0 1 0-1H13V5.5a.5.5 0 0 1 .5-.5z"/>
        </svg>
        <span id="followers">Подписаться</span>
    </button>
    </div>

</h2>
        </div>
                {% if authenticated and current_user.id == user.id %}
            <div class="col-sm-auto">

            </div>
        {% endif %}
        <div class="col-sm-auto">
            <h2 class="text-secondary">

            </h2>
        </div>
        <div class="col-sm-auto">
            <h2>

            </h2>
        </div>
    </div>
    {% if authenticated and current_user.id == user.id %}
    <div class="col-sm-auto">
        <a href="/add_video" class="btn btn-primary" role="button">Добавить видео</a>
        <a href="{{ url_for('upload_avatar') }}" class="btn btn-secondary">Изменить Аватар</a>
        <a href="{{ url_for('edit_profile') }}" class="btn btn-warning">Редактировать профиль</a>
    </div>
{% endif %}
    <div class="row">
        <div class="row">
            {% if authenticated and current_user.id == user.id %}
            <h4>Мои видео</h4>
        {% else %}
            <h4>Все видео {{ user.name }}</h4>
        {% endif %}
        </div>
        <div class="row">
            {% if empty %}
                {% if authenticated and current_user.id == user.id %}
                <h5>Вы пока не загрузили ни одного видео, но может вы можете сделать это в любой момент с помощью кнопки "Добавить видео"</h5>
            {% else %}
                <h5>Пользователь не загрузил ни одного видео</h5>
            {% endif %}
            {% else %}
                {% for row in videos %}
                <div class="row">
                    {% for vid in row %}
                    <div class="col-4">
                        <a href="/video/{{ vid.id }}"><img src="/static/pre/{{ vid.id }}.jpg" style="width: 100%"></a>
                        <a href="/video/{{ vid.id }}" class="text-decoration-none text-dark">{{ vid.title }}</a>
                    </div>
                    {% endfor %}
                </div>
                {% endfor %}
            {% endif %}
        </div>
    </div>
    <meta name="keywords" content="video, sharing, camera phone, video phone, free, upload">
    <style>

    </style>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"> </script>
    <script type=text/javascript>
        function follow() {
            var follow = document.getElementById("follow");
            var unfollow = document.getElementById("unfollow");
            var followers = document.getElementById("followers");
            if ( follow.classList.contains('active') )
            {
                follow.classList.remove("active");
                followers.innerHTML = parseInt(followers.innerText) - 1;
                $.post('/api/not_follow/{{ user.id }}');
            }
            else
            {
                follow.classList.add("active");
                followers.innerHTML = parseInt(followers.innerText) + 1;
                $.post('/api/follow/{{ user.id }}');
                if ( unfollow.classList.contains('active') )
                {
                    unfollow.classList.remove("active");
                    followers.innerHTML = parseInt(followers.innerText) + 1;
                }
            }
        }
    </script>
{% endblock %}


video.html:
{% extends "base.html" %}

{% block content %}
    <div class="row">
        <div class="col-9">
            <div class="row">
                <div class="col">
                <div class="video-container paused" data-volume-level="high">
    <img class="thumbnail-img">
    <div class="video-controls-container">
      <div class="timeline-container">
        <div class="timeline">
          <img class="preview-img">
          <div class="thumb-indicator"></div>
        </div>
      </div>
      <div class="controls">
        <button class="play-pause-btn">
          <svg class="play-icon" viewBox="0 0 24 24">
            <path fill="currentColor" d="M8,5.14V19.14L19,12.14L8,5.14Z" />
          </svg>
          <svg class="pause-icon" viewBox="0 0 24 24">
            <path fill="currentColor" d="M14,19H18V5H14M6,19H10V5H6V19Z" />
          </svg>
        </button>
        <div class="volume-container">
          <button class="mute-btn">
            <svg class="volume-high-icon" viewBox="0 0 24 24">
              <path fill="currentColor" d="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z" />
            </svg>
            <svg class="volume-low-icon" viewBox="0 0 24 24">
              <path fill="currentColor" d="M5,9V15H9L14,20V4L9,9M18.5,12C18.5,10.23 17.5,8.71 16,7.97V16C17.5,15.29 18.5,13.76 18.5,12Z" />
            </svg>
            <svg class="volume-muted-icon" viewBox="0 0 24 24">
              <path fill="currentColor" d="M12,4L9.91,6.09L12,8.18M4.27,3L3,4.27L7.73,9H3V15H7L12,20V13.27L16.25,17.53C15.58,18.04 14.83,18.46 14,18.7V20.77C15.38,20.45 16.63,19.82 17.68,18.96L19.73,21L21,19.73L12,10.73M19,12C19,12.94 18.8,13.82 18.46,14.64L19.97,16.15C20.62,14.91 21,13.5 21,12C21,7.72 18,4.14 14,3.23V5.29C16.89,6.15 19,8.83 19,12M16.5,12C16.5,10.23 15.5,8.71 14,7.97V10.18L16.45,12.63C16.5,12.43 16.5,12.21 16.5,12Z" />
            </svg>
          </button>
          <input class="volume-slider" type="range" min="0" max="1" step="any" value="1">
        </div>
        <div class="duration-container">
          <div class="current-time">0:00</div>
          /
          <div class="total-time"></div>
        </div>
        <button class="captions-btn">
          <svg viewBox="0 0 24 24">
            <path fill="currentColor" d="M18,11H16.5V10.5H14.5V13.5H16.5V13H18V14A1,1 0 0,1 17,15H14A1,1 0 0,1 13,14V10A1,1 0 0,1 14,9H17A1,1 0 0,1 18,10M11,11H9.5V10.5H7.5V13.5H9.5V13H11V14A1,1 0 0,1 10,15H7A1,1 0 0,1 6,14V10A1,1 0 0,1 7,9H10A1,1 0 0,1 11,10M19,4H5C3.89,4 3,4.89 3,6V18A2,2 0 0,0 5,20H19A2,2 0 0,0 21,18V6C21,4.89 20.1,4 19,4Z" />
          </svg>
        </button>
        <button class="speed-btn wide-btn">
          1x
        </button>
        <button class="mini-player-btn">
          <svg viewBox="0 0 24 24">
            <path fill="currentColor" d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zm-10-7h9v6h-9z"/>
          </svg>
        </button>
        <button class="theater-btn">
          <svg class="tall" viewBox="0 0 24 24">
            <path fill="currentColor" d="M19 6H5c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H5V8h14v8z"/>
          </svg>
          <svg class="wide" viewBox="0 0 24 24">
            <path fill="currentColor" d="M19 7H5c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2zm0 8H5V9h14v6z"/>
          </svg>
        </button>
        <button class="full-screen-btn">
          <svg class="open" viewBox="0 0 24 24">
            <path fill="currentColor" d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
          </svg>
          <svg class="close" viewBox="0 0 24 24">
            <path fill="currentColor" d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
          </svg>
        </button>
      </div>
    </div>
    <video width="140%" autoplay src="{{ video_src }}">
      <track kind="captions" srclang="en" src="assets/subtitles.vtt">
    </video>
  </div>
                </div>
            </div>
            {% if authenticated and current_user.id|to_int == current_video.author|to_int or current_user.id == 1 %}
            <div style="margin-bottom: 10px">
                <a href="/edit_video/{{ current_video.id }}" class="btn btn-warning" role="button">Изменить видео</a>
                <a href="/delete_video/{{ current_video.id }}" class="btn btn-danger" role="button">Удалить видео</a>
            </div>
            {% endif %}
            <div class="row">
                <div class="col-auto">
                    <h3>
                        {{ current_video.title }}
                    </h3>
                </div>
                <div class="col-auto">
                    <button onclick="like()" class="btn btn-outline-danger btn-sm {% if like %} active {% endif %}" type="submit" name="like" id="like" {% if not authenticated or current_user.id == current_video.author %} disabled {% endif %}>
                        <svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 -960 960 960" width="16px" fill="#5f6368"><path d="M840-640q32 0 56 24t24 56v80q0 7-2 15t-4 15L794-168q-9 20-30 34t-44 14H280v-520l240-238q15-15 35.5-17.5T595-888q19 10 28 28t4 37l-45 183h258Zm-480 34v406h360l120-280v-80H480l54-220-174 174ZM160-120q-33 0-56.5-23.5T80-200v-360q0-33 23.5-56.5T160-640h120v80H160v360h120v80H160Zm200-80v-406 406Z"></path></svg>
                        <span id="likes">{{ current_video.likes }}</span>
                    </button>
                </div>
            </div>
            <div class="row">
                <div class="col-auto">
                    <h4>
                    <img src="{{ avatar_url }}" alt="Avatar" class="img-fluid rounded-circle m-2" style="width:80px; height:80px;">
                        <a href="/user/{{ author.id }}" class="text-decoration-none text-dark">{{ author.name }} {{ author.surname }} </a>
                    </h4>
                </div>
                <div class="col-auto">
                    <button onclick="follow()" class="btn btn-outline-danger btn-sm {% if subscription %} active {% endif %}" type="submit" name="follow" id="follow" {% if not authenticated or current_user.id == current_video.author %} disabled {% endif %}>
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-plus-fill" viewBox="0 0 16 16">
                            <path d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
                            <path fill-rule="evenodd" d="M13.5 5a.5.5 0 0 1 .5.5V7h1.5a.5.5 0 0 1 0 1H14v1.5a.5.5 0 0 1-1 0V8h-1.5a.5.5 0 0 1 0-1H13V5.5a.5.5 0 0 1 .5-.5z"/>
                        </svg>
                        <span id="followers">{{ author.followers }}</span>
                    </button>
                </div>
            </div>



            <div class="row" style="margin-top: 10px">
                <div class="col">
                    {% autoescape false %}
                    <p style="color: #808080">{{ current_video.description }}</p>
                    {% endautoescape %}
                </div>
            </div>
            <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
            <script type=text/javascript>
                function follow() {
                    var follow = document.getElementById("follow");
                    var unfollow = document.getElementById("unfollow");
                    var followers = document.getElementById("followers");
                    if ( follow.classList.contains('active') )
                    {
                        follow.classList.remove("active");
                        followers.innerHTML = parseInt(followers.innerText) - 1;
                        $.post('/api/not_follow/{{ current_video.author }}');
                    }
                    else
                    {
                        follow.classList.add("active");
                        followers.innerHTML = parseInt(followers.innerText) + 1;
                        $.post('/api/follow/{{ current_video.author }}');
                        if ( unfollow.classList.contains('active') )
                        {
                            unfollow.classList.remove("active");
                            followers.innerHTML = parseInt(followers.innerText) + 1;
                        }
                    }
                }

                function like() {
                    var like = document.getElementById("like");
                    var dislike = document.getElementById("dislike");
                    var likes = document.getElementById("likes");
                    if ( like.classList.contains('active') )
                    {
                        like.classList.remove("active");
                        likes.innerHTML = parseInt(likes.innerText) - 1;
                        $.post('/api/not_like/{{ current_video.id }}');
                    }
                    else
                    {
                        like.classList.add("active");
                        likes.innerHTML = parseInt(likes.innerText) + 1;
                        $.post('/api/like/{{ current_video.id }}');
                        if ( dislike.classList.contains('active') )
                        {
                            dislike.classList.remove("active");
                            likes.innerHTML = parseInt(likes.innerText) + 1;
                        }
                    }
                }





const playPauseBtn = document.querySelector(".play-pause-btn")
const theaterBtn = document.querySelector(".theater-btn")
const fullScreenBtn = document.querySelector(".full-screen-btn")
const miniPlayerBtn = document.querySelector(".mini-player-btn")
const muteBtn = document.querySelector(".mute-btn")
const captionsBtn = document.querySelector(".captions-btn")
const speedBtn = document.querySelector(".speed-btn")
const currentTimeElem = document.querySelector(".current-time")
const totalTimeElem = document.querySelector(".total-time")
const previewImg = document.querySelector(".preview-img")
const thumbnailImg = document.querySelector(".thumbnail-img")
const volumeSlider = document.querySelector(".volume-slider")
const videoContainer = document.querySelector(".video-container")
const timelineContainer = document.querySelector(".timeline-container")
const video = document.querySelector("video")

document.addEventListener("keydown", e => {
  const tagName = document.activeElement.tagName.toLowerCase()

  if (tagName === "input") return

  switch (e.key.toLowerCase()) {
    case " ":
      if (tagName === "button") return
    case "k":
      togglePlay()
      break
    case "f":
      toggleFullScreenMode()
      break
    case "t":
      toggleTheaterMode()
      break
    case "i":
      toggleMiniPlayerMode()
      break
    case "m":
      toggleMute()
      break
    case "arrowleft":
    case "j":
      skip(-5)
      break
    case "arrowright":
    case "l":
      skip(5)
      break
    case "c":
      toggleCaptions()
      break
  }
})

// Timeline
timelineContainer.addEventListener("mousemove", handleTimelineUpdate)
timelineContainer.addEventListener("mousedown", toggleScrubbing)
document.addEventListener("mouseup", e => {
  if (isScrubbing) toggleScrubbing(e)
})
document.addEventListener("mousemove", e => {
  if (isScrubbing) handleTimelineUpdate(e)
})

let isScrubbing = false
let wasPaused = false
function toggleScrubbing(e) {
  const rect = timelineContainer.getBoundingClientRect()
  const percent = Math.min(Math.max(0, e.x - rect.x), rect.width) / rect.width
  isScrubbing = (e.buttons & 1) === 1
  videoContainer.classList.toggle("scrubbing", isScrubbing)
  if (isScrubbing) {
    wasPaused = video.paused
    video.pause()
  } else {
    video.currentTime = percent * video.duration
    if (!wasPaused) video.play()
  }

  handleTimelineUpdate(e)
}

function handleTimelineUpdate(e) {
  const rect = timelineContainer.getBoundingClientRect()
  const percent = Math.min(Math.max(0, e.x - rect.x), rect.width) / rect.width
  const previewImgNumber = Math.max(
    1,
    Math.floor((percent * video.duration) / 10)
  )
  const previewImgSrc = "/static/pre/{{ current_video.id }}.jpg"
  previewImg.src = previewImgSrc
  timelineContainer.style.setProperty("--preview-position", percent)

  if (isScrubbing) {
    e.preventDefault()
    thumbnailImg.src = previewImgSrc
    timelineContainer.style.setProperty("--progress-position", percent)
  }
}

// Playback Speed
speedBtn.addEventListener("click", changePlaybackSpeed)

function changePlaybackSpeed() {
  let newPlaybackRate = video.playbackRate + 0.25
  if (newPlaybackRate > 3) newPlaybackRate = 0.25
  video.playbackRate = newPlaybackRate
  speedBtn.textContent = `${newPlaybackRate}x`
}

// Captions
const captions = video.textTracks[0]
captions.mode = "hidden"

captionsBtn.addEventListener("click", toggleCaptions)

function toggleCaptions() {
  const isHidden = captions.mode === "hidden"
  captions.mode = isHidden ? "showing" : "hidden"
  videoContainer.classList.toggle("captions", isHidden)
}

// Duration
video.addEventListener("loadeddata", () => {
  totalTimeElem.textContent = formatDuration(video.duration)
})

video.addEventListener("timeupdate", () => {
  currentTimeElem.textContent = formatDuration(video.currentTime)
  const percent = video.currentTime / video.duration
  timelineContainer.style.setProperty("--progress-position", percent)
})

const leadingZeroFormatter = new Intl.NumberFormat(undefined, {
  minimumIntegerDigits: 2,
})
function formatDuration(time) {
  const seconds = Math.floor(time % 60)
  const minutes = Math.floor(time / 60) % 60
  const hours = Math.floor(time / 3600)
  if (hours === 0) {
    return `${minutes}:${leadingZeroFormatter.format(seconds)}`
  } else {
    return `${hours}:${leadingZeroFormatter.format(
      minutes
    )}:${leadingZeroFormatter.format(seconds)}`
  }
}

function skip(duration) {
  video.currentTime += duration
}

// Volume
muteBtn.addEventListener("click", toggleMute)
volumeSlider.addEventListener("input", e => {
  video.volume = e.target.value
  video.muted = e.target.value === 0
})

function toggleMute() {
  video.muted = !video.muted
}

video.addEventListener("volumechange", () => {
  volumeSlider.value = video.volume
  let volumeLevel
  if (video.muted || video.volume === 0) {
    volumeSlider.value = 0
    volumeLevel = "muted"
  } else if (video.volume >= 0.5) {
    volumeLevel = "high"
  } else {
    volumeLevel = "low"
  }

  videoContainer.dataset.volumeLevel = volumeLevel
})

// View Modes
theaterBtn.addEventListener("click", toggleTheaterMode)
fullScreenBtn.addEventListener("click", toggleFullScreenMode)
miniPlayerBtn.addEventListener("click", toggleMiniPlayerMode)

function toggleTheaterMode() {
  videoContainer.classList.toggle("theater")
}

function toggleFullScreenMode() {
  if (document.fullscreenElement == null) {
    videoContainer.requestFullscreen()
  } else {
    document.exitFullscreen()
  }
}

function toggleMiniPlayerMode() {
  if (videoContainer.classList.contains("mini-player")) {
    document.exitPictureInPicture()
  } else {
    video.requestPictureInPicture()
  }
}

document.addEventListener("fullscreenchange", () => {
  videoContainer.classList.toggle("full-screen", document.fullscreenElement)
})

video.addEventListener("enterpictureinpicture", () => {
  videoContainer.classList.add("mini-player")
})

video.addEventListener("leavepictureinpicture", () => {
  videoContainer.classList.remove("mini-player")
})

// Play/Pause
playPauseBtn.addEventListener("click", togglePlay)
video.addEventListener("click", togglePlay)

function togglePlay() {
  video.paused ? video.play() : video.pause()
}

video.addEventListener("play", () => {
  videoContainer.classList.remove("paused")
})

video.addEventListener("pause", () => {
  videoContainer.classList.add("paused")
})
            </script>
        </div>
        <style>
        *, *::before, *::after {
  box-sizing: border-box;
}

body {
  margin: 0;
}

.video-container {
  position: relative;
  width: 90%;
  max-width: 1000px;
  display: flex;
  justify-content: center;
  margin-inline: auto;
  background-color: black;
}

.video-container.theater,
.video-container.full-screen {
  max-width: initial;
  width: 100%;
}

.video-container.theater {
  max-height: 90vh;
}

.video-container.full-screen {
  max-height: 100vh;
}

video {
  width: 100%;
}

.video-controls-container {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  color: white;
  z-index: 100;
  opacity: 0;
  transition: opacity 150ms ease-in-out;
}

.video-controls-container::before {
  content: "";
  position: absolute;
  bottom: 0;
  background: linear-gradient(to top, rgba(0, 0, 0, .75), transparent);
  width: 100%;
  aspect-ratio: 6 / 1;
  z-index: -1;
  pointer-events: none;
}

.video-container:hover .video-controls-container,
.video-container:focus-within .video-controls-container,
.video-container.paused .video-controls-container {
  opacity: 1;
}

.video-controls-container .controls {
  display: flex;
  gap: .5rem;
  padding: .25rem;
  align-items: center;
}

.video-controls-container .controls button {
  background: none;
  border: none;
  color: inherit;
  padding: 0;
  height: 30px;
  width: 30px;
  font-size: 1.1rem;
  cursor: pointer;
  opacity: .85;
  transition: opacity 150ms ease-in-out;
}

.video-controls-container .controls button:hover {
  opacity: 1;
}

.video-container.paused .pause-icon {
  display: none;
}

.video-container:not(.paused) .play-icon {
  display: none;
}

.video-container.theater .tall {
  display: none;
}

.video-container:not(.theater) .wide {
  display: none;
}

.video-container.full-screen .open {
  display: none;
}

.video-container:not(.full-screen) .close {
  display: none;
}

.volume-high-icon,
.volume-low-icon,
.volume-muted-icon {
  display: none;
}

.video-container[data-volume-level="high"] .volume-high-icon {
  display: block;
}

.video-container[data-volume-level="low"] .volume-low-icon {
  display: block;
}

.video-container[data-volume-level="muted"] .volume-muted-icon {
  display: block;
}

.volume-container {
  display: flex;
  align-items: center;
}

.volume-slider {
  width: 0;
  transform-origin: left;
  transform: scaleX(0);
  transition: width 150ms ease-in-out, transform 150ms ease-in-out;
  background: linear-gradient(45deg, red, orange, yellow, green, blue, indigo, violet, red);
}

.volume-container:hover .volume-slider,
.volume-slider:focus-within {
  width: 100px;
  transform: scaleX(1);
}

.duration-container {
  display: flex;
  align-items: center;
  gap: .25rem;
  flex-grow: 1;
}

.video-container.captions .captions-btn {
  border-bottom: 3px solid red;
}

.video-controls-container .controls button.wide-btn {
  width: 50px;
}

.timeline-container {
  height: 7px;
  margin-inline: .5rem;
  cursor: pointer;
  display: flex;
  align-items: center;
}

.timeline {
  background-color: rgba(100, 100, 100, .5);
  height: 3px;
  width: 100%;
  position: relative
}

.timeline::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  right: calc(100% - var(--preview-position) * 100%);
  background-color: rgb(150, 150, 150);
  display: none;
}

.timeline::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  right: calc(100% - var(--progress-position) * 100%);
  background: linear-gradient(45deg, red, orange, yellow, green, blue, indigo, violet, red);
}

.timeline .thumb-indicator {
  --scale: 0;
  position: absolute;
  transform: translateX(-50%) scale(var(--scale));
  height: 200%;
  top: -50%;
  left: calc(var(--progress-position) * 100%);
  background-color: red;
  border-radius: 50%;
  transition: transform 150ms ease-in-out;
  aspect-ratio: 1 / 1;
}

.timeline .preview-img {
  position: absolute;
  height: 80px;
  aspect-ratio: 16 / 9;
  top: -1rem;
  transform: translate(-50%, -100%);
  left: calc(var(--preview-position) * 100%);
  border-radius: .25rem;
  border: 2px solid white;
  display: none;
}

.thumbnail-img {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: 100%;
  display: none;
}

.video-container.scrubbing .thumbnail-img {
  display: block;
}

.video-container.scrubbing .preview-img,
.timeline-container:hover .preview-img {
  display: block;
}

.video-container.scrubbing .timeline::before,
.timeline-container:hover .timeline::before {
  display: block;
}

.video-container.scrubbing .thumb-indicator,
.timeline-container:hover .thumb-indicator {
  --scale: 1;
}

.video-container.scrubbing .timeline,
.timeline-container:hover .timeline {
  height: 100%;
}
        </style>
        <div class="col-3">
            {% for vid in last_videos %}
            <div class="row">
                <a href="/video/{{ vid.id }}"><img src="/static/pre/{{ vid.id }}.jpg" style="width: 100%"></a>
                <p><a class="text-decoration-none text-dark" href="/video/{{ vid.id }}">{{ vid.title }}</a> - <a class="text-decoration-none text-dark" href="/user/{{ db_sess.query(User).filter(User.id == vid.author).first().id }}">{{ db_sess.query(User).filter(User.id == vid.author).first().name }} {{ db_sess.query(User).filter(User.id == vid.author).first().surname }}</a></p>
            </div>
            {% endfor %}
        </div>
    </div>
{% endblock %}


base.html:
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link
                href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css"
                rel="stylesheet"
                integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6"
                crossorigin="anonymous">
        <style type="text/css">

        </style>
        <title>{{ title }}</title>
    </head>
    <body>
        <nav class="navbar sticky-top navbar-expand-lg navbar-light bg-light">
            <div class="container-fluid">
                <a class="navbar-brand mb-0 h1" href="/">

                    <img src="/static/img/vhs.png" alt="" width="201 height="35">
                </a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                        <li class="nav-item">
                            <a class="nav-link" href="/">Главная</a>
                        </li>
                        {% if authenticated %}
                            <li class="nav-item">
                                <a class="nav-link" href="/feed">Лента</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="/favorite">Избранное</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="/people">Пользователи</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="/user/{{ current_user.id }}">Мои видео</a>
                            </li>
                            {% if current_user.id == 1 %}
                            <li class="nav-item">
                                <a class="nav-link" href="{{ url_for('admin_users') }}">Админ</a>
                            </li>
                            {% endif %}
                            <li class="nav-item">
                                <a class="nav-link" href="/logout">Выйти</a>

                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="/about">Про сайт</a>
                            </li>
                        {% else %}
                            <li class="nav-item">
                                <a class="nav-link" href="/login">Войти</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="/register">Зарегаться</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="/about">Про сайт</a>
                            </li>
                        {% endif %}
                    </ul>

                    <form class="d-flex" action="/search">
                        <input class="form-control me-2" type="search" name="search" placeholder="Поиск" aria-label="Search">
                        <button class="btn btn-outline-success" type="submit">Найти</button>
                    </form>
                </div>
            </div>
        </nav>
        <div class="container" style="margin-top: 20px">
            {% block content %}{% endblock %}
        </div>
        <script
                src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js"
                integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf"
                crossorigin="anonymous">
        </script>
        <style>
        div {
            backdrop-filter: blur(10px);
        }
        </style>
    </body>
</html>


index.html:
{% extends "base.html" %}

{% block content %}
    <div class="row">
        <div class="row">
            <h4>Новинки</h4>
        </div>
        <div class="row">
            {% for vid in last_videos %}
            <div class="col-4">
                <a href="/video/{{ vid.id }}"><img src="static/pre/{{ vid.id }}.jpg" style="width: 100%"></a>
                <p><a class="text-decoration-none text-dark" href="/video/{{ vid.id }}">{{ vid.title }}</a> - <a class="text-decoration-none text-dark" href="/user/{{ db_sess.query(User).filter(User.id == vid.author).first().id }}">{{ db_sess.query(User).filter(User.id == vid.author).first().name }} {{ db_sess.query(User).filter(User.id == vid.author).first().surname }}</a></p>
            </div>
            {% endfor %}
        </div>
    </div>
    <div class="row">
        <div class="row">
            <h4>Лучшее</h4>
        </div>
        <div class="row">
            {% for vid in best_videos %}
            <div class="col-4">
                <a href="/video/{{ vid.id }}"><img src="static/pre/{{ vid.id }}.jpg" style="width: 100%"></a>
                <p><a class="text-decoration-none text-dark" href="/video/{{ vid.id }}">{{ vid.title }}</a> - <a class="text-decoration-none text-dark" href="/user/{{ db_sess.query(User).filter(User.id == vid.author).first().id }}">{{ db_sess.query(User).filter(User.id == vid.author).first().name }} {{ db_sess.query(User).filter(User.id == vid.author).first().surname }}</a></p>
            </div>
            {% endfor %}
        </div>
    </div>
    <div class="row">
        <div class="row">
            <h4>Недооцененное</h4>
        </div>
        <div class="row">
            {% for vid in underrated_videos %}
            <div class="col-4">
                <a href="/video/{{ vid.id }}"><img src="static/pre/{{ vid.id }}.jpg" style="width: 100%"></a>
                <p><a class="text-decoration-none text-dark" href="/video/{{ vid.id }}">{{ vid.title }}</a> - <a class="text-decoration-none text-dark" href="/user/{{ db_sess.query(User).filter(User.id == vid.author).first().id }}">{{ db_sess.query(User).filter(User.id == vid.author).first().name }} {{ db_sess.query(User).filter(User.id == vid.author).first().surname }}</a></p>
            </div>
            {% endfor %}
        </div>

        <div class="row">
            <h4>Все видео</h4>
        </div>
        <div class="row">
            {% for vid in all_videos %}
            <div class="col-4">
                <a href="/video/{{ vid.id }}"><img src="static/pre/{{ vid.id }}.jpg" style="width: 100%"></a>
                <p><a class="text-decoration-none text-dark" href="/video/{{ vid.id }}">{{ vid.title }}</a> - <a class="text-decoration-none text-dark" href="/user/{{ db_sess.query(User).filter(User.id == vid.author).first().id }}">{{ db_sess.query(User).filter(User.id == vid.author).first().name }} {{ db_sess.query(User).filter(User.id == vid.author).first().surname }}</a></p>
            </div>
            {% endfor %}
        </div>

    </div>
{% endblock %}
data folder:
videos.py:
import sqlalchemy
from .db_session import SqlAlchemyBase
from sqlalchemy_serializer import SerializerMixin
from datetime import datetime


class Video(SqlAlchemyBase, SerializerMixin):
    """Определяет таблицу с видео в базе данных"""
    __tablename__ = 'videos'

    id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, unique=True, autoincrement=True, nullable=False)
    title = sqlalchemy.Column(sqlalchemy.String, nullable=False)
    author = sqlalchemy.Column(sqlalchemy.String, sqlalchemy.ForeignKey("users.id"), nullable=False)
    description = sqlalchemy.Column(sqlalchemy.String, nullable=False, default='')
    likes = sqlalchemy.Column(sqlalchemy.Integer, nullable=False, default=0)
    datetime = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, default=datetime.utcnow)

users.py:
# data/users.py
from sqlalchemy import Column, Integer, String, Text, Boolean
from flask_login import UserMixin
from .db_session import SqlAlchemyBase

class User(UserMixin, SqlAlchemyBase):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True, autoincrement=True)
    login = Column(String, unique=True, nullable=True)
    password = Column(String, nullable=True)
    name = Column(String, nullable=True)
    surname = Column(String, nullable=True)
    likes = Column(Text, nullable=True, default='[]')  # Users' liked videos
    subscriptions = Column(Text, nullable=True, default='[]')  # Users this user subscribes to
    followers = Column(Integer, nullable=True, default=0)  # Number of followers
    avatar = Column(String, nullable=True, default='default.png')  # User avatar
    is_checked = Column(Boolean, default=False, nullable=False)  # Previously добавленное поле
    is_blocked = Column(Boolean, default=False, nullable=False)  # Новое поле для блокировки
    is_admin = Column(Boolean, default=False, nullable=False)  # Новое поле для админов

edit_profile_form.py:
# data/edit_profile_form.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, EqualTo, Length, Optional

class EditProfileForm(FlaskForm):
    login = StringField('Логин', validators=[Optional(), Length(min=3, max=25)])
    name = StringField('Имя', validators=[Optional(), Length(min=1, max=50)])
    surname = StringField('Фамилия', validators=[Optional(), Length(min=1, max=50)])
    password = PasswordField('Новый пароль', validators=[Optional(), Length(min=6, max=100)])
    confirm_password = PasswordField('Подтвердите пароль', validators=[Optional(), EqualTo('password', message='Пароли должны совпадать')])
    submit = SubmitField('Сохранить изменения')
login_form.py:
from flask_wtf import FlaskForm
from wtforms.validators import DataRequired
from wtforms import PasswordField, BooleanField, SubmitField, StringField


class LoginForm(FlaskForm):
    """Создает форму входа"""
    login = StringField('Логин', validators=[DataRequired()])
    password = PasswordField('Пароль', validators=[DataRequired()])
    remember_me = BooleanField('Запомнить меня')
    submit = SubmitField('Войти')
register_form.py:
from flask_wtf import FlaskForm
from wtforms.validators import DataRequired
from wtforms import PasswordField, BooleanField, SubmitField, StringField


class RegisterForm(FlaskForm):
    """Создает форму регистрации"""
    login = StringField('Логин', validators=[DataRequired()])
    password = PasswordField('Пароль', validators=[DataRequired()])
    repeat_password = PasswordField('Пароль еще раз', validators=[DataRequired()])
    name = StringField('Имя', validators=[DataRequired()])
    surname = StringField('Фамилия', validators=[])
    remember_me = BooleanField('Запомнить меня')
    submit = SubmitField('Зарегистрироваться')
subscriptions_resource.py:
import json
from . import db_session
from flask import jsonify
from data.users import User
from flask_restful import abort, Resource
from flask_login import current_user


def abort_if_user_not_found(user_id):
    """Возвращает ошибку 404 если пользователь не найден"""
    session = db_session.create_session()
    user = session.query(User).get(user_id)
    if not user:
        abort(404, message=f"User {user_id} not found")


def abort_if_user_not_authenticated():
    """Возвращает ошибку 403 если пользователь не аутентифицирован"""
    if not current_user.is_authenticated:
        return forbidden('')


class FollowResource(Resource):
    """Создает ресурс API для работы с подпиской на пользователя"""

    def post(self, user_id):
        """Подписывает на пользователя"""
        abort_if_user_not_authenticated()
        abort_if_user_not_found(user_id)
        if user_id == current_user.id:
            abort(403, message=f"User {current_user.id} has no permission")
        session = db_session.create_session()
        user = session.query(User).get(current_user.id)
        subscriptions = json.loads(user.subscriptions)
        if user_id in subscriptions:
            abort(403, message=f'User {user_id} already in subscriptions')
        subscriptions.append(user_id)
        user.subscriptions = json.dumps(subscriptions)
        author = session.query(User).get(user_id)
        author.followers += 1
        session.commit()
        return jsonify({'success': 'OK'})


class NotFollowResource(Resource):
    """Создает ресурс API для работы с отпиской от пользователя"""

    def post(self, user_id):
        """Отписывает от пользователя"""
        abort_if_user_not_authenticated()
        abort_if_user_not_found(user_id)
        if user_id == current_user.id:
            abort(403, message=f"User {current_user.id} has no permission")
        session = db_session.create_session()
        user = session.query(User).get(current_user.id)
        subscriptions = json.loads(user.subscriptions)
        if user_id not in subscriptions:
            abort(403, message=f'User {user_id} not in subscriptions')
        del subscriptions[subscriptions.index(user_id)]
        user.subscriptions = json.dumps(subscriptions)
        author = session.query(User).get(user_id)
        author.followers -= 1
        session.commit()
        return jsonify({'success': 'OK'})
likes_resource.py:
import json
from . import db_session
from flask import jsonify
from data.users import User
from data.videos import Video
from flask_restful import abort, Resource
from flask_login import current_user


def abort_if_video_not_found(video_id):
    """Возвращает ошибку 404 если видео не найдено"""
    session = db_session.create_session()
    video = session.query(Video).get(video_id)
    if not video:
        abort(404, message=f"Cassette {video_id} not found")


def abort_if_user_not_authenticated():
    """Возвращает ошибку 403 если пользователь не аутентифицирован"""
    if not current_user.is_authenticated:
        abort(404, message=f"User is not authenticated")


class LikeResource(Resource):
    """Создает ресурс API для работы с добавлением видео в любимые"""

    def post(self, video_id):
        """Добавляет видео в любимые"""
        abort_if_user_not_authenticated()
        abort_if_video_not_found(video_id)
        session = db_session.create_session()
        video = session.query(Video).get(video_id)
        if video.author == current_user.id:
            abort(403, message=f"User {current_user.id} has no permission")
        user = session.query(User).get(current_user.id)
        likes = json.loads(user.likes)
        if video_id in likes:
            abort(403, message=f"Cassette {video_id} already in likes")
        likes.append(video_id)
        user.likes = json.dumps(likes)
        video.likes += 1
        session.commit()
        return jsonify({'success': 'OK'})


class NotLikeResource(Resource):
    """Создает ресурс API для работы с удалением видео из любимых"""

    def post(self, video_id):
        """Удаляет видео из любимых"""
        abort_if_user_not_authenticated()
        abort_if_video_not_found(video_id)
        session = db_session.create_session()
        video = session.query(Video).get(video_id)
        if video.author == current_user.id:
            abort(403, message=f"User {current_user.id} has no permission")
        user = session.query(User).get(current_user.id)
        likes = json.loads(user.likes)
        if video_id not in likes:
            abort(403, message=f"Cassette {video_id} already in likes")
        del likes[likes.index(video_id)]
        user.likes = json.dumps(likes)
        video.likes -= 1
        session.commit()
        return jsonify({'success': 'OK'})
edit_profile_form.py:
# data/edit_profile_form.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, EqualTo, Length, Optional

class EditProfileForm(FlaskForm):
    login = StringField('Логин', validators=[Optional(), Length(min=3, max=25)])
    name = StringField('Имя', validators=[Optional(), Length(min=1, max=50)])
    surname = StringField('Фамилия', validators=[Optional(), Length(min=1, max=50)])
    password = PasswordField('Новый пароль', validators=[Optional(), Length(min=6, max=100)])
    confirm_password = PasswordField('Подтвердите пароль', validators=[Optional(), EqualTo('password', message='Пароли должны совпадать')])
    submit = SubmitField('Сохранить изменения')
db_session.py:
import sqlalchemy as sa
import sqlalchemy.orm as orm
from sqlalchemy.orm import Session
import sqlalchemy.ext.declarative as dec

SqlAlchemyBase = dec.declarative_base()

__factory = None


def global_init(db_file):
    """Создает подключение к базе данных"""
    global __factory

    if __factory:
        return

    if not db_file or not db_file.strip():
        raise Exception("Необходимо указать файл базы данных.")

    conn_str = f'sqlite:///{db_file.strip()}?check_same_thread=False'
    print(f"Подключение к базе данных по адресу {conn_str}")

    engine = sa.create_engine(conn_str, echo=False)
    __factory = orm.sessionmaker(bind=engine)

    from . import __all_models

    SqlAlchemyBase.metadata.create_all(engine)


def create_session() -> Session:
    """Возвращает сессию, ассоциированную с текущим соединением"""
    global __factory
    return __factory()
register_form.py:
from flask_wtf import FlaskForm
from wtforms.validators import DataRequired
from wtforms import PasswordField, BooleanField, SubmitField, StringField


class RegisterForm(FlaskForm):
    """Создает форму регистрации"""
    login = StringField('Логин', validators=[DataRequired()])
    password = PasswordField('Пароль', validators=[DataRequired()])
    repeat_password = PasswordField('Пароль еще раз', validators=[DataRequired()])
    name = StringField('Имя', validators=[DataRequired()])
    surname = StringField('Фамилия', validators=[])
    remember_me = BooleanField('Запомнить меня')
    submit = SubmitField('Зарегистрироваться')
users_arg_parser.py:
from flask_restful import reqparse

parser = reqparse.RequestParser()
parser.add_argument('login', required=True, type=str)
parser.add_argument('password', required=True, type=str)
parser.add_argument('name', required=True, type=str)
parser.add_argument('surname', required=True, type=str)
users_resource.py:
from . import db_session
from flask import jsonify
from data.users import User
from .users_arg_parser import parser
from flask_restful import abort, Resource


def abort_if_user_not_found(user_id):
    """Возвращает ошибку 404 если пользователь не найден"""
    session = db_session.create_session()
    user = session.query(User).get(user_id)
    if not user:
        abort(404, message=f"User {user_id} not found")


class UsersResource(Resource):
    """Создает ресурс API для работы с пользователем"""

    def get(self, user_id):
        """Возвращает информацию о пользователе"""
        abort_if_user_not_found(user_id)
        session = db_session.create_session()
        user = session.query(User).get(user_id)
        return jsonify({'user': user.to_dict(
            only=('id', 'login', 'name', 'surname', 'followers', 'datetime'))})


class UserListResource(Resource):
    """Создает ресурс API для работы с пользователями"""

    def get(self):
        """Возвращает информацию о всех пользователях"""
        session = db_session.create_session()
        user = session.query(User).all()
        return jsonify({'user': [item.to_dict(
            only=('id', 'login', 'name', 'surname', 'followers', 'datetime')) for item in user]})

    def post(self):
        """Добавляет новго пользователя"""
        args = parser.parse_args()
        db_sess = db_session.create_session()
        user = User()
        user.login = args['login']
        user.password = args['password']
        user.name = args['name']
        user.surname = args['surname']
        db_sess.add(user)
        db_sess.commit()
        return jsonify({'success': 'OK'})
video_form.py:
from flask_wtf import FlaskForm
from wtforms.validators import DataRequired
from wtforms import SubmitField, StringField, TextAreaField, FileField


class VideoForm(FlaskForm):
    """Создает форму добавления/изменения видео"""
    title = StringField('Название видео', validators=[DataRequired()])
    description = TextAreaField('Описание', validators=[])
    video = FileField('Видео', validators=[DataRequired()])
    preview = FileField('Обложка', validators=[DataRequired()])
    submit = SubmitField('Загрузить')
subscriptions_resource.py:
import json
from . import db_session
from flask import jsonify
from data.users import User
from flask_restful import abort, Resource
from flask_login import current_user


def abort_if_user_not_found(user_id):
    """Возвращает ошибку 404 если пользователь не найден"""
    session = db_session.create_session()
    user = session.query(User).get(user_id)
    if not user:
        abort(404, message=f"User {user_id} not found")


def abort_if_user_not_authenticated():
    """Возвращает ошибку 403 если пользователь не аутентифицирован"""
    if not current_user.is_authenticated:
        return forbidden('')


class FollowResource(Resource):
    """Создает ресурс API для работы с подпиской на пользователя"""

    def post(self, user_id):
        """Подписывает на пользователя"""
        abort_if_user_not_authenticated()
        abort_if_user_not_found(user_id)
        if user_id == current_user.id:
            abort(403, message=f"User {current_user.id} has no permission")
        session = db_session.create_session()
        user = session.query(User).get(current_user.id)
        subscriptions = json.loads(user.subscriptions)
        if user_id in subscriptions:
            abort(403, message=f'User {user_id} already in subscriptions')
        subscriptions.append(user_id)
        user.subscriptions = json.dumps(subscriptions)
        author = session.query(User).get(user_id)
        author.followers += 1
        session.commit()
        return jsonify({'success': 'OK'})


class NotFollowResource(Resource):
    """Создает ресурс API для работы с отпиской от пользователя"""

    def post(self, user_id):
        """Отписывает от пользователя"""
        abort_if_user_not_authenticated()
        abort_if_user_not_found(user_id)
        if user_id == current_user.id:
            abort(403, message=f"User {current_user.id} has no permission")
        session = db_session.create_session()
        user = session.query(User).get(current_user.id)
        subscriptions = json.loads(user.subscriptions)
        if user_id not in subscriptions:
            abort(403, message=f'User {user_id} not in subscriptions')
        del subscriptions[subscriptions.index(user_id)]
        user.subscriptions = json.dumps(subscriptions)
        author = session.query(User).get(user_id)
        author.followers -= 1
        session.commit()
        return jsonify({'success': 'OK'})
avatar_form.py:
# data/avatar_form.py
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed, FileRequired
from wtforms import SubmitField

class AvatarForm(FlaskForm):
    avatar = FileField('Загрузить аватар', validators=[
        FileRequired(),
        FileAllowed(['jpg', 'jpeg', 'png', 'gif'], 'Разрешены только картинки!')
    ])
    submit = SubmitField('Upload')


static folder:
/fonts/
/pre/
/vid/
/img/
/avatars/
User with id 1 - admin
Main prompt:'сделай этьо все на ноде плиз'