diff --git a/modules/app_video.py b/modules/app_video.py
index 4ced921..6c29d85 100644
--- a/modules/app_video.py
+++ b/modules/app_video.py
@@ -17,7 +17,7 @@ CONVERT_SETTING = "video_convert_enabled"
_thumbnail_lock = Semaphore()
_generating = set()
_generating_lock = Semaphore()
-_converting = set()
+_converting = {} # path -> {"started_at": float, "source_size": int, "tmp_path": str}
_converting_lock = Semaphore()
@@ -36,7 +36,7 @@ def queue_convert(relative_path, force=False):
with _converting_lock:
if relative_path in _converting:
return False
- _converting.add(relative_path)
+ _converting[relative_path] = {"started_at": time.time(), "source_size": 0, "tmp_path": ""}
t = threading.Thread(target=_convert_bg, args=(relative_path,), daemon=True)
t.start()
return True
@@ -53,6 +53,11 @@ def _convert_bg(relative_path):
try:
tmp_path = mp4_path + ".converting.mp4"
+ source_size = os.path.getsize(source_path) if os.path.exists(source_path) else 0
+ with _converting_lock:
+ if relative_path in _converting:
+ _converting[relative_path]["source_size"] = source_size
+ _converting[relative_path]["tmp_path"] = tmp_path
cmd = [
"ffmpeg", "-nostdin", "-loglevel", "error", "-y",
"-i", source_path,
@@ -84,7 +89,7 @@ def _convert_bg(relative_path):
app_dash.end_local_video_use(relative_path)
finally:
with _converting_lock:
- _converting.discard(relative_path)
+ _converting.pop(relative_path, None)
def _local_path(relative_path):
@@ -117,7 +122,24 @@ def list_local_videos():
errors.append(f"{relative_path}: {e}")
continue
with _converting_lock:
- converting = relative_path in _converting
+ conv_info = _converting.get(relative_path)
+ converting = conv_info is not None
+ convert_progress = None
+ if converting and conv_info:
+ elapsed = time.time() - conv_info["started_at"]
+ source_size = conv_info["source_size"]
+ tmp_path = conv_info["tmp_path"]
+ tmp_size = 0
+ if tmp_path and os.path.exists(tmp_path):
+ try:
+ tmp_size = os.path.getsize(tmp_path)
+ except OSError:
+ pass
+ pct = round(tmp_size / source_size * 100) if source_size > 0 else 0
+ convert_progress = {
+ "elapsed_s": int(elapsed),
+ "pct": min(pct, 99), # never show 100% until done
+ }
files.append(
{
"path": relative_path,
@@ -129,6 +151,7 @@ def list_local_videos():
stat.st_mtime,
) in uploaded_stats,
"converting": converting,
+ "convert_progress": convert_progress,
}
)
total_size += stat.st_size
diff --git a/static/js/local-videos.js b/static/js/local-videos.js
index a1f9ad8..e8e7cca 100644
--- a/static/js/local-videos.js
+++ b/static/js/local-videos.js
@@ -119,9 +119,12 @@
preview.appendChild(mobileMeta);
// Converting badge shown on mobile inside thumbnail cell
if (file.converting) {
+ const p = file.convert_progress;
+ const elapsed = p ? `${Math.floor(p.elapsed_s / 60)}m${p.elapsed_s % 60}s` : '…';
+ const pct = p ? ` ${p.pct}%` : '';
const mobileBadge = document.createElement("span");
mobileBadge.className = "badge bg-info text-dark dash-thumb-converting mt-1";
- mobileBadge.innerHTML = '';
+ mobileBadge.innerHTML = `${pct || elapsed}`;
preview.appendChild(mobileBadge);
}
row.appendChild(preview);
@@ -143,13 +146,16 @@
dateCell.className = "dash-col-date";
row.appendChild(dateCell);
- // Upload / convert badge
+ // Upload / convert badge (desktop)
const upload = document.createElement("td");
upload.className = "dash-col-upload";
const badge = document.createElement("span");
if (file.converting) {
+ const p = file.convert_progress;
+ const pct = p && p.pct > 0 ? ` ${p.pct}%` : '';
+ const elapsed = p ? ` ${Math.floor(p.elapsed_s / 60)}m${p.elapsed_s % 60}s` : '';
badge.className = "badge bg-info text-dark";
- badge.innerHTML = 'Converting';
+ badge.innerHTML = `Converting${pct}${elapsed}`;
convertingCount++;
} else {
badge.className = file.uploaded ? "badge bg-success" : "badge bg-warning text-dark";