Skip to main content

yt-dlp: print the absolute path, not the relative path

ID
96e65c9
date
2025-10-05 07:06:26+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
5ad14de
message
yt-dlp: print the absolute path, not the relative path
changed files
4 files, 86 additions, 14 deletions

Changed files

dev_requirements.in (26) → dev_requirements.in (33)

diff --git a/dev_requirements.in b/dev_requirements.in
index 1d7e062..368464a 100644
--- a/dev_requirements.in
+++ b/dev_requirements.in
@@ -1,3 +1,4 @@
 -r requirements.txt
 
+pytest
 ruff

dev_requirements.txt (761) → dev_requirements.txt (940)

diff --git a/dev_requirements.txt b/dev_requirements.txt
index 2c8efa7..6f2f120 100644
--- a/dev_requirements.txt
+++ b/dev_requirements.txt
@@ -14,10 +14,20 @@ idna==3.10
     # via
     #   -r requirements.txt
     #   requests
+iniconfig==2.1.0
+    # via pytest
 mutagen==1.47.0
     # via -r requirements.txt
+packaging==25.0
+    # via pytest
+pluggy==1.6.0
+    # via pytest
 pycryptodomex==3.23.0
     # via -r requirements.txt
+pygments==2.19.2
+    # via pytest
+pytest==8.4.2
+    # via -r dev_requirements.in
 requests==2.32.5
     # via -r requirements.txt
 ruff==0.13.3

tests/test_yt-dlp_alexwlchan.py (0) → tests/test_yt-dlp_alexwlchan.py (667)

diff --git a/tests/test_yt-dlp_alexwlchan.py b/tests/test_yt-dlp_alexwlchan.py
new file mode 100644
index 0000000..1e516a0
--- /dev/null
+++ b/tests/test_yt-dlp_alexwlchan.py
@@ -0,0 +1,25 @@
+import json
+import os
+import subprocess
+
+
+def test_public_domain_video() -> None:
+    """
+    Download a public domain video and check we get the expected output.
+    """
+    output = subprocess.check_output(
+        [
+            "python3",
+            "yt-dlp_alexwlchan.py",
+            "https://www.youtube.com/watch?v=TUQaGhPdlxs",
+        ]
+    )
+    video_info = json.loads(output)
+
+    assert (
+        video_info["title"]
+        == '"new york city, manhattan, people" - Free Public Domain Video'
+    )
+    assert os.path.exists(video_info["video_path"])
+    assert os.path.exists(video_info["thumbnail_path"])
+    assert video_info["subtitle_path"] is None

yt-dlp_alexwlchan.py (2772) → yt-dlp_alexwlchan.py (3497)

diff --git a/yt-dlp_alexwlchan.py b/yt-dlp_alexwlchan.py
index f9284f4..5a7b173 100755
--- a/yt-dlp_alexwlchan.py
+++ b/yt-dlp_alexwlchan.py
@@ -1,9 +1,10 @@
 #!/usr/bin/env python3
 
 import json
-import os
+from pathlib import Path
 import sys
 import tempfile
+from typing import TypedDict
 
 from yt_dlp import YoutubeDL
 
@@ -61,25 +62,35 @@ def get_avatar_url(channel_url: str) -> str:
     return best_thumbnail["url"]
 
 
-if __name__ == "__main__":
-    try:
-        url = sys.argv[1]
-    except IndexError:
-        sys.exit(f"Usage: {__file__} URL")
+class ChannelInfo(TypedDict):
+    id: str
+    name: str
+    url: str
+    avatar_url: str
+
 
-    tmp_dir = tempfile.mkdtemp()
+class VideoInfo(TypedDict):
+    url: str
+    title: str
+    description: str
+    video_path: Path
+    thumbnail_path: Path
+    subtitle_path: Path
+    channel: ChannelInfo
 
-    os.chdir(tmp_dir)
+
+def download_video(url: str) -> VideoInfo:
+    tmp_dir = Path(tempfile.mkdtemp())
+
+    ydl_opts["outtmpl"] = str(tmp_dir / "%(title)s.%(ext)s")
 
     with YoutubeDL(ydl_opts) as ydl:
         video_info = ydl.extract_info(url)
 
-    downloaded_files = os.listdir(tmp_dir)
-
-    video_path = next(p for p in downloaded_files if p.endswith(".mp4"))
-    thumbnail_path = next(p for p in downloaded_files if p.endswith(".jpg"))
+    video_path = next(p for p in tmp_dir.iterdir() if p.suffix == ".mp4")
+    thumbnail_path = next(p for p in tmp_dir.iterdir() if p.suffix == ".jpg")
     try:
-        subtitle_path = next(p for p in downloaded_files if p.endswith(".vtt"))
+        subtitle_path = next(p for p in tmp_dir.iterdir() if p.suffix == ".vtt")
     except StopIteration:
         subtitle_path = None
 
@@ -100,4 +111,29 @@ if __name__ == "__main__":
         "channel": channel,
     }
 
-    print(json.dumps(result, indent=2))
+    return result
+
+
+class PathEncoder(json.JSONEncoder):
+    """
+    Custom JSON encoder that encodes paths as a string.
+    """
+
+    def default(self, o):
+        if isinstance(o, Path):
+            return str(o.absolute())
+        else:
+            return super().default(o)
+
+
+if __name__ == "__main__":
+    try:
+        url = sys.argv[1]
+    except IndexError:
+        sys.exit(f"Usage: {__file__} URL")
+
+    video_info = download_video(url)
+
+    json_string = json.dumps(video_info, indent=2, cls=PathEncoder)
+
+    print(json_string)