From aa70b68f98402a5a971e6960188c45479f3d7ef2 Mon Sep 17 00:00:00 2001 From: Lalit Gupta Date: Tue, 11 Aug 2026 16:02:19 +0530 Subject: [PATCH] Return the permanent url from generate_url Generated assets only ever had a signed url with the expiry baked in - 6 days for images, 7 for audio. Callers store what generate_url returns, so the link dies and nothing can renew it. The server now gives each asset a permanent url. Prefer it, and keep the signed url as a fallback for assets that predate it. Note the returned url is a redirect rather than a direct storage url: fetch it with a client that follows redirects, and a HEAD returns the redirect's content-length rather than the object's. Audio gained the url attribute; Image already had it. --- videodb/audio.py | 15 +++++++++++++-- videodb/image.py | 13 +++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/videodb/audio.py b/videodb/audio.py index 9dd6987..79978ca 100644 --- a/videodb/audio.py +++ b/videodb/audio.py @@ -14,6 +14,7 @@ class Audio: :ivar str collection_id: ID of the collection this audio belongs to :ivar str name: Name of the audio file :ivar float length: Duration of the audio in seconds + :ivar str url: Permanent url of the audio, when the server provides one :ivar list transcript: Timestamped transcript segments :ivar str transcript_text: Full transcript text """ @@ -26,6 +27,7 @@ def __init__( self.collection_id = collection_id self.name = kwargs.get("name", None) self.length = float(kwargs.get("length", 0.0)) + self.url = kwargs.get("url", None) self.transcript = kwargs.get("transcript", None) self.transcript_text = kwargs.get("transcript_text", None) @@ -39,12 +41,21 @@ def __repr__(self) -> str: ) def generate_url(self) -> str: - """Generate the signed url of the audio. + """Get a url for the audio. + + Returns the audio's permanent url when the server provides one. That url + never expires, so it is safe to store; it redirects to storage, so fetch it + with a client that follows redirects. + + Falls back to a signed storage url for audio created before permanent urls + existed. Those expire after a few days -- do not persist them. :raises InvalidRequestError: If the get_url fails - :return: The signed url of the audio + :return: The url of the audio :rtype: str """ + if self.url: + return self.url url_data = self._connection.post( path=f"{ApiPath.audio}/{self.id}/{ApiPath.generate_url}", params={"collection_id": self.collection_id}, diff --git a/videodb/image.py b/videodb/image.py index 97c8902..eed998c 100644 --- a/videodb/image.py +++ b/videodb/image.py @@ -29,12 +29,21 @@ def __repr__(self) -> str: ) def generate_url(self) -> str: - """Generate the signed url of the image. + """Get a url for the image. + + Returns the image's permanent url when the server provides one. That url + never expires, so it is safe to store; it redirects to storage, so fetch it + with a client that follows redirects. + + Falls back to a signed storage url for images created before permanent urls + existed. Those expire after a few days -- do not persist them. :raises InvalidRequestError: If the get_url fails - :return: The signed url of the image + :return: The url of the image :rtype: str """ + if self.url: + return self.url url_data = self._connection.post( path=f"{ApiPath.image}/{self.id}/{ApiPath.generate_url}", params={"collection_id": self.collection_id},