|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Reddit Video Processor</title>
|
|
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
</head>
|
|
<body class="bg-gray-100">
|
|
|
|
<nav class="bg-white shadow">
|
|
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
|
|
<h1 class="text-xl font-bold">Reddit Video Processor</h1>
|
|
<button id="openSettings" class="bg-blue-500 text-white px-4 py-2 rounded">
|
|
Settings
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
|
|
|
|
<div class="container mx-auto px-4 py-6">
|
|
|
|
<div class="mb-8">
|
|
<h2 class="text-2xl font-semibold mb-4">Available Videos</h2>
|
|
<div id="videoList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
|
|
</div>
|
|
<button
|
|
id="processBtn"
|
|
class="mt-4 bg-green-500 text-white px-6 py-3 rounded"
|
|
>
|
|
Start Processing Selected Videos
|
|
</button>
|
|
</div>
|
|
|
|
|
|
<div class="mb-8">
|
|
<h2 class="text-2xl font-semibold mb-4">Processed Videos</h2>
|
|
<div id="resultsList" class="space-y-4">
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="mb-8">
|
|
<h2 class="text-2xl font-semibold mb-4">Live Console Output</h2>
|
|
<div id="consoleOutput" class="bg-black text-green-400 p-4 text-xs h-64 overflow-y-scroll rounded">
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div
|
|
id="settingsModal"
|
|
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 hidden"
|
|
>
|
|
<div class="bg-white p-6 rounded shadow-lg w-1/3">
|
|
<h2 class="text-xl font-bold mb-4">Settings</h2>
|
|
<div>
|
|
<label class="block mb-2">Subreddits (comma separated):</label>
|
|
<input
|
|
id="subredditsInput"
|
|
type="text"
|
|
class="w-full border border-gray-300 rounded px-3 py-2"
|
|
/>
|
|
</div>
|
|
<div class="mt-4 flex justify-end">
|
|
<button
|
|
id="saveSettings"
|
|
class="bg-blue-500 text-white px-4 py-2 rounded mr-2"
|
|
>
|
|
Save
|
|
</button>
|
|
<button
|
|
id="closeSettings"
|
|
class="bg-gray-300 text-gray-700 px-4 py-2 rounded"
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
const videoList = document.getElementById("videoList");
|
|
const processBtn = document.getElementById("processBtn");
|
|
const resultsList = document.getElementById("resultsList");
|
|
const settingsModal = document.getElementById("settingsModal");
|
|
const openSettings = document.getElementById("openSettings");
|
|
const closeSettings = document.getElementById("closeSettings");
|
|
const saveSettings = document.getElementById("saveSettings");
|
|
const subredditsInput = document.getElementById("subredditsInput");
|
|
const consoleOutput = document.getElementById("consoleOutput");
|
|
|
|
let availableVideos = [];
|
|
let selectedVideoIds = new Set();
|
|
|
|
|
|
async function fetchVideos() {
|
|
const res = await fetch("/api/videos");
|
|
availableVideos = await res.json();
|
|
renderVideoList();
|
|
}
|
|
|
|
function renderVideoList() {
|
|
videoList.innerHTML = "";
|
|
availableVideos.forEach((video) => {
|
|
const card = document.createElement("div");
|
|
card.className = "bg-white p-4 rounded shadow flex flex-col justify-between";
|
|
|
|
|
|
const videoContainer = document.createElement("div");
|
|
videoContainer.className = "relative w-full rounded overflow-hidden";
|
|
videoContainer.style.aspectRatio = "9 / 16";
|
|
|
|
|
|
const previewVideo = document.createElement("video");
|
|
previewVideo.className = "absolute top-0 left-0 w-full h-full object-cover";
|
|
previewVideo.playsInline = true;
|
|
previewVideo.controls = true;
|
|
if (video.preview_video) {
|
|
previewVideo.src = video.preview_video;
|
|
} else {
|
|
previewVideo.poster = video.thumbnail;
|
|
previewVideo.src = "";
|
|
}
|
|
videoContainer.appendChild(previewVideo);
|
|
card.appendChild(videoContainer);
|
|
|
|
|
|
const title = document.createElement("h3");
|
|
title.className = "mt-2 font-semibold";
|
|
title.textContent = video.title;
|
|
card.appendChild(title);
|
|
|
|
|
|
const info = document.createElement("p");
|
|
info.className = "text-sm text-gray-600";
|
|
info.textContent = `Subreddit: ${video.subreddit} | Upvotes: ${video.ups}`;
|
|
card.appendChild(info);
|
|
|
|
|
|
if (video.duration) {
|
|
const durationText = document.createElement("p");
|
|
durationText.className = "text-sm text-gray-600";
|
|
durationText.textContent = `Duration: ${video.duration} sec`;
|
|
card.appendChild(durationText);
|
|
}
|
|
|
|
|
|
const checkbox = document.createElement("input");
|
|
checkbox.type = "checkbox";
|
|
checkbox.className = "mt-2";
|
|
checkbox.addEventListener("change", (e) => {
|
|
if (e.target.checked) {
|
|
selectedVideoIds.add(video.id);
|
|
} else {
|
|
selectedVideoIds.delete(video.id);
|
|
}
|
|
});
|
|
card.appendChild(checkbox);
|
|
videoList.appendChild(card);
|
|
});
|
|
}
|
|
|
|
processBtn.addEventListener("click", async () => {
|
|
const selectedVideos = availableVideos.filter((video) =>
|
|
selectedVideoIds.has(video.id)
|
|
);
|
|
if (selectedVideos.length === 0) {
|
|
alert("Please select at least one video to process.");
|
|
return;
|
|
}
|
|
const res = await fetch("/api/process", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(selectedVideos),
|
|
});
|
|
const data = await res.json();
|
|
console.log(data);
|
|
|
|
startResultsPolling();
|
|
});
|
|
|
|
|
|
let pollingInterval;
|
|
function startResultsPolling() {
|
|
|
|
if (pollingInterval) clearInterval(pollingInterval);
|
|
pollingInterval = setInterval(async () => {
|
|
const res = await fetch("/api/results");
|
|
const results = await res.json();
|
|
renderResults(results);
|
|
|
|
const pendingExists = Object.values(results).some(
|
|
(r) => r.status === "pending"
|
|
);
|
|
if (!pendingExists && Object.keys(results).length > 0) {
|
|
clearInterval(pollingInterval);
|
|
}
|
|
}, 5000);
|
|
}
|
|
|
|
function renderResults(results) {
|
|
resultsList.innerHTML = "";
|
|
for (const id in results) {
|
|
const result = results[id];
|
|
const container = document.createElement("div");
|
|
container.className = "bg-white p-4 rounded shadow";
|
|
|
|
const header = document.createElement("div");
|
|
header.className = "flex justify-between items-center";
|
|
const title = document.createElement("h3");
|
|
title.className = "font-semibold text-lg";
|
|
title.textContent = result.generated_title || "Processing...";
|
|
header.appendChild(title);
|
|
|
|
|
|
if (result.generated_title) {
|
|
const titleCopyBtn = document.createElement("button");
|
|
titleCopyBtn.textContent = "Copy Title";
|
|
titleCopyBtn.className = "bg-blue-500 text-white px-2 py-1 rounded ml-2";
|
|
titleCopyBtn.addEventListener("click", () => {
|
|
navigator.clipboard.writeText(result.generated_title);
|
|
});
|
|
header.appendChild(titleCopyBtn);
|
|
}
|
|
container.appendChild(header);
|
|
|
|
if (result.status === "completed") {
|
|
|
|
const videoContainer = document.createElement("div");
|
|
videoContainer.className = "max-w-md mx-auto mt-2";
|
|
const videoElem = document.createElement("video");
|
|
videoElem.src = result.video_url;
|
|
videoElem.controls = true;
|
|
videoElem.className = "w-full";
|
|
videoElem.style.maxHeight = "500px";
|
|
|
|
|
|
videoElem.setAttribute("draggable", "true");
|
|
videoElem.addEventListener("dragstart", (e) => {
|
|
|
|
const fullUrl = window.location.origin + result.video_url;
|
|
|
|
const fileName = "video_" + id + ".mp4";
|
|
|
|
const downloadURLData = "video/mp4:" + fileName + ":" + fullUrl;
|
|
e.dataTransfer.setData("DownloadURL", downloadURLData);
|
|
});
|
|
|
|
videoContainer.appendChild(videoElem);
|
|
container.appendChild(videoContainer);
|
|
|
|
|
|
const captionDiv = document.createElement("div");
|
|
captionDiv.className = "mt-2 flex items-center";
|
|
const captionText = document.createElement("p");
|
|
captionText.textContent = result.generated_caption;
|
|
captionText.className = "flex-1";
|
|
captionDiv.appendChild(captionText);
|
|
const copyBtn = document.createElement("button");
|
|
copyBtn.textContent = "Copy Caption";
|
|
copyBtn.className = "bg-blue-500 text-white px-2 py-1 rounded ml-2";
|
|
copyBtn.addEventListener("click", () => {
|
|
navigator.clipboard.writeText(result.generated_caption);
|
|
});
|
|
captionDiv.appendChild(copyBtn);
|
|
container.appendChild(captionDiv);
|
|
} else if (result.status === "error") {
|
|
const errorMsg = document.createElement("p");
|
|
errorMsg.textContent = "Error: " + result.error;
|
|
errorMsg.className = "text-red-500 mt-2";
|
|
container.appendChild(errorMsg);
|
|
} else {
|
|
const pendingMsg = document.createElement("p");
|
|
pendingMsg.textContent = "Processing...";
|
|
pendingMsg.className = "text-gray-600 mt-2";
|
|
container.appendChild(pendingMsg);
|
|
}
|
|
|
|
|
|
if (result.status === "completed") {
|
|
const deleteBtn = document.createElement("button");
|
|
deleteBtn.textContent = "Delete Video";
|
|
deleteBtn.className = "bg-red-500 text-white px-2 py-1 rounded mt-2";
|
|
deleteBtn.addEventListener("click", async () => {
|
|
if (confirm("Are you sure you want to delete this video?")) {
|
|
const res = await fetch(`/api/video/${id}`, {
|
|
method: "DELETE",
|
|
});
|
|
if (res.ok) {
|
|
container.remove();
|
|
} else {
|
|
alert("Failed to delete video.");
|
|
}
|
|
}
|
|
});
|
|
container.appendChild(deleteBtn);
|
|
}
|
|
|
|
resultsList.appendChild(container);
|
|
}
|
|
}
|
|
|
|
|
|
openSettings.addEventListener("click", async () => {
|
|
const res = await fetch("/api/settings");
|
|
const settings = await res.json();
|
|
subredditsInput.value = settings.subreddits.join(", ");
|
|
settingsModal.classList.remove("hidden");
|
|
});
|
|
closeSettings.addEventListener("click", () => {
|
|
settingsModal.classList.add("hidden");
|
|
});
|
|
saveSettings.addEventListener("click", async () => {
|
|
const newSubs = subredditsInput.value
|
|
.split(",")
|
|
.map((s) => s.trim())
|
|
.filter((s) => s);
|
|
const res = await fetch("/api/settings", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ subreddits: newSubs }),
|
|
});
|
|
const data = await res.json();
|
|
console.log(data);
|
|
settingsModal.classList.add("hidden");
|
|
|
|
fetchVideos();
|
|
});
|
|
|
|
|
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
const logsSocket = new WebSocket(`${protocol}//${location.host}/ws/logs`);
|
|
logsSocket.onmessage = function (event) {
|
|
const newLog = document.createElement("div");
|
|
newLog.textContent = event.data;
|
|
consoleOutput.appendChild(newLog);
|
|
|
|
consoleOutput.scrollTop = consoleOutput.scrollHeight;
|
|
};
|
|
|
|
|
|
fetchVideos();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|