feat(videos): show conversion progress % and elapsed time
- _converting dict now tracks started_at, source_size, tmp_path per file - list_local_videos() reads tmp file size to compute pct complete - Desktop badge: 'Converting 42% 1m30s' - Mobile badge: '42%' (compact, fits under thumbnail) Co-authored-by: Copilot <[email protected]>
This commit is contained in:
+27
-4
@@ -17,7 +17,7 @@ CONVERT_SETTING = "video_convert_enabled"
|
|||||||
_thumbnail_lock = Semaphore()
|
_thumbnail_lock = Semaphore()
|
||||||
_generating = set()
|
_generating = set()
|
||||||
_generating_lock = Semaphore()
|
_generating_lock = Semaphore()
|
||||||
_converting = set()
|
_converting = {} # path -> {"started_at": float, "source_size": int, "tmp_path": str}
|
||||||
_converting_lock = Semaphore()
|
_converting_lock = Semaphore()
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ def queue_convert(relative_path, force=False):
|
|||||||
with _converting_lock:
|
with _converting_lock:
|
||||||
if relative_path in _converting:
|
if relative_path in _converting:
|
||||||
return False
|
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 = threading.Thread(target=_convert_bg, args=(relative_path,), daemon=True)
|
||||||
t.start()
|
t.start()
|
||||||
return True
|
return True
|
||||||
@@ -53,6 +53,11 @@ def _convert_bg(relative_path):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
tmp_path = mp4_path + ".converting.mp4"
|
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 = [
|
cmd = [
|
||||||
"ffmpeg", "-nostdin", "-loglevel", "error", "-y",
|
"ffmpeg", "-nostdin", "-loglevel", "error", "-y",
|
||||||
"-i", source_path,
|
"-i", source_path,
|
||||||
@@ -84,7 +89,7 @@ def _convert_bg(relative_path):
|
|||||||
app_dash.end_local_video_use(relative_path)
|
app_dash.end_local_video_use(relative_path)
|
||||||
finally:
|
finally:
|
||||||
with _converting_lock:
|
with _converting_lock:
|
||||||
_converting.discard(relative_path)
|
_converting.pop(relative_path, None)
|
||||||
|
|
||||||
|
|
||||||
def _local_path(relative_path):
|
def _local_path(relative_path):
|
||||||
@@ -117,7 +122,24 @@ def list_local_videos():
|
|||||||
errors.append(f"{relative_path}: {e}")
|
errors.append(f"{relative_path}: {e}")
|
||||||
continue
|
continue
|
||||||
with _converting_lock:
|
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(
|
files.append(
|
||||||
{
|
{
|
||||||
"path": relative_path,
|
"path": relative_path,
|
||||||
@@ -129,6 +151,7 @@ def list_local_videos():
|
|||||||
stat.st_mtime,
|
stat.st_mtime,
|
||||||
) in uploaded_stats,
|
) in uploaded_stats,
|
||||||
"converting": converting,
|
"converting": converting,
|
||||||
|
"convert_progress": convert_progress,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
total_size += stat.st_size
|
total_size += stat.st_size
|
||||||
|
|||||||
@@ -119,9 +119,12 @@
|
|||||||
preview.appendChild(mobileMeta);
|
preview.appendChild(mobileMeta);
|
||||||
// Converting badge shown on mobile inside thumbnail cell
|
// Converting badge shown on mobile inside thumbnail cell
|
||||||
if (file.converting) {
|
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");
|
const mobileBadge = document.createElement("span");
|
||||||
mobileBadge.className = "badge bg-info text-dark dash-thumb-converting mt-1";
|
mobileBadge.className = "badge bg-info text-dark dash-thumb-converting mt-1";
|
||||||
mobileBadge.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
mobileBadge.innerHTML = `<i class="fas fa-spinner fa-spin me-1"></i>${pct || elapsed}`;
|
||||||
preview.appendChild(mobileBadge);
|
preview.appendChild(mobileBadge);
|
||||||
}
|
}
|
||||||
row.appendChild(preview);
|
row.appendChild(preview);
|
||||||
@@ -143,13 +146,16 @@
|
|||||||
dateCell.className = "dash-col-date";
|
dateCell.className = "dash-col-date";
|
||||||
row.appendChild(dateCell);
|
row.appendChild(dateCell);
|
||||||
|
|
||||||
// Upload / convert badge
|
// Upload / convert badge (desktop)
|
||||||
const upload = document.createElement("td");
|
const upload = document.createElement("td");
|
||||||
upload.className = "dash-col-upload";
|
upload.className = "dash-col-upload";
|
||||||
const badge = document.createElement("span");
|
const badge = document.createElement("span");
|
||||||
if (file.converting) {
|
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.className = "badge bg-info text-dark";
|
||||||
badge.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Converting';
|
badge.innerHTML = `<i class="fas fa-spinner fa-spin me-1"></i>Converting${pct}${elapsed}`;
|
||||||
convertingCount++;
|
convertingCount++;
|
||||||
} else {
|
} else {
|
||||||
badge.className = file.uploaded ? "badge bg-success" : "badge bg-warning text-dark";
|
badge.className = file.uploaded ? "badge bg-success" : "badge bg-warning text-dark";
|
||||||
|
|||||||
Reference in New Issue
Block a user