diff --git a/app.py b/app.py
index b5f072d..363b79b 100644
--- a/app.py
+++ b/app.py
@@ -14,23 +14,33 @@
CONFIG_FILE = 'config.json'
config = load_config(CONFIG_FILE)
+transcoding_tasks = {}
+
@app.route('/')
def index():
return render_template('index.html')
@app.route("/single")
def single_media():
- # Получаем путь из строки запроса
- path = request.args.get("path", "") # Значение параметра "path"
+ path = request.args.get("path", "")
if not path:
return "Path not provided", 400
- # Декодируем путь
path_to_mediafile = unquote(path)
media_file = get_single_media_by_path(path_to_mediafile)
- # return jsonify(media_file)
+
return render_template('single.html', file=media_file)
+@app.route("/single-json")
+def single_media_json():
+ path = request.args.get("path", "")
+ if not path:
+ return "Path not provided", 400
+
+ path_to_mediafile = unquote(path)
+
+ return jsonify(get_single_media_by_path(path_to_mediafile))
+
@app.route('/media-list/clear-cache')
def media_list_clear_cache():
return jsonify({"status": media_remove_cache(config["cache_dir"])})
@@ -75,11 +85,18 @@
dest_dir = config.get('transcoded_directory', 'transcoded_files')
os.makedirs(dest_dir, exist_ok=True)
dest_path = os.path.join(dest_dir, os.path.basename(file_path))
+ socketio.emit("progress", {
+ "task": {
+ "file": file_path
+ },
+ "message": f"Copying the original file to {dest_path}. Waiting"
+ })
shutil.copy(file_path, dest_path)
# Step 2: Start transcoding
socketio.start_background_task(
target=transcode_file,
+ transcoding_tasks=transcoding_tasks,
socketio=socketio,
file_path=file_path,
codec=codec,
@@ -91,6 +108,18 @@
return jsonify({"status": "processing started", "file": file_path}), 202
+@app.route('/stop-transcoding', methods=['GET'])
+def stop_transcoding():
+ task_id = request.args.get("task_id")
+
+ if task_id in transcoding_tasks:
+ task = transcoding_tasks.pop(task_id, None)
+ if task:
+ task["process"].terminate()
+ return jsonify({"status": "stopped", "task_id": task_id})
+
+ return jsonify({"status": "error", "message": "Task not found"}), 404
+
@app.route('/configure', methods=['GET', 'POST'])
def configure():
if request.method == 'POST':
diff --git a/mediascan.py b/mediascan.py
index b3bc0dc..e27f3d1 100644
--- a/mediascan.py
+++ b/mediascan.py
@@ -47,7 +47,7 @@
streams_command = [
"ffprobe", "-v", "error",
"-show_entries", "stream=index,codec_type,codec_name,width,height,bit_rate,channels,channel_layout",
- "-show_entries", "stream_tags=language",
+ "-show_entries", "stream_tags=language,title",
"-of", "json", file_path
]
@@ -96,6 +96,7 @@
channels = stream.get("channels", "Unknown")
channel_layout = stream.get("channel_layout", "Unknown")
language = stream.get("tags", {}).get("language", "Unknown")
+ title = stream.get("tags", {}).get("title", "Unknown")
bitrate = stream_bitrate if stream_bitrate else container_bitrate
if codec_type == "video":
@@ -115,6 +116,7 @@
"channels": channels,
"layout": channel_layout,
"language": language,
+ "title": title,
"codec": codec_name
})
diff --git a/static/css/main.css b/static/css/main.css
index 0baa756..2d58f16 100644
--- a/static/css/main.css
+++ b/static/css/main.css
@@ -1,3 +1,7 @@
+a {
+ text-decoration: none;
+}
+
/* LOADING SPINNER */
.loading-spinner-container {
@@ -59,4 +63,32 @@
#progress {
margin-top: 20px;
margin-bottom: 0;
-}
\ No newline at end of file
+}
+
+.progress-circle {
+/* transform: rotate(-90deg);*/
+}
+
+.circle-bg {
+ stroke-dasharray: 100, 100;
+}
+
+.circle-progress {
+ transition: stroke-dasharray 0.3s ease;
+}
+
+.list-group-item.task {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ gap: 12px;
+}
+
+.list-group-item.task .file {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+ align-items: center;
+}
+
diff --git a/static/js/components/global-transcoding-tasks.js b/static/js/components/global-transcoding-tasks.js
new file mode 100644
index 0000000..e4444b1
--- /dev/null
+++ b/static/js/components/global-transcoding-tasks.js
@@ -0,0 +1,143 @@
+const globalTasks = {};
+const mediaInfoCenter = {};
+
+function createEmptyTaskView(taskId) {
+ const li = document.createElement('li');
+ li.classList.add("list-group-item");
+ li.classList.add("task");
+ li.dataset.taskId = taskId;
+
+ let html = `
+
+
+
Stop
+
+ Loading...
+
+
+ `;
+
+ li.innerHTML = html;
+ initSingleCircleProgressBar(li.querySelector(".circle-progress-bar"));
+ return initTaskView(li);
+}
+
+function getTotalActiveTasks() {
+ return Object.keys(globalTasks).length;
+}
+
+function initTaskView(view) {
+ view.querySelector(".control .cancel").addEventListener("click", e => {
+ const taskId = e.currentTarget.dataset.taskId;
+ $(e.currentTarget).hide();
+ $(e.currentTarget).parent().find(".spinner-border").show();
+
+ $.getJSON(`/stop-transcoding?task_id=${taskId}`, function(resp) {
+ if(resp.status == "stoped" && resp.task_id == taskId) {
+ if(typeof globalTasks[resp.task_id] != "undefined") {
+ // delete globalTasks[resp.task_id];
+ }
+ } else {
+ // TODO: show error
+ }
+ });
+ });
+
+ return view;
+}
+
+function updateExistsView(data, view) {
+ const taskContainer = $(view);
+ const fileLink = taskContainer.find(".file a");
+ fileLink.text(data.task.file.split("/").at(-1));
+ fileLink.attr("href", `/single?path=${ data.task.file }`);
+
+ let time = "00:00:00";
+ if(data.message.indexOf("time=") != -1 && data.message.indexOf("time=N/A") == -1) {
+ time = data.message.split("time=")[1].split(" ")[0];
+ }
+
+ const mInf = getMediaInfo(data.task.file);
+
+ if(!mInf) {
+ return;
+ }
+
+ const progressPercent = Math.floor(timeToSeconds(time) / parseInt(mInf.info.container.duration) * 100);
+
+ taskContainer.find(".file .progress-percent").text(`- ${ progressPercent }%`);
+ taskContainer.find(".file .circle-progress-bar")[0].dataset.value = progressPercent;
+}
+
+function globalTranscodingTasksInit() {
+ const tasksContainer = $(".transcoding-tasks-container > ul");
+
+ socket.on("progress", data => {
+ if(typeof data.task.id == "undefined") {
+ return;
+ }
+
+ if(typeof globalTasks[data.task.id] == "undefined") {
+ // create new view
+ const view = createEmptyTaskView(data.task.id);
+ tasksContainer.append(view);
+ globalTasks[data.task.id] = {
+ "data": data,
+ "view": view
+ }
+ }
+
+ // update exists view
+ updateExistsView(data, globalTasks[data.task.id].view);
+
+ $(".total-tasks").text(getTotalActiveTasks());
+ });
+
+ socket.on("completed", data => {
+ if(typeof globalTasks[data.task.id] != "undefined") {
+ // remove task view
+ $(globalTasks[data.task.id].view).remove();
+ delete globalTasks[data.task.id];
+
+ $(".total-tasks").text(getTotalActiveTasks());
+ }
+ });
+
+ socket.on("canceled", data => {
+ if(typeof globalTasks[data.task.id] != "undefined") {
+ // remove task view
+ $(globalTasks[data.task.id].view).remove();
+ delete globalTasks[data.task.id];
+ $(".total-tasks").text(getTotalActiveTasks());
+ }
+ });
+
+ socket.on("error", data => {
+ // show error
+ });
+}
+
+function getMediaInfo(path) {
+ if(typeof mediaInfoCenter[path] == "undefined") {
+ getSingleMediaFileInfo(path, resp => {
+ console.log(resp);
+ mediaInfoCenter[resp.path] = resp;
+ });
+
+ return false;
+ }
+
+ return mediaInfoCenter[path];
+}
+
+$(document).ready(function() {
+ if(typeof mediaInfo != "undefined") {
+ mediaInfoCenter[mediaInfo.path] = mediaInfo;
+ }
+
+ globalTranscodingTasksInit();
+});
\ No newline at end of file
diff --git a/static/js/components/media-list.js b/static/js/components/media-list.js
new file mode 100644
index 0000000..bef1e85
--- /dev/null
+++ b/static/js/components/media-list.js
@@ -0,0 +1,102 @@
+function LSpinnerShow() {
+ $(".loading-spinner-container").removeClass("d-none");
+}
+
+function LSpinnerHide() {
+ $(".loading-spinner-container").addClass("d-none");
+}
+
+// Function to load media data
+function loadMediaList() {
+ LSpinnerShow();
+ $("#media-table").addClass("d-none");
+
+ const tableBody = $("#media-table tbody");
+ tableBody.empty();
+
+ $.ajax({
+ url: "/media-list",
+ method: "GET",
+ success: function(data) {
+
+ if (data.length > 0) {
+ data.forEach((file, index) => {
+ const videoInfo = file.info.video
+ ? file.info.video.map((v, i) => !i ? `Resolution: ${v.resolution}, Codec: ${v.codec}, Bitrate: ${v.bitrate} ` : `Resolution: ${v.resolution}, Codec: ${v.codec} `).join("")
+ : "No video streams ";
+
+ const audioInfo = file.info.audio
+ ? file.info.audio.map(a => `${a.title}, Channels: ${a.layout}, Codec: ${a.codec}, Bitrate: ${a.bitrate}, Language: ${a.language} `).join("")
+ : "No audio streams ";
+
+ const details = `
+ Video:
+
+ Audio:
+
+ `;
+
+ const pathEncoded = encodeURIComponent(file.path);
+
+ tableBody.append(`
+
+ ${index + 1}
+
+
+ ${file.path}
+ ${details}
+
+ ${file.size}${file.size_unit}
+
+ `);
+ });
+ } else {
+ tableBody.append(`No files found `);
+ }
+
+ if(!$('#media-table_wrapper').length) {
+ $('#media-table').DataTable({
+ columnDefs: [
+ {
+ targets: "_all",
+ render: function(data, type, row, meta) {
+ var cell = meta.settings.aoData[meta.row].anCells[meta.col];
+ if (type === 'sort' && cell) {
+ var orderValue = $(cell).attr('data-order');
+ return orderValue !== undefined ? orderValue : data;
+ }
+ return data;
+ }
+ }
+ ]
+ });
+ }
+
+ LSpinnerHide();
+ $("#media-table").removeClass("d-none");
+ },
+ error: function(xhr, status, error) {
+ LSpinnerHide();
+ console.error("Error loading media list:", error);
+ }
+ });
+}
+
+function rescanMediaLibHandler() {
+ $("#do-rescan-media-lib").on("click", function(){
+ $.getJSON("/media-list/clear-cache").done(function(resp){
+ if(resp.status) {
+ return loadMediaList();
+ }
+
+ console.error("Error of rescan media library");
+ // TODO: make alert bar
+ alert("Error of rescan media library");
+ });
+ });
+}
+
+$(document).ready(function() {
+ loadMediaList();
+ rescanMediaLibHandler();
+});
\ No newline at end of file
diff --git a/static/js/components/single-transcoding.js b/static/js/components/single-transcoding.js
new file mode 100644
index 0000000..cf94fd1
--- /dev/null
+++ b/static/js/components/single-transcoding.js
@@ -0,0 +1,185 @@
+function singleTranscodingInit() {
+ const codecCRFMap = {
+ "libx264": [0, 51],
+ "libx265": [0, 51],
+ "libvpx-vp9": [0, 63],
+ "libaom-av1": [0, 63]
+ };
+
+ const maxResolution = $(`[name="resolution"]`).data("max-resolution");
+ $(`[name="resolution"] option`).each(function(){
+ parseInt($(this).attr("value")) > maxResolution
+ ? $(this).attr("disabled", "disabled")
+ : !$(`[name="resolution"] option[selected]`).length && $(this).attr("selected", "selected")
+ });
+
+ $(".show-transcodate-form").on("click", function(){
+ const formContainer = $(".transcodate-form-container");
+ if(formContainer.is(":visible")) {
+ formContainer.slideUp();
+ } else {
+ formContainer.slideDown();
+ }
+ });
+
+ $(".close-transcodate-form-container").on("click", function(){
+ const formContainer = $(".transcodate-form-container");
+ formContainer.slideUp();
+ });
+
+ const progressContainer = $("#progress");
+ progressContainer.pushNewMessage = (msg) => {
+ !progressContainer.is(":visible") && progressContainer.slideDown();
+ progressContainer.html(msg);
+ };
+ // Listen for progress updates
+ socket.on("progress", (data) => {
+ if(progressContainer.data("file-path") != data.task.file) {
+ return;
+ }
+
+ progressContainer.pushNewMessage(data.message);
+ $(".transcodate-form-container").slideUp();
+ $(".show-transcodate-form").hide();
+ $(".stop-transcoding").show();
+ $(".stop-transcoding").attr("data-task_id", data.task.id);
+ $(".transcodate-form-container .run-transcodate").hide();
+ });
+
+ // Listen for completion
+ socket.on("completed", (data) => {
+ if(progressContainer.data("file-path") != data.task.file) {
+ return;
+ }
+
+ progressContainer.pushNewMessage("Completed!");
+ $(".show-transcodate-form").show();
+ $(".stop-transcoding").hide();
+ $(".transcodate-form-container .run-transcodate").show();
+ });
+
+ // Listen for errors
+ socket.on("error", (data) => {
+ if(progressContainer.data("file-path") != data.task.file) {
+ return;
+ }
+
+ // TODO: push to notification bar
+ progressContainer.pushNewMessage(data.message);
+ $(".show-transcodate-form").show();
+ $(".stop-transcoding").hide();
+ $(".transcodate-form-container .run-transcodate").show();
+ });
+
+ socket.on("canceled", (data) => {
+ if(progressContainer.data("file-path") != data.task.file) {
+ return;
+ }
+
+ progressContainer.pushNewMessage(data.message);
+ $(".stop-transcoding").hide();
+ $(".show-transcodate-form").show();
+ $(".transcodate-form-container .run-transcodate").show();
+ });
+
+ $(".transcodate-form-container .run-transcodate").on("click", async () => {
+ const formContiner = $(".transcodate-form-container");
+ const fields = formContiner.find("[name]");
+ $(".transcodate-form-container .run-transcodate").hide();
+
+ const data = {};
+ for(let field of fields) {
+ field = $(field);
+ data[field.attr("name")] = field.val();
+ }
+
+ data["crf"] = "" + Math.abs(data["crf"]);
+ console.log(data);
+
+ const response = await fetch("/process-media", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(data)
+ });
+
+ if (response.ok) {
+ document.getElementById("progress").innerText = "Processing started...";
+ } else {
+ const error = await response.json();
+ $(".transcodate-form-container .run-transcodate").show();
+ alert(`Error: ${error.error}`);
+ }
+
+ });
+
+ $("#codec").on("change", function(){
+ const crf = $(`[name="crf"]`);
+
+ const codec = $(this).val();
+ crf.attr("min", codecCRFMap[codec][0]);
+ crf.attr("max", codecCRFMap[codec][1]);
+ if(parseInt(crf.val()) > codecCRFMap[codec][1]) {
+ crf.val(codecCRFMap[codec][1]);
+ }
+
+ crf.parent().find(".crf-range").text(`${codecCRFMap[codec][0]}-${codecCRFMap[codec][1]}`);
+
+ // toggle form of preset
+ const presetLibxForm = $(`[name="preset-libx"]`);
+ const presetVP9Form = $(`[name="preset-vp9"]`);
+ const presetAV1Form = $(`[name="preset-av1"]`);
+ if(codec == "libx264" || codec == "libx265") {
+ presetLibxForm.show();
+ presetVP9Form.hide();
+ presetAV1Form.hide();
+ } else if(codec == "libaom-av1"){
+ presetLibxForm.hide();
+ presetVP9Form.hide();
+ presetAV1Form.show();
+ } else if(codec == "libvpx-vp9") {
+ presetLibxForm.hide();
+ presetVP9Form.show();
+ presetAV1Form.hide();
+ }
+ });
+
+ $(`[name="crf"]`).on("input", function(){
+ const val = parseInt($(this).val());
+
+ if(isNaN(val)) {
+ $(this).val(0);
+ return;
+ }
+
+ if(val < 0) {
+ $(this).val(0);
+ return;
+ }
+
+ const max = parseInt($(this).attr("max"));
+ if(val > max) {
+ $(this).val(max);
+ }
+ });
+
+ $(".stop-transcoding").on("click", function(){
+ stopTranscoding($(this).data("task_id"));
+ });
+}
+
+function stopTranscoding(taskId) {
+ if(taskId && taskId != "undefined") {
+ $(".stop-transcoding").hide();
+
+ $.getJSON(`/stop-transcoding?task_id=${taskId}`, function(resp) {
+ if(resp.status == "stoped" && resp.task_id == taskId) {
+ processContainer.slideUp();
+ setTimeout(() => processContainer.html(""), 200);
+ }
+ });
+ }
+}
+
+$(document).ready(function() {
+ singleTranscodingInit();
+});
\ No newline at end of file
diff --git a/static/js/index.js b/static/js/index.js
new file mode 100644
index 0000000..872d05b
--- /dev/null
+++ b/static/js/index.js
@@ -0,0 +1,91 @@
+const socket = io();
+
+function makeProgressBar(props) {
+ let html = `
+
+
+
+
+ `;
+
+ return html;
+}
+
+function initSingleCircleProgressBar(view) {
+ if(view.alreadyInitProgressBars) {
+ return ;
+ }
+
+ $(view).html(makeProgressBar({
+ "trackColor": view.dataset.barColor + "33",
+ "fillColor": view.dataset.barColor,
+ "strokeWidth": view.dataset.barStroke,
+ "size": view.dataset.barSize
+ }));
+
+ setProgressBarVal(view, view.dataset.value);
+
+ const targetElement = view;
+
+ const observer = new MutationObserver((mutationsList) => {
+ for (const mutation of mutationsList) {
+ if (mutation.type === "attributes" && mutation.attributeName.startsWith("data-")) {
+ const changedAttr = mutation.attributeName;
+ const newValue = targetElement.getAttribute(changedAttr);
+
+ setProgressBarVal(targetElement, newValue);
+ }
+ }
+ });
+
+ observer.observe(targetElement, {
+ attributes: true,
+ attributeFilter: ["data-value"],
+ });
+
+ view.alreadyInitProgressBars = true;
+}
+
+function initCircleProgressBars() {
+ $(".circle-progress-bar[data-value]").each(function(){
+ initSingleCircleProgressBar(view);
+ });
+}
+
+function setProgressBarVal(target, percent) {
+ const circle = target.querySelector(".circle-progress");
+ const radius = circle.r.baseVal.value;
+ const circumference = 2 * Math.PI * radius;
+ const offset = circumference - (percent / 100) * circumference;
+
+ circle.style.strokeDasharray = `${circumference - offset} ${circumference}`;
+}
+
+$(document).ready(function() {
+ $(".duration-to-minutes[data-duration-in-seconds]").each(function() {
+ const durationInSec = parseInt($(this).data("duration-in-seconds"));
+ const durationInMinutes = Math.floor(durationInSec / 60);
+ const leftSeconds = Math.floor(durationInSec - durationInMinutes * 60);
+ $(this).text(`${durationInMinutes}m ${leftSeconds}s`);
+ });
+
+ initCircleProgressBars();
+});
\ No newline at end of file
diff --git a/static/js/lib.js b/static/js/lib.js
new file mode 100644
index 0000000..d722c6d
--- /dev/null
+++ b/static/js/lib.js
@@ -0,0 +1,5 @@
+function getSingleMediaFileInfo(filepath, callback) {
+ $.getJSON(`/single-json?path=${ filepath }`, function(resp) {
+ callback(resp);
+ });
+}
\ No newline at end of file
diff --git a/static/js/media.js b/static/js/media.js
deleted file mode 100644
index 460a935..0000000
--- a/static/js/media.js
+++ /dev/null
@@ -1,230 +0,0 @@
-function LSpinnerShow() {
- $(".loading-spinner-container").removeClass("d-none");
-}
-
-function LSpinnerHide() {
- $(".loading-spinner-container").addClass("d-none");
-}
-
-// Function to load media data
-function loadMediaList() {
- LSpinnerShow();
- $("#media-table").addClass("d-none");
-
- const tableBody = $("#media-table tbody");
- tableBody.empty();
-
- $.ajax({
- url: "/media-list",
- method: "GET",
- success: function(data) {
-
- if (data.length > 0) {
- data.forEach((file, index) => {
- const videoInfo = file.info.video
- ? file.info.video.map((v, i) => !i ? `Resolution: ${v.resolution}, Codec: ${v.codec}, Bitrate: ${v.bitrate} ` : `Resolution: ${v.resolution}, Codec: ${v.codec} `).join("")
- : "No video streams ";
-
- const audioInfo = file.info.audio
- ? file.info.audio.map(a => `Channels: ${a.layout}, Codec: ${a.codec}, Bitrate: ${a.bitrate}, Language: ${a.language} `).join("")
- : "No audio streams ";
-
- const details = `
- Video:
-
- Audio:
-
- `;
-
- const pathEncoded = encodeURIComponent(file.path);
-
- tableBody.append(`
-
- ${index + 1}
-
-
- ${file.path}
- ${details}
-
- ${file.size}${file.size_unit}
-
- `);
- });
- } else {
- tableBody.append(`No files found `);
- }
-
- if(!$('#media-table_wrapper').length) {
- $('#media-table').DataTable({
- columnDefs: [
- {
- targets: "_all",
- render: function(data, type, row, meta) {
- var cell = meta.settings.aoData[meta.row].anCells[meta.col];
- if (type === 'sort' && cell) {
- var orderValue = $(cell).attr('data-order');
- return orderValue !== undefined ? orderValue : data;
- }
- return data;
- }
- }
- ]
- });
- }
-
- LSpinnerHide();
- $("#media-table").removeClass("d-none");
- },
- error: function(xhr, status, error) {
- LSpinnerHide();
- console.error("Error loading media list:", error);
- }
- });
-}
-
-function rescanMediaLibHandler() {
- $("#do-rescan-media-lib").on("click", function(){
- $.getJSON("/media-list/clear-cache").done(function(resp){
- if(resp.status) {
- return loadMediaList();
- }
-
- console.error("Error of rescan media library");
- // TODO: make alert bar
- alert("Error of rescan media library");
- });
- });
-}
-
-function transcodateFormHandlers() {
- const codecCRFMap = {
- "libx264": [0, 51],
- "libx265": [0, 51],
- "libvpx-vp9": [0, 63],
- "libaom-av1": [0, 63]
- };
-
- const maxResolution = $(`[name="resolution"]`).data("max-resolution");
- $(`[name="resolution"] option`).each(function(){
- parseInt($(this).attr("value")) > maxResolution
- ? $(this).attr("disabled", "disabled")
- : !$(`[name="resolution"] option[selected]`).length && $(this).attr("selected", "selected")
- });
-
- $(".show-transcodate-form").on("click", function(){
- const formContainer = $(".transcodate-form-container");
- if(formContainer.is(":visible")) {
- formContainer.slideUp();
- } else {
- formContainer.slideDown();
- }
- });
-
- $(".close-transcodate-form-container").on("click", function(){
- const formContainer = $(".transcodate-form-container");
- formContainer.slideUp();
- });
-
- const socket = io();
-
- // Listen for progress updates
- socket.on("progress", (data) => {
- document.getElementById("progress").innerText = data.message;
- });
-
- // Listen for completion
- socket.on("completed", (data) => {
- document.getElementById("progress").innerText = "Completed!";
- });
-
- // Listen for errors
- socket.on("error", (data) => {
- alert(data.message);
- });
-
- $(".transcodate-form-container .run-transcodate").on("click", async () => {
- const formContiner = $(".transcodate-form-container");
- const fields = formContiner.find("[name]");
-
- const data = {};
- for(let field of fields) {
- field = $(field);
- data[field.attr("name")] = field.val();
- }
-
- data["crf"] = "" + Math.abs(data["crf"]);
- console.log(data);
-
- const response = await fetch("/process-media", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(data)
- });
-
- if (response.ok) {
- document.getElementById("progress").innerText = "Processing started...";
- } else {
- const error = await response.json();
- alert(`Error: ${error.error}`);
- }
-
- });
-
- $("#codec").on("change", function(){
- const crf = $(`[name="crf"]`);
-
- const codec = $(this).val();
- crf.attr("min", codecCRFMap[codec][0]);
- crf.attr("max", codecCRFMap[codec][1]);
- if(parseInt(crf.val()) > codecCRFMap[codec][1]) {
- crf.val(codecCRFMap[codec][1]);
- }
-
- crf.parent().find(".crf-range").text(`${codecCRFMap[codec][0]}-${codecCRFMap[codec][1]}`);
-
- // toggle form of preset
- const presetLibxForm = $(`[name="preset-libx"]`);
- const presetVP9Form = $(`[name="preset-vp9"]`);
- const presetAV1Form = $(`[name="preset-av1"]`);
- if(codec == "libx264" || codec == "libx265") {
- presetLibxForm.show();
- presetVP9Form.hide();
- presetAV1Form.hide();
- } else if(codec == "libaom-av1"){
- presetLibxForm.hide();
- presetVP9Form.hide();
- presetAV1Form.show();
- } else if(codec == "libvpx-vp9") {
- presetLibxForm.hide();
- presetVP9Form.show();
- presetAV1Form.hide();
- }
- });
-
- $(`[name="crf"]`).on("input", function(){
- const val = parseInt($(this).val());
-
- if(isNaN(val)) {
- $(this).val(0);
- return;
- }
-
- if(val < 0) {
- $(this).val(0);
- return;
- }
-
- const max = parseInt($(this).attr("max"));
- if(val > max) {
- $(this).val(max);
- }
- });
-}
-
-
-// Load media list when the page is ready
-$(document).ready(function() {
- loadMediaList();
- rescanMediaLibHandler();
- transcodateFormHandlers();
-});
\ No newline at end of file
diff --git a/static/js/utils.js b/static/js/utils.js
new file mode 100644
index 0000000..baf2706
--- /dev/null
+++ b/static/js/utils.js
@@ -0,0 +1,8 @@
+function timeToSeconds(timeStr) {
+ let parts = timeStr.split(':');
+ let hours = parseInt(parts[0], 10);
+ let minutes = parseInt(parts[1], 10);
+ let seconds = parseFloat(parts[2]);
+
+ return hours * 3600 + minutes * 60 + seconds;
+}
\ No newline at end of file
diff --git a/templates/components/global-transcoding-tasks.html b/templates/components/global-transcoding-tasks.html
new file mode 100644
index 0000000..f79e5ec
--- /dev/null
+++ b/templates/components/global-transcoding-tasks.html
@@ -0,0 +1,21 @@
+
+
+
\ No newline at end of file
diff --git a/templates/components/media-list.html b/templates/components/media-list.html
new file mode 100644
index 0000000..7b09573
--- /dev/null
+++ b/templates/components/media-list.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/components/single-transcoding.html b/templates/components/single-transcoding.html
new file mode 100644
index 0000000..4698aa9
--- /dev/null
+++ b/templates/components/single-transcoding.html
@@ -0,0 +1,84 @@
+
+ Transcodate
+ Stop transcoding
+
+
+
+
+
+
+
diff --git a/templates/index.html b/templates/index.html
index af8ba01..55f65a6 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -2,27 +2,8 @@
Media Library Manager
-
-
-
-
-
-
+ {% include "components/media-list.html" %}
{% include "partials/footer.html" %}
\ No newline at end of file
diff --git a/templates/partials/footer.html b/templates/partials/footer.html
index 691287b..826f5e0 100644
--- a/templates/partials/footer.html
+++ b/templates/partials/footer.html
@@ -1,2 +1,3 @@
+