fix(thumbnails): address the final review of the thumbnail format work
- ffmpeg writes JPEG XL with -update 1, so a % in a file name is never an image-sequence pattern, and reads videos as file:<path>. The clear and a startup sweep remove temp files that interrupted writes left behind. - Convert outdated converts each group of same-stem files once, from the first source in the R2 order. An old preview that is already gone counts as removed, and each file's bytes count once. A current preview is only kept if it decodes. - An encoder that fails makes the write detect encoders again once. Another encoder is used if one is found; with none, the file is written as WebP and the availability Appearance shows is refreshed. - Changing the thumbnail format, quality or size while Convert outdated runs returns 409 busy. If the format still changes underneath, the job stops without removing anything. - Legacy preview spellings are matched case-insensitively, so X_thumb.JPG counts as outdated. - History's cover uses the same lookup as Images. It serves .jxl only when Accept lists image/jxl, and otherwise the original or the placeholder. - A video whose thumbnail the browser cannot show keeps its own element (Images) or its label (Library).
This commit is contained in:
@@ -1424,6 +1424,71 @@ class TestThumbnailSettings(AdminAPITestCase):
|
||||
self.assertEqual(self._read_config()["thumbnails"]["format"], "jxl")
|
||||
self.assertEqual(PromptManagerConfig.THUMBNAIL_FORMAT, "jxl")
|
||||
|
||||
async def _running_job(self, mode):
|
||||
from py.api.jobs import THUMBNAILS_JOB_KIND
|
||||
from py.api.thumbnail_convert import CONVERT_JOB_TITLE
|
||||
|
||||
gate = asyncio.Event()
|
||||
|
||||
async def runner(job):
|
||||
await gate.wait()
|
||||
job.finish("finished")
|
||||
|
||||
job = self.api._jobs.start(
|
||||
THUMBNAILS_JOB_KIND,
|
||||
CONVERT_JOB_TITLE if mode == "convert" else "Thumbnail generation",
|
||||
admission="guarded",
|
||||
scope={"mode": mode},
|
||||
runner=runner,
|
||||
)
|
||||
|
||||
async def release():
|
||||
gate.set()
|
||||
await asyncio.wait_for(job.task, 5)
|
||||
|
||||
self.addAsyncCleanup(release)
|
||||
return job
|
||||
|
||||
async def test_thumbnail_changes_wait_for_a_running_conversion(self):
|
||||
# Review I4: a convert job's format is fixed; changing the format,
|
||||
# quality or size limit under it is refused with the job named.
|
||||
PromptManagerConfig.update_config(
|
||||
{"thumbnails": {"format": "webp", "quality": 80, "edge": 512}}
|
||||
)
|
||||
job = await self._running_job("convert")
|
||||
for change in (
|
||||
{"thumbnail_format": "jpeg"},
|
||||
{"thumbnail_quality": 70},
|
||||
{"thumbnail_edge": 300},
|
||||
):
|
||||
resp = await self.client.request("POST", self.URL, json=change)
|
||||
body = await resp.json()
|
||||
self.assertEqual(resp.status, 409, change)
|
||||
self.assertEqual(body["code"], "busy")
|
||||
self.assertEqual(body["running"]["id"], job.id)
|
||||
self.assertIn("Convert outdated thumbnails", body["error"])
|
||||
self.assertIn("thumbnail", body["error"].lower())
|
||||
self.assertEqual(
|
||||
(
|
||||
PromptManagerConfig.THUMBNAIL_FORMAT,
|
||||
PromptManagerConfig.THUMBNAIL_QUALITY,
|
||||
PromptManagerConfig.THUMBNAIL_EDGE,
|
||||
),
|
||||
("webp", 80, 512),
|
||||
)
|
||||
# The same values, or other settings, still save.
|
||||
resp = await self.client.request(
|
||||
"POST", self.URL, json={"thumbnail_format": "webp", "thumbnail_quality": 80}
|
||||
)
|
||||
self.assertEqual(resp.status, 200)
|
||||
|
||||
async def test_a_missing_previews_job_does_not_block_thumbnail_changes(self):
|
||||
await self._running_job("missing")
|
||||
resp = await self.client.request(
|
||||
"POST", self.URL, json={"thumbnail_format": "jpeg"}
|
||||
)
|
||||
self.assertEqual(resp.status, 200)
|
||||
|
||||
|
||||
class TestDiagnostics(AdminAPITestCase):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user