Skip to content

Contributors #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/discord.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ NeTube/
- **Улучшение дизайна:** Сделайте интерфейс более привлекательным и удобным.
- **Расширенная система поиска:** Внедрите более мощный поиск видео по названию, описанию, тегам.

**Лицензия:** [[MIT]](https://github.com/SL1dee36/NeTube/blob/main/LICENSE)
**Лицензия:**

[MIT]


![image](https://github.com/user-attachments/assets/b7629010-373d-4c55-8780-b8cdce19d24d)
Expand Down
57 changes: 57 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# app.py
import traceback

from flask import Flask, render_template
from markupsafe import Markup

from helpers.utils import get_current_user
from models import db
import logging
from config import Config
from routes import register_routes

app = Flask(__name__)
app.config.from_object(Config)
db.init_app(app)

# Конфигурация логгера
logging.basicConfig(level=logging.DEBUG)

# Register routes
register_routes(app)


# Регистрация обработчиков ошибок
@app.errorhandler(400)
def bad_request_error(error):
current_user = get_current_user()
return render_template('errors/400.html',current_user=current_user, Markup=Markup), 400

@app.errorhandler(401)
def unauthorized_error(error):
current_user = get_current_user()
return render_template('errors/401.html',current_user=current_user, Markup=Markup), 401

@app.errorhandler(403)
def forbidden_error(error):
current_user = get_current_user()
return render_template('errors/403.html',current_user=current_user, Markup=Markup), 403

@app.errorhandler(404)
def not_found_error(error):
current_user = get_current_user()
return render_template('errors/404.html',current_user=current_user, Markup=Markup), 404


@app.errorhandler(500)
def internal_server_error(error):
current_user = get_current_user()

return render_template('errors/500.html', current_user=current_user, Markup=Markup, error=error), 500



if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
15 changes: 15 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import os

class Config:
SQLALCHEMY_DATABASE_URI = 'sqlite:///netube.db'
SQLALCHEMY_TRACK_MODIFICATIONS = False
UPLOAD_FOLDER = os.path.join(os.getcwd(), 'static/videos')
THUMBNAIL_FOLDER = os.path.join(os.getcwd(), 'static/thumbnails')
ICONS_FOLDER = os.path.join(os.getcwd(), 'static/avatars')
AVATARS_FOLDER = os.path.join(os.getcwd(), 'static/avatars/users')
VIDEOS_FOLDER = os.path.join(os.getcwd(), 'static/videos')
MAX_CONTENT_LENGTH = 8 * 1024 * 1024 * 1024 # 8GB
SECRET_KEY = 'your_secret_key' # Change to a secure key
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'mp4'}
ADMIN_SECRET_KEY = "123" # Change this in production

82 changes: 82 additions & 0 deletions helpers/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# utils.py

import random
import string
from flask import session
from fuzzywuzzy import process, fuzz
from moviepy.editor import VideoFileClip
from PIL import Image

from config import Config
from models import User, Video # Импортируйте модель User



def allowed_file(filename):
"""Проверяет, является ли файл допустимого формата."""
return '.' in filename and filename.rsplit('.', 1)[1].lower() in Config.ALLOWED_EXTENSIONS


def generate_thumbnail(video_path, thumbnail_path):
"""
Генерирует миниатюру видео, сохраняя ее в нужном размере и соотношении сторон.

Args:
video_path (str): Путь к видеофайлу.
thumbnail_path (str): Путь для сохранения миниатюры.
"""
video = VideoFileClip(video_path)
frame = video.get_frame(t=0.0)
image = Image.fromarray(frame)
width, height = image.size

# Обрезка для сохранения соотношения 16:9
if width / height != 16 / 9:
crop_width = width if width > height else int(height * 16 / 9)
crop_height = int(crop_width * 9 / 16) if width > height else height
left_offset = (width - crop_width) // 2
top_offset = (height - crop_height) // 2
image = image.crop((left_offset, top_offset, left_offset + crop_width, top_offset + crop_height))

# Сжатие до 1280x720, если необходимо
if width > 1280 or height > 720:
image = image.resize((1280, 720))

image.save(thumbnail_path)


def generate_random_name(length=10):
"""Генерирует случайное имя заданной длины из букв и цифр."""
letters = string.ascii_letters + string.digits
return ''.join(random.choice(letters) for _ in range(length))


def get_current_user():
"""Возвращает текущего пользователя на основе данных сессии."""
user_id = session.get('user_id')
if user_id:
return User.query.get(user_id)
return None


def search_videos(search_query, videos):
"""
Выполняет нечеткий поиск видео по названию.

Args:
search_query (str): Поисковый запрос.
videos (list): Список видео объектов.

Returns:
list: Список найденных видео.
"""
# Получаем заголовки видео для поиска
video_titles = [(video, video.title) for video in videos]

# Выполняем нечеткий поиск по названиям видео
matches = process.extract(search_query, [title for video, title in video_titles], scorer=fuzz.token_sort_ratio)

# Оставляем только те результаты, где совпадение больше или равно 20
search_results = [video for (video, title), score in zip(video_titles, matches) if score[1] >= 20]

return search_results
Binary file removed images/demo (1).png
Binary file not shown.
Binary file removed images/demo (2).png
Binary file not shown.
Binary file removed instance/netube.db
Binary file not shown.
107 changes: 53 additions & 54 deletions src/models.py → models.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,54 @@
from flask_sqlalchemy import SQLAlchemy
import datetime
from datetime import datetime, timezone

db = SQLAlchemy()

# Association table for subscriptions
subscriptions = db.Table('subscriptions',
db.Column('subscriber_id', db.Integer, db.ForeignKey('user.id')),
db.Column('subscribed_to_id', db.Integer, db.ForeignKey('user.id'))
)

class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
avatar = db.Column(db.String(255), nullable=True)
videos = db.relationship('Video', backref='author', lazy=True)
# Relationships for subscribers and subscriptions
subscribed_to = db.relationship('User', secondary='subscriptions',
primaryjoin=(subscriptions.c.subscriber_id == id),
secondaryjoin=(subscriptions.c.subscribed_to_id == id),
backref=db.backref('subscribers', lazy='dynamic'),
lazy='dynamic')

class Video(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), nullable=False)
url = db.Column(db.String(200), nullable=False)
description = db.Column(db.Text(500), nullable=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
likes = db.Column(db.Integer, default=0)
dislikes = db.Column(db.Integer, default=0)
comments = db.relationship('Comment', lazy=True) # Removed backref
views = db.Column(db.Integer, default=0)

class Like(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
video_id = db.Column(db.Integer, db.ForeignKey('video.id'), nullable=False)

class DisLike(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
video_id = db.Column(db.Integer, db.ForeignKey('video.id'), nullable=False)

class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.now(timezone.utc))
user = db.relationship('User', backref='comments')

video_id = db.Column(db.Integer, db.ForeignKey('video.id'), nullable=False)
from flask_sqlalchemy import SQLAlchemy
import datetime
from datetime import datetime, timezone

db = SQLAlchemy()

# Association table for subscriptions
subscriptions = db.Table('subscriptions',
db.Column('subscriber_id', db.Integer, db.ForeignKey('user.id')),
db.Column('subscribed_to_id', db.Integer, db.ForeignKey('user.id'))
)

class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
avatar = db.Column(db.String(255), nullable=True)
videos = db.relationship('Video', backref='author', lazy=True)
# Relationships for subscribers and subscriptions
subscribed_to = db.relationship('User', secondary='subscriptions',
primaryjoin=(subscriptions.c.subscriber_id == id),
secondaryjoin=(subscriptions.c.subscribed_to_id == id),
backref=db.backref('subscribers', lazy='dynamic'),
lazy='dynamic')

class Video(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), nullable=False)
url = db.Column(db.String(200), nullable=False)
description = db.Column(db.Text(500), nullable=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
likes = db.Column(db.Integer, default=0)
dislikes = db.Column(db.Integer, default=0)
comments = db.relationship('Comment', lazy=True) # Removed backref
views = db.Column(db.Integer, default=0)
class Like(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
video_id = db.Column(db.Integer, db.ForeignKey('video.id'), nullable=False)

class DisLike(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
video_id = db.Column(db.Integer, db.ForeignKey('video.id'), nullable=False)

class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.now(timezone.utc))
user = db.relationship('User', backref='comments')

video_id = db.Column(db.Integer, db.ForeignKey('video.id'), nullable=False)
video = db.relationship('Video')
19 changes: 19 additions & 0 deletions routes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from .channel_routes import channel_bp
from .main_routes import main_bp
from .static_routes import static_bp
from .video_like_routes import video_like_bp
from .video_routes import video_bp
from .subscription_routes import subscription_bp
from .user_routes import user_bp
from .admin_routes import admin_bp

def register_routes(app):
app.register_blueprint(video_bp)
app.register_blueprint(user_bp)
app.register_blueprint(admin_bp)
app.register_blueprint(main_bp)
app.register_blueprint(channel_bp)
app.register_blueprint(subscription_bp)
app.register_blueprint(static_bp)
app.register_blueprint(video_like_bp)

Loading