Merge branch 'main' of https://github.com/john-david3/cs3305-team11
This commit is contained in:
@@ -71,7 +71,7 @@ const ForgotPasswordForm: React.FC<SubmitProps> = ({ onSubmit }) => {
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<div className="flex flex-col items-center p-[2.5rem]">
|
||||
<h1 className="text-white text-[1.5em] font-[800] md:text-[1.75em] lg:text-[2em]">
|
||||
<h1 className="text-white text-[1.4em] font-[800] md:text-[1.5em] lg:text-[1.75em]">
|
||||
Forgot Password
|
||||
</h1>
|
||||
<div className="mt-10 bg-white/10 backdrop-blur-md p-6 rounded-xl shadow-lg w-full max-w-[10em] min-w-[14em] border border-white/10 sm:max-w-[16em] md:max-w-[18em] lg:max-w-[20em]">
|
||||
|
||||
@@ -154,7 +154,7 @@ const LoginForm: React.FC<SubmitProps> = ({ onSubmit, onForgotPassword }) => {
|
||||
onClick={onForgotPassword}
|
||||
>
|
||||
<ForgotIcon size={16} className="flex flex-row mr-1" />
|
||||
<span className="text-[calc((1.5vw+1vh)/2)]">
|
||||
<span className="text-[0.6rem] 2lg:text-[0.75rem]">
|
||||
Forgot Password
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function GoogleLogin() {
|
||||
alt="Google logo"
|
||||
className="w-[2em] h-[2em] mr-2"
|
||||
/>
|
||||
<span className="flex-grow text-[calc((1.5vw+1.5vh)/2)]">
|
||||
<span className="flex-grow text-[0.75rem] 2lg:text-[1rem]">
|
||||
Sign in with Google
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -15,7 +15,7 @@ const Button: React.FC<ButtonProps> = ({
|
||||
return (
|
||||
<button
|
||||
type={type}
|
||||
className={`${extraClasses} p-2 text-[1.5vw] text-white hover:text-purple-600 bg-black/30 hover:bg-black/80 rounded-md border border-gray-300 hover:border-purple-500 hover:border-b-4 hover:border-l-4 active:border-b-2 active:border-l-2 transition-all`}
|
||||
className={`${extraClasses} p-2 text-[clamp(1rem, 1.5vw, 1.25rem)] text-white hover:text-purple-600 bg-black/30 hover:bg-black/80 rounded-md border border-gray-300 hover:border-purple-500 hover:border-b-4 hover:border-l-4 active:border-b-2 active:border-l-2 transition-all`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -119,7 +119,7 @@ const UserPage: React.FC = () => {
|
||||
}, [username]);
|
||||
|
||||
if (!profileData) return <LoadingScreen />;
|
||||
|
||||
console.log(isUser)
|
||||
return (
|
||||
<DynamicPageContent
|
||||
className={`min-h-screen text-white flex flex-col`}
|
||||
|
||||
@@ -93,22 +93,29 @@ http {
|
||||
expires -1d;
|
||||
}
|
||||
|
||||
#! Unused right now so the following are inaccurate locations
|
||||
# The thumbnails location
|
||||
# location ~ ^/stream/(.+)/thumbnails/(.+\.jpg)$ {
|
||||
# alias /stream_data/$1/thumbnails/$2;
|
||||
## The thumbnails location
|
||||
location ~ ^/stream/(.+)/thumbnails/(.+\.png)$ {
|
||||
alias /stream_data/stream/$1/$2;
|
||||
|
||||
# # The thumbnails should not be cacheable
|
||||
# expires -1d;
|
||||
# }
|
||||
# The thumbnails should not be cacheable
|
||||
expires -1d;
|
||||
}
|
||||
|
||||
# # The vods location
|
||||
# location ~ ^/stream/(.+)/vods/(.+\.mp4)$ {
|
||||
# alias /stream_data/$1/vods/$2;
|
||||
## The vods location
|
||||
location ~ ^/stream/(.+)/vods/(.+\.mp4)$ {
|
||||
alias /stream_data/vods/$1/$2;
|
||||
|
||||
# # The vods should not be cacheable
|
||||
# expires -1d;
|
||||
# }
|
||||
# The vods should not be cacheable
|
||||
expires -1d;
|
||||
}
|
||||
|
||||
## Profile pictures location
|
||||
location ~ ^/user/(.+)/profile_picture$ {
|
||||
alias /stream_data/profile_pictures/$1.png;
|
||||
|
||||
# The profile pictures should not be cacheable
|
||||
expires -1d;
|
||||
}
|
||||
|
||||
# location ~ ^/\?token=.*$ {
|
||||
# proxy_pass http://frontend:5173;
|
||||
|
||||
@@ -5,7 +5,7 @@ from utils.user_utils import get_user_id
|
||||
from blueprints.middleware import login_required
|
||||
from database.database import Database
|
||||
from datetime import datetime
|
||||
from celery_tasks import update_thumbnail, combine_ts_stream
|
||||
from celery_tasks.streaming import update_thumbnail, combine_ts_stream
|
||||
from dateutil import parser
|
||||
from utils.path_manager import PathManager
|
||||
import json
|
||||
@@ -198,7 +198,7 @@ def init_stream():
|
||||
|
||||
# Create necessary directories
|
||||
username = user_info["username"]
|
||||
create_local_directories(username)
|
||||
create_user_directories(username)
|
||||
|
||||
return redirect(f"/stream/{username}")
|
||||
|
||||
@@ -216,7 +216,6 @@ def publish_stream():
|
||||
periodically update thumbnail
|
||||
"""
|
||||
|
||||
|
||||
try:
|
||||
data = json.loads(request.form.get("data"))
|
||||
except json.JSONDecodeError as ex:
|
||||
|
||||
@@ -4,6 +4,8 @@ from utils.auth import *
|
||||
from utils.utils import get_category_id
|
||||
from blueprints.middleware import login_required
|
||||
from utils.email import send_email, forgot_password_body, newsletter_conf
|
||||
from utils.path_manager import PathManager
|
||||
from celery_tasks.streaming import convert_image_to_png
|
||||
import redis
|
||||
|
||||
from io import BytesIO
|
||||
@@ -14,6 +16,8 @@ r = redis.from_url(redis_url, decode_responses=True)
|
||||
|
||||
user_bp = Blueprint("user", __name__)
|
||||
|
||||
path_manager = PathManager()
|
||||
|
||||
@user_bp.route('/user/<string:username>')
|
||||
def user_data(username: str):
|
||||
"""
|
||||
@@ -42,13 +46,28 @@ def user_profile_picture_save():
|
||||
"""
|
||||
Saves user profile picture
|
||||
"""
|
||||
user_id = session.get("user_id")
|
||||
image = request.files['image']
|
||||
ext = image.filename.split('.')[-1]
|
||||
username = session.get("username")
|
||||
thumbnail_path = path_manager.get_profile_picture_file_path(username)
|
||||
|
||||
image.save(f"/web_server/stream_data/{user_id}.{ext}")
|
||||
# Check if the post request has the file part
|
||||
if 'image' not in request.files:
|
||||
return jsonify({"error": "No image found in request"}), 400
|
||||
|
||||
# Fetch image, convert to png, and save
|
||||
image = Image.open(request.files['image'])
|
||||
image.convert('RGB')
|
||||
image.save(thumbnail_path, "PNG")
|
||||
|
||||
return "Success", 200
|
||||
return jsonify({"message": "Profile picture saved"})
|
||||
|
||||
|
||||
@user_bp.route('/user/profile_picture/<string:username>')
|
||||
def user_profile_picture(username: str):
|
||||
"""
|
||||
Returns the profile picture of a user
|
||||
"""
|
||||
user_id = get_user_id(username)
|
||||
image = Image.open(f"/web_server/stream_data/{user_id}.jpg")
|
||||
|
||||
@login_required
|
||||
@user_bp.route('/user/same/<string:username>')
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
from celery import Celery, shared_task, Task
|
||||
from utils.stream_utils import generate_thumbnail, get_streamer_live_status
|
||||
from time import sleep
|
||||
from os import listdir, remove
|
||||
from datetime import datetime
|
||||
from celery_tasks.preferences import user_preferences
|
||||
import subprocess
|
||||
|
||||
def celery_init_app(app) -> Celery:
|
||||
class FlaskTask(Task):
|
||||
@@ -23,53 +17,3 @@ def celery_init_app(app) -> Celery:
|
||||
celery_app.set_default()
|
||||
app.extensions["celery"] = celery_app
|
||||
return celery_app
|
||||
|
||||
|
||||
@shared_task
|
||||
def update_thumbnail(user_id, stream_file, thumbnail_file, sleep_time) -> None:
|
||||
"""
|
||||
Updates the thumbnail of a stream periodically
|
||||
"""
|
||||
|
||||
if get_streamer_live_status(user_id)['is_live']:
|
||||
print("Updating thumbnail...")
|
||||
generate_thumbnail(stream_file, thumbnail_file)
|
||||
update_thumbnail.apply_async((user_id, stream_file, thumbnail_file, sleep_time), countdown=sleep_time)
|
||||
else:
|
||||
print("Stream has ended, stopping thumbnail updates")
|
||||
|
||||
@shared_task
|
||||
def combine_ts_stream(stream_path, vods_path, vod_file_name):
|
||||
"""
|
||||
Combines all ts files into a single vod, and removes the ts files
|
||||
"""
|
||||
ts_files = [f for f in listdir(stream_path) if f.endswith(".ts")]
|
||||
ts_files.sort()
|
||||
|
||||
# Create temp file listing all ts files
|
||||
with open(f"{stream_path}/list.txt", "w") as f:
|
||||
for ts_file in ts_files:
|
||||
f.write(f"file '{ts_file}'\n")
|
||||
|
||||
# Concatenate all ts files into a single vod
|
||||
|
||||
vod_command = [
|
||||
"ffmpeg",
|
||||
"-f",
|
||||
"concat",
|
||||
"-safe",
|
||||
"0",
|
||||
"-i",
|
||||
f"{stream_path}/list.txt",
|
||||
"-c",
|
||||
"copy",
|
||||
f"{vods_path}/{vod_file_name}.mp4"
|
||||
]
|
||||
|
||||
subprocess.run(vod_command)
|
||||
|
||||
# Remove ts files
|
||||
for ts_file in ts_files:
|
||||
remove(f"{stream_path}/{ts_file}")
|
||||
# Remove m3u8 file
|
||||
remove(f"{stream_path}/index.m3u8")
|
||||
71
web_server/celery_tasks/streaming.py
Normal file
71
web_server/celery_tasks/streaming.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from celery import Celery, shared_task, Task
|
||||
from datetime import datetime
|
||||
from celery_tasks.preferences import user_preferences
|
||||
from utils.stream_utils import generate_thumbnail, get_streamer_live_status
|
||||
from time import sleep
|
||||
from os import listdir, remove
|
||||
import subprocess
|
||||
|
||||
@shared_task
|
||||
def update_thumbnail(user_id, stream_file, thumbnail_file, sleep_time) -> None:
|
||||
"""
|
||||
Updates the thumbnail of a stream periodically
|
||||
"""
|
||||
|
||||
if get_streamer_live_status(user_id)['is_live']:
|
||||
print("Updating thumbnail...")
|
||||
generate_thumbnail(stream_file, thumbnail_file)
|
||||
update_thumbnail.apply_async((user_id, stream_file, thumbnail_file, sleep_time), countdown=sleep_time)
|
||||
else:
|
||||
print("Stream has ended, stopping thumbnail updates")
|
||||
|
||||
@shared_task
|
||||
def combine_ts_stream(stream_path, vods_path, vod_file_name):
|
||||
"""
|
||||
Combines all ts files into a single vod, and removes the ts files
|
||||
"""
|
||||
ts_files = [f for f in listdir(stream_path) if f.endswith(".ts")]
|
||||
ts_files.sort()
|
||||
|
||||
# Create temp file listing all ts files
|
||||
with open(f"{stream_path}/list.txt", "w") as f:
|
||||
for ts_file in ts_files:
|
||||
f.write(f"file '{ts_file}'\n")
|
||||
|
||||
# Concatenate all ts files into a single vod
|
||||
|
||||
vod_command = [
|
||||
"ffmpeg",
|
||||
"-f",
|
||||
"concat",
|
||||
"-safe",
|
||||
"0",
|
||||
"-i",
|
||||
f"{stream_path}/list.txt",
|
||||
"-c",
|
||||
"copy",
|
||||
f"{vods_path}/{vod_file_name}.mp4"
|
||||
]
|
||||
|
||||
subprocess.run(vod_command)
|
||||
|
||||
# Remove ts files
|
||||
for ts_file in ts_files:
|
||||
remove(f"{stream_path}/{ts_file}")
|
||||
# Remove m3u8 file
|
||||
remove(f"{stream_path}/index.m3u8")
|
||||
|
||||
@shared_task
|
||||
def convert_image_to_png(image_path, png_path):
|
||||
"""
|
||||
Converts an image to a png
|
||||
"""
|
||||
image_command = [
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-i",
|
||||
image_path,
|
||||
png_path
|
||||
]
|
||||
|
||||
subprocess.run(image_command)
|
||||
@@ -1,20 +1,53 @@
|
||||
import os
|
||||
# Description: This file contains the PathManager class which is responsible for managing the paths of the stream data.
|
||||
|
||||
class PathManager():
|
||||
def __init__(self) -> None:
|
||||
self.root_path = "stream_data"
|
||||
self.vods_path = os.path.join(self.root_path, "vods")
|
||||
self.stream_path = os.path.join(self.root_path, "stream")
|
||||
self.profile_pictures_path = os.path.join(self.root_path, "profile_pictures")
|
||||
|
||||
self._create_root_directories()
|
||||
|
||||
def _create_root_directories(self):
|
||||
"""
|
||||
Create directories for stream data if they do not exist
|
||||
"""
|
||||
if not os.path.exists(self.vods_path):
|
||||
os.makedirs(self.vods_path)
|
||||
|
||||
if not os.path.exists(self.stream_path):
|
||||
os.makedirs(self.stream_path)
|
||||
|
||||
if not os.path.exists(self.profile_pictures_path):
|
||||
os.makedirs(self.profile_pictures_path)
|
||||
|
||||
# Fix permissions
|
||||
os.chmod(self.vods_path, 0o777)
|
||||
os.chmod(self.stream_path, 0o777)
|
||||
os.chmod(self.profile_pictures_path, 0o777)
|
||||
|
||||
def get_vods_path(self, username):
|
||||
return f"stream_data/vods/{username}"
|
||||
return os.path.join(self.vods_path, username)
|
||||
|
||||
def get_stream_path(self, username):
|
||||
return f"stream_data/stream/{username}"
|
||||
return os.path.join(self.stream_path, username)
|
||||
|
||||
def get_stream_file_path(self, username):
|
||||
return f"{self.get_stream_path(username)}/index.m3u8"
|
||||
return os.path.join(self.get_stream_path(username), "index.m3u8")
|
||||
|
||||
def get_current_stream_thumbnail_file_path(self, username):
|
||||
return f"{self.get_stream_path(username)}/index.jpg"
|
||||
return os.path.join(self.get_stream_path(username), "index.png")
|
||||
|
||||
def get_vod_file_path(self, username, vod_id):
|
||||
return f"{self.get_vods_path(username)}/{vod_id}.mp4"
|
||||
return os.path.join(self.get_vods_path(username), f"{vod_id}.mp4")
|
||||
|
||||
def get_vod_thumbnail_file_path(self, username, vod_id):
|
||||
return f"{self.get_vods_path(username)}/{vod_id}.jpg"
|
||||
return os.path.join(self.get_vods_path(username), f"{vod_id}.png")
|
||||
|
||||
def get_profile_picture_file_path(self, username):
|
||||
return os.path.join(self.profile_pictures_path, f"{username}.png")
|
||||
|
||||
def get_profile_picture_path(self):
|
||||
return self.profile_pictures_path
|
||||
@@ -151,7 +151,7 @@ def get_vod_tags(vod_id: int):
|
||||
""", (vod_id,))
|
||||
return tags
|
||||
|
||||
def create_local_directories(username: str):
|
||||
def create_user_directories(username: str):
|
||||
"""
|
||||
Create directories for user stream data if they do not exist
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user