{% extends "base.html" %}

{% import 'macros.html' as macros %}
{% 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>
            <hr>

    <hr>

    <hr>

    <h3>Комментарии</h3>

    <!-- Форма для добавления комментария -->
    {% if current_user.is_authenticated %}
        <form action="{{ url_for('add_comment', video_id=video_id) }}" method="post">
            {{ form.hidden_tag() }}
            <div class="mb-3">
                {{ form.content.label }}
                {{ form.content(class="form-control", rows="3") }}
                {% for error in form.content.errors %}
                    <div class="text-danger">{{ error }}</div>
                {% endfor %}
            </div>
            <button type="submit" class="btn btn-primary">Отправить</button>
        </form>
    {% else %}
        <p>Пожалуйста, <a href="{{ url_for('login') }}">войдите</a>, чтобы оставить комментарий.</p>
    {% endif %}

    <hr>

    <!-- Список комментариев -->
    {% for comment in comments %}
        {{ macros.render_comment(comment, form) }}
    {% endfor %}

    <script>
        function showReplyForm(commentId) {
            const form = document.getElementById('reply-form-' + commentId);
            if (form.style.display === 'none') {
                form.style.display = 'block';
            } else {
                form.style.display = 'none';
            }
        }
    </script>


    <script>
        function showReplyForm(commentId) {
            const form = document.getElementById('reply-form-' + commentId);
            if (form.style.display === 'none') {
                form.style.display = 'block';
            } else {
                form.style.display = 'none';
            }
        }
    </script>


    <style>
        .comment {
            border: 1px solid #ddd;
            padding: 10px;
            border-radius: 5px;
            margin-bottom: 10px;
        }
        .comment .avatar {
            float: left;
            margin-right: 10px;
        }
        .comment .content {
            overflow: hidden;
        }
        .comment .actions {
            margin-top: 5px;
        }
    </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/{{ 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 content %}
This is my video.html
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
from data.comments import Comment
from data.comments import Comment  # Импорт модели комментариев
from data.comment_form import CommentForm  # Импорт формы комментариев
from sqlalchemy import desc
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
)
login_manager.init_app(app)

# 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')

@login_manager.user_loader
def load_user(user_id):
    return db_sess.query(User).get(int(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.route('/add_comment/<int:video_id>', methods=['POST'])
def add_comment(video_id):
    """Добавляет новый комментарий к видео"""
    if not current_user.is_authenticated:
        flash('Только авторизованные пользователи могут оставлять комментарии.', 'danger')
        return redirect(url_for('video', video_id=video_id))

    form = CommentForm()
    if form.validate_on_submit():
        db_sess = db_session.create_session()
        comment = Comment()
        comment.video_id = video_id
        comment.user_id = current_user.id
        comment.content = form.content.data
        parent_id = request.form.get('parent_id')
        if parent_id:
            parent_comment = db_sess.query(Comment).get(int(parent_id))
            if parent_comment:
                comment.parent_id = parent_comment.id
        db_sess.add(comment)
        db_sess.commit()
        flash('Комментарий добавлен успешно!', 'success')
    else:
        for field, errors in form.errors.items():
            for error in errors:
                flash(f"Ошибка в поле '{field}': {error}", 'danger')

    return redirect(url_for('video', video_id=video_id))
@app.route('/like_comment/<int:comment_id>', methods=['POST'])
def like_comment(comment_id):
    """Лайкает комментарий"""
    if not current_user.is_authenticated:
        flash('Только авторизованные пользователи могут ставить лайки.', 'danger')
        return redirect(request.referrer or url_for('index'))

    db_sess = db_session.create_session()
    comment = db_sess.query(Comment).get(comment_id)
    if comment:
        comment.likes += 1
        db_sess.commit()
        flash('Вы поставили лайк этому комментарию.', 'success')
    else:
        flash('Комментарий не найден.', 'danger')

    return redirect(request.referrer or url_for('index'))
@app.route('/pin_comment/<int:comment_id>', methods=['POST'])
def pin_comment(comment_id):
    """Закрепляет или открепляет комментарий"""
    if not current_user.is_authenticated:
        flash('Только авторизованные пользователи могут закреплять комментарии.', 'danger')
        return redirect(request.referrer or url_for('index'))

    db_sess = db_session.create_session()
    comment = db_sess.query(Comment).get(comment_id)
    if comment and comment.user_id == current_user.id:
        comment.pinned = not comment.pinned
        db_sess.commit()
        status = 'закреплен' if comment.pinned else 'откреплен'
        flash(f'Комментарий {status}.', 'success')
    else:
        flash('Вы не можете закрепить этот комментарий.', 'danger')

    return redirect(request.referrer or url_for('index'))

@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('')


def get_replies(comment, db_sess, current_depth=1, max_depth=3):
    """Рекурсивно получает ответы на комментарий с ограничением глубины и устанавливает depth."""
    if current_depth > max_depth:
        return []
    replies = db_sess.query(Comment).filter(Comment.parent_id == comment.id).order_by(desc(Comment.pinned), desc(Comment.datetime)).all()
    for reply in replies:
        reply.depth = current_depth  # Устанавливаем глубину
        reply.replies = get_replies(reply, db_sess, current_depth + 1, max_depth)
    return replies

@app.route('/video/<int:video_id>', methods=['GET', 'POST'])
def video(video_id):
    """Возвращает страницу с видео и комментариями"""
    current_video = db_sess.query(Video).get(video_id)
    if current_video is None:
        return page_not_found('')
    all_videos = db_sess.query(Video).all()
    if len(all_videos) > 3:
        last_videos = 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

    # Получение верхнеуровневых комментариев
    comments = db_sess.query(Comment).filter(Comment.video_id == video_id, Comment.parent_id == None).order_by(desc(Comment.pinned), desc(Comment.datetime)).all()

    # Установка depth для верхнеуровневых комментариев и получение их ответов
    for comment in comments:
        comment.depth = 0  # Верхнеуровневые комментарии имеют depth = 0
        comment.replies = get_replies(comment, db_sess)

    # Создание формы комментариев
    form = CommentForm()

    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,
        'authenticated': current_user.is_authenticated,

        'avatar_url': url_for('static', filename=f'avatars/{author.avatar}'),
        'comments': comments,
        'form': form  # Передача формы в шаблон
    }
    return render_template('video.html', **params, current_user = 'current_user')

@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 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]
    subscription = False
    if current_user.is_authenticated:
        subscriptions = json.loads(current_user.subscriptions)
        if user_id in subscriptions:
            subscription = True
    params = {
        'title': f'{user.name} {user.surname}',
        'user': user,
        'videos': [videos[i * 3:i * 3 + 3] for i in range(len(videos) // 3)] +
                  [videos[(len(videos) // 3) * 3:]],
        'subscription': subscription,
        'empty': True if len(videos) == 0 else False,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('user.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': db_sess.query(Video).get(i),
            'author': db_sess.query(User).get(db_sess.query(Video).get(i).author)
        }
        for i in likes
    ]
    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)

@app.route('/about')
def about():
    params = {
        'title': f'Про сайт'

    }
    return render_template('about.html', **params)
@app.route('/search')
def search():
    """Возвращает страницу с результатами поиска видео"""
    query = request.args['search']
    videos = db_sess.query(Video).filter(Video.title == query).all()
    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__':
    app.run(debug=True, port=8080, host='0.0.0.0')
and this is my main.py