home · contact · privacy
Add live download status updates.
authorChristian Heller <c.heller@plomlompom.de>
Mon, 3 Mar 2025 14:52:20 +0000 (15:52 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Mon, 3 Mar 2025 14:52:20 +0000 (15:52 +0100)
src/ytplom/misc.py

index aade5de3a5933c852eab9d5c42db41b6ef436e76..26d1369a73ba5acc4ad1b734f44bb08c58f9de42 100644 (file)
@@ -74,7 +74,8 @@ LEGAL_EXTENSIONS = {'webm', 'mp4', 'mkv'}
 FILE_FLAGS: dict[FlagName, FlagsInt] = {
   FlagName('do not sync'): FlagsInt(1 << 62)
 }
-ONE_MILLION = 1000 * 1000
+MILLION = 1000 * 1000
+MEGA = 1024 * 1024
 
 
 def ensure_expected_dirs(expected_dirs: list[Path]) -> None:
@@ -359,7 +360,7 @@ class VideoFile(DbData):
         self.yt_id = yt_id
         self.duration_ms = (
                 duration_ms if duration_ms >= 0
-                else int(ONE_MILLION * Decimal(
+                else int(MILLION * Decimal(
                     ffprobe(self.full_path)['format']['duration'])))
         self.last_update = last_update if last_update else _now_string()
         self._hash_on_last_update = hash(self)
@@ -457,14 +458,14 @@ class VideoFile(DbData):
         """If file at .full_path, return its megabytes size, else -1."""
         if not self.full_path.is_file():
             return -1
-        return self.full_path.stat().st_size / (1024 * 1024)
+        return self.full_path.stat().st_size / MEGA
 
     def duration(self, short: bool = False) -> str:
         """Return human-friendly formatting of .duration_ms."""
         if self.duration_ms < 0:
             return '?'
-        seconds_str = f'{_readable_seconds(self.duration_ms // ONE_MILLION)}'
-        milliseconds_str = f'{self.duration_ms // ONE_MILLION}'.rjust(6, '0')
+        seconds_str = f'{_readable_seconds(self.duration_ms // MILLION)}'
+        milliseconds_str = f'{self.duration_ms // MILLION}'.rjust(6, '0')
         return seconds_str if short else f'{seconds_str}.{milliseconds_str}'
 
     @property
@@ -875,10 +876,29 @@ class DownloadsManager:
 
     def _download_next(self) -> None:
         if self._to_download:
+            downloaded_before: int = 0
+            estimated_total: int = 0
+            estimated_total_mb: float
+
+            def hook(d) -> None:
+                nonlocal downloaded_before
+                if d['status'] in {'downloading', 'finished'}:
+                    downloaded_i = d['downloaded_bytes']
+                    downloaded_mb = (downloaded_i + downloaded_before) / MEGA
+                    msg = f'{int(100 * downloaded_mb/estimated_total_mb)}%: '\
+                          f'{downloaded_mb:5.1f}/{estimated_total_mb:.1f}'
+                    self._update_status(video_id, f'downloading: {msg} MB')
+                    if d['status'] == 'finished':
+                        downloaded_before += downloaded_i
+
             video_id = self._to_download.pop(0)
-            with YoutubeDL(YT_DL_PARAMS) as ydl:
-                self._update_status(video_id, 'downloading')
-                ydl.download([f'{YOUTUBE_URL_PREFIX}{video_id}'])
+            url = f'{YOUTUBE_URL_PREFIX}{video_id}'
+            with YoutubeDL(YT_DL_PARAMS | {'progress_hooks': [hook]}) as ydl:
+                info = ydl.sanitize_info(ydl.extract_info(url, download=False))
+                for requested in info['requested_formats']:
+                    estimated_total += requested['filesize_approx']
+                estimated_total_mb = estimated_total / MEGA
+                ydl.download(url)
             self._sync_db()
 
     def start_thread(self) -> None: