Merge 681aad24cd into 5429200fba
This commit is contained in:
commit
2824303412
3 changed files with 71 additions and 2 deletions
36
app/ytdl.py
36
app/ytdl.py
|
|
@ -231,10 +231,24 @@ class DownloadInfo:
|
|||
self.live_status = live_status
|
||||
self.live_release_timestamp = live_release_timestamp
|
||||
self.subtitle_files = []
|
||||
# Actual media properties captured from yt-dlp's info_dict when the
|
||||
# download finishes. Unlike `quality`/`codec` (the user's request),
|
||||
# these reflect what yt-dlp actually resolved to. Set lazily by the
|
||||
# MoveFiles postprocessor hook, so they stay None for in-flight,
|
||||
# failed, or non-media (captions/thumbnail) downloads.
|
||||
self.width = None
|
||||
self.height = None
|
||||
self.fps = None
|
||||
self.vcodec_actual = None
|
||||
self.abr = None
|
||||
|
||||
def __setstate__(self, state):
|
||||
"""BACKWARD COMPATIBILITY: migrate old DownloadInfo from persistent queue files."""
|
||||
self.__dict__.update(state)
|
||||
# Backfill resolved-media fields for items persisted before this patch.
|
||||
for field in ('width', 'height', 'fps', 'vcodec_actual', 'abr'):
|
||||
if not hasattr(self, field):
|
||||
setattr(self, field, None)
|
||||
if 'download_type' not in state:
|
||||
old_format = state.get('format', 'any')
|
||||
old_video_codec = state.get('video_codec', 'auto')
|
||||
|
|
@ -454,7 +468,21 @@ class Download:
|
|||
filename = os.path.join(finaldir, os.path.basename(filepath))
|
||||
else:
|
||||
filename = filepath
|
||||
self.status_queue.put({'status': 'finished', 'filename': filename})
|
||||
status_update = {'status': 'finished', 'filename': filename}
|
||||
# Capture actual media properties resolved by yt-dlp so the
|
||||
# UI can show "Best · 1080p60" instead of just "Best".
|
||||
# Skip vcodec='none' (audio-only formats) — that's not the
|
||||
# video codec, it just means there isn't one.
|
||||
info = d['info_dict']
|
||||
for src, dst in (('width', 'width'), ('height', 'height'),
|
||||
('fps', 'fps'), ('abr', 'abr')):
|
||||
value = info.get(src)
|
||||
if value is not None:
|
||||
status_update[dst] = value
|
||||
vcodec = info.get('vcodec')
|
||||
if vcodec and vcodec != 'none':
|
||||
status_update['vcodec_actual'] = vcodec
|
||||
self.status_queue.put(status_update)
|
||||
# For captions-only downloads, yt-dlp may still report a media-like
|
||||
# filepath in MoveFiles. Capture subtitle outputs explicitly so the
|
||||
# UI can link to real caption files.
|
||||
|
|
@ -585,6 +613,12 @@ class Download:
|
|||
self.info.size = os.path.getsize(fileName) if os.path.exists(fileName) else None
|
||||
if getattr(self.info, 'download_type', '') == 'thumbnail':
|
||||
self.info.filename = re.sub(r'\.webm$', '.jpg', self.info.filename)
|
||||
# Capture actual media properties yt-dlp resolved (width / height /
|
||||
# fps / actual vcodec / audio bitrate). Pushed by the MoveFiles
|
||||
# postprocessor hook; never overwrite a present value with None.
|
||||
for field in ('width', 'height', 'fps', 'vcodec_actual', 'abr'):
|
||||
if field in status and status[field] is not None:
|
||||
setattr(self.info, field, status[field])
|
||||
|
||||
# Handle chapter files
|
||||
log.debug(f"Update status for {self.info.title}: {status}")
|
||||
|
|
|
|||
|
|
@ -862,11 +862,36 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
|||
}
|
||||
const q = download.quality;
|
||||
if (!q) return '';
|
||||
if (/^\d+$/.test(q) && download.download_type === 'audio') return `${q} kbps`;
|
||||
const requested = this.formatRequestedQuality(q, download.download_type);
|
||||
const actual = this.formatActualQuality(download);
|
||||
// Show the actual value next to the requested one only when it differs
|
||||
// — most useful for "Best" / "Worst" (no exact label) or when yt-dlp
|
||||
// had to downgrade (user requested 1080p, only 720p was available).
|
||||
if (!actual || actual === requested) return requested;
|
||||
return `${requested} · ${actual}`;
|
||||
}
|
||||
|
||||
private formatRequestedQuality(q: string, dl_type: string): string {
|
||||
if (/^\d+$/.test(q) && dl_type === 'audio') return `${q} kbps`;
|
||||
if (/^\d+$/.test(q)) return `${q}p`;
|
||||
return q.charAt(0).toUpperCase() + q.slice(1);
|
||||
}
|
||||
|
||||
// Returns the actually-resolved quality from yt-dlp's info_dict
|
||||
// (height+fps for video, abr for audio). Returns null for old persisted
|
||||
// entries that pre-date the backend patch, or for non-media types.
|
||||
private formatActualQuality(download: Download): string | null {
|
||||
if (download.download_type === 'audio') {
|
||||
return download.abr ? `${Math.round(download.abr)} kbps` : null;
|
||||
}
|
||||
if (!download.height) return null;
|
||||
let label = `${download.height}p`;
|
||||
// High frame rates (50+) are worth highlighting; standard 24/25/30
|
||||
// would clutter without telling the user anything new.
|
||||
if (download.fps && download.fps >= 50) label += Math.round(download.fps);
|
||||
return label;
|
||||
}
|
||||
|
||||
downloadTypeLabel(download: Download): string {
|
||||
const type = download.download_type || 'video';
|
||||
return type.charAt(0).toUpperCase() + type.slice(1);
|
||||
|
|
|
|||
|
|
@ -32,4 +32,14 @@ export interface Download {
|
|||
error?: string;
|
||||
deleting?: boolean;
|
||||
chapter_files?: { filename: string, size: number }[];
|
||||
// Actual media properties resolved by yt-dlp at download time. Unlike
|
||||
// `quality`/`codec` (the user's request), these reflect what came out.
|
||||
// Optional because they're only populated for finished downloads of
|
||||
// video/audio (not captions/thumbnails) and old persisted entries
|
||||
// pre-dating the patch don't have them.
|
||||
width?: number;
|
||||
height?: number;
|
||||
fps?: number;
|
||||
vcodec_actual?: string;
|
||||
abr?: number;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue