From 16510c1e32ea8fcf1b20af5a8cd0ccd0a3e908bb Mon Sep 17 00:00:00 2001 From: anAirdrop Date: Mon, 13 Jul 2026 12:17:29 -0400 Subject: [PATCH] Use url_utils.url_from_filepath() for file:// URLs in docs/examples The adapter-writing tutorial and two example scripts (conform.py, shot_detect.py) built target_url values by concatenating "file://" directly onto a filesystem path. Per RFC 3986, this only produces a correct file URI when the path is POSIX-absolute; for a relative path or a Windows drive-letter path (e.g. "C:/show/movie.mov"), the first path segment is parsed as the URL's host/netloc, silently corrupting the URL (e.g. "file://C:/show/movie.mov" instead of the correct "file:///C:/show/movie.mov"). opentimelineio.url_utils.url_from_filepath() already handles this correctly (absolute vs. relative paths, Windows drive letters, UNC paths) and is covered by tests/test_url_conversions.py. This change points the docs and examples at that existing utility instead of hand-rolling the URL. Signed-off-by: Ian Roth --- docs/tutorials/write-an-adapter.md | 13 +++++++++++-- examples/conform.py | 2 +- examples/shot_detect.py | 4 ++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/tutorials/write-an-adapter.md b/docs/tutorials/write-an-adapter.md index b37675ad9d..6942c87e26 100644 --- a/docs/tutorials/write-an-adapter.md +++ b/docs/tutorials/write-an-adapter.md @@ -165,9 +165,18 @@ Note that all metadata should be nested inside a sub-dictionary (in this example Clip media (if known) should be linked like this: ```python clip.media_reference = otio.schema.ExternalReference( - target_url="file://example/movie.mov" + target_url=otio.url_utils.url_from_filepath("/show/seq/shot/movie.mov") ) ``` +Prefer `otio.url_utils.url_from_filepath()` over building the `file://` URL by +hand (e.g. `"file://" + path`). A hand-built URL is easy to get subtly wrong — +for example `"file://" + "/show/movie.mov"` yields `file:///show/movie.mov` +correctly, but `"file://" + "show/movie.mov"` (no leading slash, or a +Windows-style path like `C:\show\movie.mov`) produces a URL whose first path +segment is parsed as the host per [RFC 3986](https://tools.ietf.org/html/rfc3986#section-2.1), +silently dropping part of the path. `url_from_filepath()` handles absolute vs. +relative paths and Windows drive letters correctly, and its output round-trips +through `otio.url_utils.filepath_from_url()`. Some formats don't support direct links to media, but focus on metadata instead. It is fine to leave the media_reference empty ('None') if your adapter doesn't know a real file path or URL for the media. @@ -186,7 +195,7 @@ Note that the source_range of the clip is not necessarily the same as the availa If you know the range of media available at that Media Reference's URL, then you can specify it like this: ```python clip.media_reference = otio.schema.ExternalReference( - target_url="file://example/movie.mov", + target_url=otio.url_utils.url_from_filepath("/show/seq/shot/movie.mov"), available_range=otio.opentime.TimeRange( start_time=otio.opentime.RationalTime(100, 24), # frame 100 @ 24fps duration=otio.opentime.RationalTime(500, 24) # 500 frames @ 24fps diff --git a/examples/conform.py b/examples/conform.py index 4d7bb7dba7..2d5d852a4f 100755 --- a/examples/conform.py +++ b/examples/conform.py @@ -109,7 +109,7 @@ def _conform_timeline(timeline, folder): # relink to the found path clip.media_reference = otio.schema.ExternalReference( - target_url="file://" + new_path, + target_url=otio.url_utils.url_from_filepath(new_path), available_range=None # the available range is unknown without # opening the file ) diff --git a/examples/shot_detect.py b/examples/shot_detect.py index ef46e04c17..a5dd071b8f 100755 --- a/examples/shot_detect.py +++ b/examples/shot_detect.py @@ -108,7 +108,7 @@ def _timeline_with_single_clip(name, full_path, dryrun=False): available_range = _media_start_end_of(full_path, fps) media_reference = otio.schema.ExternalReference( - target_url="file://" + full_path, + target_url=otio.url_utils.url_from_filepath(full_path), available_range=available_range ) @@ -167,7 +167,7 @@ def _timeline_with_breaks(name, full_path, dryrun=False): available_range = _media_start_end_of(full_path, fps) clip.media_reference = otio.schema.ExternalReference( - target_url="file://" + full_path, + target_url=otio.url_utils.url_from_filepath(full_path), available_range=available_range ) track.append(clip)