Compare commits

...
Author SHA1 Message Date
bflatasticandClaude Opus 5.5 a8ef87a097 Track which app card or icon visitors pick on the Apps pages
Adds an app_select event to the /apps icon row, the lineup cards and the
"More from the family" sibling cards, so link_location shows which entry
point sent visitors to each app page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 19:51:34 +07:00
bflatasticandClaude Opus 5.5 084aa1b4b4 Add Google Analytics to the marketing site
Loads GA4 (gtag.js) when NEXT_PUBLIC_GA_MEASUREMENT_ID is set and sends
events for the site's conversion paths: desktop/Crafting App downloads,
webapp and CTA clicks, social and contact links, pricing checkout,
the H3 promptbox and auth gate, beta leads, video plays, and shares.

Most link clicks are classified by href in one delegated listener, so
components only change where an action isn't a plain link. Analytics
stays off when the env var is unset (local dev, deploy previews).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 19:34:44 +07:00
Brandon Thomas 03dea1c660 app downloads 2 (#49) 2026-10-05 06:38:56 -04:00
Brandon Thomas b5272188f7 App downloads (#48) 2026-10-05 06:17:51 -04:00
bflatastic b02f710c76 page scroll fix 2026-10-04 21:15:58 +07:00
BFlat 708de238c5 Add VectorCraft, EffectCraft, DesignCraft and app icons to Craft Apps pages (#47)
## Summary

Updates the Craft Apps pages to cover the org's seven Craft repos and
adds each app's icon.

- **VectorCraft replaces DrawCraft.** The repo was renamed, so the page
moves to `/apps/vectorcraft`. `/apps/drawcraft` permanently redirects
there, because every Craft README's app table still links the old URL.
- **New pages:** `/apps/effectcraft` (motion graphics & VFX) and
`/apps/designcraft` (page layout & publishing), each with copy,
screenshots and a share image.
- **App icons:**
  - A row in the `/apps` header linking to every app.
- On each lineup card and sibling-app card, overlapping the bottom of
the screenshot.
  - Above the wordmark in each app's hero.
- **PhotoCraft installers (`v0.1.1-rc.4`):**
  - A download button in the hero.
- macOS, Windows and Linux buttons, plus an "All builds and checksums"
link.
  - An "Installers ready" badge on its card.
  - `softwareVersion` and `downloadUrl` in its JSON-LD.
- **Share images:** each app's image has its icon in the corner, and the
`/apps` share image is the seven icons in a row.
- **Press kit:** a new "Crafting Apps icons" section, linking to each
repo's 1024px PNG.

## Details
- **Data file:** everything is still driven by
`src/lib/crafting-apps.ts`. An app's `release` field (version plus a
filename suffix per platform) produces its download buttons, so other
apps need one data entry when they ship.
- **Icons:** each repo's 512px render, converted to a 256px WebP with
transparency, about 25 KB each.
- **DesignCraft gallery:** its magazine pages are portrait, so they show
four across instead of in the 16:10 layout.
- **Hub copy:** the app count is worked out from the data ("Seven
apps"). The lineup is four columns wide on large screens, so seven cards
plus the Discord cell fill two even rows.
- **Colors:** EffectCraft and DesignCraft get their icon tile colors as
accents, with AA-contrast text colors in light and dark.
- **Adobe names:** the copy still never names Adobe products.

## Testing
- A clean `npm ci && npm run build` (what Netlify runs) passes with a
clean TypeScript check, and all seven app pages are generated as static
HTML.
- `next start` serves every page and asset with a 200, `/apps/drawcraft`
returns a 308 to `/apps/vectorcraft`, and an unknown slug returns 404.
- Checked visually at 1440px and at a 390px mobile viewport: no broken
images or horizontal overflow, and the mobile icon row wraps four over
three.
- The PhotoCraft download links and every 1024px icon URL in the press
kit resolve.
2026-10-02 23:06:56 +07:00
Brandon Thomas fc78787f30 better website hero text (#46) 2026-10-01 20:22:51 -04:00
BFlat e92253c5b4 Add Craft Apps marketing pages to the website (#45)
## Summary

Marketing pages on getartcraft.com for the five Craft Apps: PhotoCraft,
DrawCraft, FilmCraft, LightCraft and PrintCraft. Everything is driven by
one data file, `src/lib/crafting-apps.ts`.

- **`/apps` hub:** lineup cards for all five apps, shared principles,
and Discord calls to action.
- **`/apps/<slug>`:**
  - Hero screenshot with Discord, GitHub and share links.
  - Six highlights and a screenshot gallery.
- "Get it": a Discord waitlist for installers, next to build-from-source
steps with a copy button.
  - Cards linking to the sibling apps.
- **Nav:** a "Craft Apps" link after a divider following Support, and a
Discord button on desktop.
- **Footer:** a "Craft Apps" column.
- **Sitemap:** entries for the hub and all five app pages.
- **Press kit:** all 20 app screenshots, with full-resolution downloads.

## Branding
- **Colors:** each app has its own accent (`.app-*` in `globals.css`),
AA-contrast in light and dark.
- **Wordmarks:** text only, with "Craft" set in the app's color. These
pages don't use the serif accent font.
- **Share previews:** a 1200×630 image per app plus one for the hub, and
`SoftwareApplication` JSON-LD on each app page.
- **Adobe:** the copy never names Adobe products.

## Notes
- No installers exist yet in any of the five repos, so each page sends
visitors to Discord and to building from source. When an app ships a
release, adding `downloads` to its entry in `crafting-apps.ts` replaces
that block with download buttons.
- Screenshots come from each repo's `docs/images`, show only
public-domain artwork, and are converted to WebP at 1600px.
- **Small refactors:**
- New shared `DiscordButton`, now also used by the community strip and
the press kit.
  - New `CopyButton` in the UI kit.
  - `SITE_URL`/`siteUrl` moved into `lib/links.ts`.
  - New `kicker`/`mediaTag` props on `CampaignHero`.

## Testing
- A clean `npm ci && npm run build`, the same build Netlify runs,
passes: TypeScript is clean, `/apps` is static, and all five app pages
prerender as static HTML.
- `next start` serves all six routes plus their images, and an unknown
slug returns 404.
- Each page's Open Graph and Twitter tags render with the per-app image.
- Checked visually at 1440px and 1024px (the nav fits) and at a 390px
mobile viewport.
2026-10-01 22:56:48 +07:00
Brandon Thomas 847edc81e5 Merge pull request #44 from storytold/bt-no-more-gif-thumbnails
webp instead of gif thumbnails
2026-09-28 01:15:13 -04:00
Brandon Thomas 2e596cc4b3 ffmpeg fallback on mac 2026-09-28 01:07:45 -04:00
Brandon Thomas c3d72fb321 Generate WebP thumbnails for videos instead of Gif (except: MacOS 10.15)
MacOS Catalina 10.15 released October 7, 2019 can't do gifs in
safari/webkit.
2026-09-28 00:52:07 -04:00
Brandon Thomas cd27599c0a Merge pull request #43 from storytold/bt-additional-cors-tests
additional cors tests to prevent regressions
2026-09-27 23:43:50 -04:00
190 changed files with 4255 additions and 383 deletions

No files matched your search

@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id > ?\nORDER BY m.id ASC\nLIMIT ?\n ",
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id > ?\nORDER BY m.id ASC\nLIMIT ?\n ",
"describe": {
"columns": [
{
@@ -75,6 +75,16 @@
},
{
"ordinal": 7,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 8,
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
@@ -84,7 +94,7 @@
}
},
{
"ordinal": 8,
"ordinal": 9,
"name": "maybe_creator_username",
"type_info": {
"type": "VarString",
@@ -94,7 +104,7 @@
}
},
{
"ordinal": 9,
"ordinal": 10,
"name": "maybe_creator_display_name",
"type_info": {
"type": "VarString",
@@ -104,7 +114,7 @@
}
},
{
"ordinal": 10,
"ordinal": 11,
"name": "maybe_creator_gravatar_hash",
"type_info": {
"type": "String",
@@ -114,7 +124,7 @@
}
},
{
"ordinal": 11,
"ordinal": 12,
"name": "maybe_file_cover_image_public_bucket_hash",
"type_info": {
"type": "VarString",
@@ -124,7 +134,7 @@
}
},
{
"ordinal": 12,
"ordinal": 13,
"name": "maybe_file_cover_image_public_bucket_prefix",
"type_info": {
"type": "VarString",
@@ -134,7 +144,7 @@
}
},
{
"ordinal": 13,
"ordinal": 14,
"name": "maybe_file_cover_image_public_bucket_extension",
"type_info": {
"type": "VarString",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "creator_set_visibility: enums::common::visibility::Visibility",
"type_info": {
"type": "String",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
"type_info": {
"type": "VarString",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -174,7 +184,7 @@
}
},
{
"ordinal": 17,
"ordinal": 18,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -184,7 +194,7 @@
}
},
{
"ordinal": 18,
"ordinal": 19,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -212,6 +222,7 @@
true,
true,
true,
true,
false,
true,
true,
@@ -219,5 +230,5 @@
false
]
},
"hash": "a52cc2d6e86607c430173553230b3794a4c1ada07274a975c04128e47d3b7f83"
"hash": "251082fa59374723719147af19e3411300291f1c7de0acffd078dff73f7fcf40"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_mime_type,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n m.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n\n m.maybe_title,\n m.maybe_text_transcript,\n\n m.maybe_origin_filename,\n\n m.maybe_duration_millis,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n prompts.maybe_other_args as maybe_other_prompt_args,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.is_user_upload,\n m.is_intermediate_system_file,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nLEFT OUTER JOIN prompts\n ON prompts.token = m.maybe_prompt_token\nWHERE\n m.maybe_batch_token = ?\n ",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_mime_type,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n m.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n\n m.maybe_title,\n m.maybe_text_transcript,\n\n m.maybe_origin_filename,\n\n m.maybe_duration_millis,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n prompts.maybe_other_args as maybe_other_prompt_args,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.is_user_upload,\n m.is_intermediate_system_file,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nLEFT OUTER JOIN prompts\n ON prompts.token = m.maybe_prompt_token\nWHERE\n m.maybe_batch_token = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n ",
"describe": {
"columns": [
{
@@ -255,6 +255,16 @@
},
{
"ordinal": 25,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 26,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -264,7 +274,7 @@
}
},
{
"ordinal": 26,
"ordinal": 27,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -303,9 +313,10 @@
false,
true,
true,
true,
false,
false
]
},
"hash": "971471f349bcd22364b16b5d6c68e807ed897101eec1e8a62a7b8fab3359fa0c"
"hash": "29f205860e3a790cc0b4b4b8bbd28bbc92a9df27ba62cc2688be1aa195b1fcf2"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n pci.media_token as `media_token: tokens::tokens::media_files::MediaFileToken`,\n pci.context_semantic_type as `context_semantic_type: enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension\n\nFROM prompt_context_items pci\nJOIN media_files m\nON\n pci.media_token = m.token\nWHERE\n pci.prompt_token = ?\nORDER BY pci.id ASC\n ",
"query": "\nSELECT\n pci.media_token as `media_token: tokens::tokens::media_files::MediaFileToken`,\n pci.context_semantic_type as `context_semantic_type: enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n m.created_at as `created_at: DateTime<Utc>`\n\nFROM prompt_context_items pci\nJOIN media_files m\nON\n pci.media_token = m.token\nWHERE\n pci.prompt_token = ?\nORDER BY pci.id ASC\n ",
"describe": {
"columns": [
{
@@ -52,6 +52,26 @@
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 5,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 6,
"name": "created_at: DateTime<Utc>",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
@@ -62,8 +82,10 @@
false,
false,
true,
true
true,
true,
false
]
},
"hash": "094eb099cde85835db0662c2700c6b82644b51bf9b3609391ed4d2945fe5e98f"
"hash": "2e53d51429bf380b141a8a680d1fc17990ab02f822cae4d8def2d1126e513549"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n fmf.created_at as `added_to_folder_at: DateTime<Utc>`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n mf.is_intermediate_system_file as `is_intermediate_system_file: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM folder_media_files fmf\nJOIN media_files mf\n ON mf.token = fmf.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE fmf.folder_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n fmf.created_at as `added_to_folder_at: DateTime<Utc>`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n mf.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n mf.is_intermediate_system_file as `is_intermediate_system_file: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM folder_media_files fmf\nJOIN media_files mf\n ON mf.token = fmf.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE fmf.folder_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
@@ -95,6 +95,16 @@
},
{
"ordinal": 9,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 10,
"name": "maybe_cover_public_bucket_directory_hash?",
"type_info": {
"type": "VarString",
@@ -104,7 +114,7 @@
}
},
{
"ordinal": 10,
"ordinal": 11,
"name": "maybe_cover_public_bucket_prefix?",
"type_info": {
"type": "VarString",
@@ -114,7 +124,7 @@
}
},
{
"ordinal": 11,
"ordinal": 12,
"name": "maybe_cover_public_bucket_extension?",
"type_info": {
"type": "VarString",
@@ -124,7 +134,7 @@
}
},
{
"ordinal": 12,
"ordinal": 13,
"name": "creator_set_visibility: Visibility",
"type_info": {
"type": "String",
@@ -134,7 +144,7 @@
}
},
{
"ordinal": 13,
"ordinal": 14,
"name": "is_user_upload: bool",
"type_info": {
"type": "Tiny",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "is_intermediate_system_file: bool",
"type_info": {
"type": "Tiny",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_prompt_token: PromptToken",
"type_info": {
"type": "VarString",
@@ -174,7 +184,7 @@
}
},
{
"ordinal": 17,
"ordinal": 18,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
@@ -184,7 +194,7 @@
}
},
{
"ordinal": 18,
"ordinal": 19,
"name": "maybe_duration_millis",
"type_info": {
"type": "Long",
@@ -194,7 +204,7 @@
}
},
{
"ordinal": 19,
"ordinal": 20,
"name": "maybe_frame_width",
"type_info": {
"type": "Long",
@@ -204,7 +214,7 @@
}
},
{
"ordinal": 20,
"ordinal": 21,
"name": "maybe_frame_height",
"type_info": {
"type": "Long",
@@ -214,7 +224,7 @@
}
},
{
"ordinal": 21,
"ordinal": 22,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -224,7 +234,7 @@
}
},
{
"ordinal": 22,
"ordinal": 23,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -250,6 +260,7 @@
true,
true,
true,
true,
false,
false,
false,
@@ -263,5 +274,5 @@
false
]
},
"hash": "c0ba9f9aa3184b1cb3d714173b31e530e201a6c24afd29ad534cf7f3954c7316"
"hash": "41b47b3e0874676651bd7de2c6c3bd2c25db56c51ce6e16810f4a7f56dfb48ab"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n\n jobs.status as `status: enums::common::job_status_plus::JobStatusPlus`,\n jobs.attempt_count,\n\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n\n jobs.product_category as `product_category: enums::by_table::generic_inference_jobs::inference_job_product_category::InferenceJobProductCategory`,\n jobs.inference_category as `inference_category: enums::by_table::generic_inference_jobs::inference_category::InferenceCategory`,\n jobs.maybe_model_type,\n jobs.maybe_model_token,\n jobs.maybe_raw_inference_text,\n jobs.maybe_inference_args,\n jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n\n jobs.frontend_failure_category as `maybe_frontend_failure_category: enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory`,\n jobs.failure_reason,\n\n jobs.on_success_result_entity_type as maybe_result_entity_type,\n jobs.on_success_result_entity_token as maybe_result_entity_token,\n jobs.on_success_result_batch_token as maybe_result_batch_token,\n\n model_weights.title as maybe_model_weights_title,\n tts_models.title as maybe_tts_model_title,\n voice_conversion_models.title as maybe_voice_conversion_model_title,\n\n tts_results.public_bucket_wav_audio_path as maybe_tts_public_bucket_path,\n voice_conversion_results.public_bucket_hash as maybe_voice_conversion_public_bucket_hash,\n\n media_files.public_bucket_directory_hash as maybe_media_file_public_bucket_directory_hash,\n media_files.maybe_public_bucket_prefix as maybe_media_file_public_bucket_prefix,\n media_files.maybe_public_bucket_extension as maybe_media_file_public_bucket_extension,\n\n jobs.assigned_worker as maybe_assigned_worker,\n jobs.assigned_cluster as maybe_assigned_cluster,\n\n jobs.is_keepalive_required,\n\n jobs.created_at,\n jobs.updated_at,\n\n jobs.first_started_at as maybe_first_started_at,\n jobs.successfully_completed_at as maybe_successfully_completed_at,\n\n NOW() as database_clock\n\nFROM generic_inference_jobs as jobs\n\nLEFT OUTER JOIN model_weights ON jobs.maybe_model_token = model_weights.token\nLEFT OUTER JOIN tts_models ON jobs.maybe_model_token = tts_models.token\nLEFT OUTER JOIN voice_conversion_models ON jobs.maybe_model_token = voice_conversion_models.token\n\nLEFT OUTER JOIN tts_results ON jobs.on_success_result_entity_token = tts_results.token\nLEFT OUTER JOIN voice_conversion_results ON jobs.on_success_result_entity_token = voice_conversion_results.token\nLEFT OUTER JOIN media_files ON jobs.on_success_result_entity_token = media_files.token\n\nWHERE jobs.token = ?\n ",
"query": "\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n\n jobs.status as `status: enums::common::job_status_plus::JobStatusPlus`,\n jobs.attempt_count,\n\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n\n jobs.product_category as `product_category: enums::by_table::generic_inference_jobs::inference_job_product_category::InferenceJobProductCategory`,\n jobs.inference_category as `inference_category: enums::by_table::generic_inference_jobs::inference_category::InferenceCategory`,\n jobs.maybe_model_type,\n jobs.maybe_model_token,\n jobs.maybe_raw_inference_text,\n jobs.maybe_inference_args,\n jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n\n jobs.frontend_failure_category as `maybe_frontend_failure_category: enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory`,\n jobs.failure_reason,\n\n jobs.on_success_result_entity_type as maybe_result_entity_type,\n jobs.on_success_result_entity_token as maybe_result_entity_token,\n jobs.on_success_result_batch_token as maybe_result_batch_token,\n\n model_weights.title as maybe_model_weights_title,\n tts_models.title as maybe_tts_model_title,\n voice_conversion_models.title as maybe_voice_conversion_model_title,\n\n tts_results.public_bucket_wav_audio_path as maybe_tts_public_bucket_path,\n voice_conversion_results.public_bucket_hash as maybe_voice_conversion_public_bucket_hash,\n\n media_files.public_bucket_directory_hash as maybe_media_file_public_bucket_directory_hash,\n media_files.maybe_public_bucket_prefix as maybe_media_file_public_bucket_prefix,\n media_files.maybe_public_bucket_extension as maybe_media_file_public_bucket_extension,\n media_files.maybe_thumbnail_version as `maybe_media_file_thumbnail_version?: u8`,\n media_files.created_at as `maybe_media_file_created_at?: DateTime<Utc>`,\n\n jobs.assigned_worker as maybe_assigned_worker,\n jobs.assigned_cluster as maybe_assigned_cluster,\n\n jobs.is_keepalive_required,\n\n jobs.created_at,\n jobs.updated_at,\n\n jobs.first_started_at as maybe_first_started_at,\n jobs.successfully_completed_at as maybe_successfully_completed_at,\n\n NOW() as database_clock\n\nFROM generic_inference_jobs as jobs\n\nLEFT OUTER JOIN model_weights ON jobs.maybe_model_token = model_weights.token\nLEFT OUTER JOIN tts_models ON jobs.maybe_model_token = tts_models.token\nLEFT OUTER JOIN voice_conversion_models ON jobs.maybe_model_token = voice_conversion_models.token\n\nLEFT OUTER JOIN tts_results ON jobs.on_success_result_entity_token = tts_results.token\nLEFT OUTER JOIN voice_conversion_results ON jobs.on_success_result_entity_token = voice_conversion_results.token\nLEFT OUTER JOIN media_files ON jobs.on_success_result_entity_token = media_files.token\n\nWHERE jobs.token = ?\n ",
"describe": {
"columns": [
{
@@ -265,6 +265,26 @@
},
{
"ordinal": 26,
"name": "maybe_media_file_thumbnail_version?: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 27,
"name": "maybe_media_file_created_at?: DateTime<Utc>",
"type_info": {
"type": "Timestamp",
"flags": "MULTIPLE_KEY | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 28,
"name": "maybe_assigned_worker",
"type_info": {
"type": "VarString",
@@ -274,7 +294,7 @@
}
},
{
"ordinal": 27,
"ordinal": 29,
"name": "maybe_assigned_cluster",
"type_info": {
"type": "VarString",
@@ -284,7 +304,7 @@
}
},
{
"ordinal": 28,
"ordinal": 30,
"name": "is_keepalive_required",
"type_info": {
"type": "Tiny",
@@ -294,7 +314,7 @@
}
},
{
"ordinal": 29,
"ordinal": 31,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -304,7 +324,7 @@
}
},
{
"ordinal": 30,
"ordinal": 32,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -314,7 +334,7 @@
}
},
{
"ordinal": 31,
"ordinal": 33,
"name": "maybe_first_started_at",
"type_info": {
"type": "Timestamp",
@@ -324,7 +344,7 @@
}
},
{
"ordinal": 32,
"ordinal": 34,
"name": "maybe_successfully_completed_at",
"type_info": {
"type": "Timestamp",
@@ -334,7 +354,7 @@
}
},
{
"ordinal": 33,
"ordinal": 35,
"name": "database_clock",
"type_info": {
"type": "Datetime",
@@ -376,6 +396,8 @@
true,
true,
true,
true,
true,
false,
false,
false,
@@ -384,5 +406,5 @@
false
]
},
"hash": "6948edca3f06643605695665df51cc94f897e2cb9a2d6906910d58d4d56ee73e"
"hash": "4b3fa0dcfb916ba7af04ae35c69a2b75d1f4a4858e8f94f87d74c69fccc32079"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.maybe_project_type as `project_type!: enums::by_table::media_files::media_file_project_type::MediaFileProjectType`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = 'project'\n AND m.maybe_project_type IS NOT NULL\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id > ?\nORDER BY m.id ASC\nLIMIT ?\n ",
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.maybe_project_type as `project_type!: enums::by_table::media_files::media_file_project_type::MediaFileProjectType`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = 'project'\n AND m.maybe_project_type IS NOT NULL\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id > ?\nORDER BY m.id ASC\nLIMIT ?\n ",
"describe": {
"columns": [
{
@@ -85,6 +85,16 @@
},
{
"ordinal": 8,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 9,
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
@@ -94,7 +104,7 @@
}
},
{
"ordinal": 9,
"ordinal": 10,
"name": "maybe_creator_username",
"type_info": {
"type": "VarString",
@@ -104,7 +114,7 @@
}
},
{
"ordinal": 10,
"ordinal": 11,
"name": "maybe_creator_display_name",
"type_info": {
"type": "VarString",
@@ -114,7 +124,7 @@
}
},
{
"ordinal": 11,
"ordinal": 12,
"name": "maybe_creator_gravatar_hash",
"type_info": {
"type": "String",
@@ -124,7 +134,7 @@
}
},
{
"ordinal": 12,
"ordinal": 13,
"name": "maybe_file_cover_image_public_bucket_hash",
"type_info": {
"type": "VarString",
@@ -134,7 +144,7 @@
}
},
{
"ordinal": 13,
"ordinal": 14,
"name": "maybe_file_cover_image_public_bucket_prefix",
"type_info": {
"type": "VarString",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "maybe_file_cover_image_public_bucket_extension",
"type_info": {
"type": "VarString",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "creator_set_visibility: enums::common::visibility::Visibility",
"type_info": {
"type": "String",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -174,7 +184,7 @@
}
},
{
"ordinal": 17,
"ordinal": 18,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -184,7 +194,7 @@
}
},
{
"ordinal": 18,
"ordinal": 19,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -213,11 +223,12 @@
true,
true,
true,
true,
false,
true,
false,
false
]
},
"hash": "d8f4ce4e1026c71a7be8fd87f00f6f8858c401701da1ca3316eed1ed971d4bb2"
"hash": "503c8faa461462b3e2f8cae3cb702922ad54a8ddbdb01d1e7fc785ca1b7dd4c4"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.maybe_project_type as `project_type!: enums::by_table::media_files::media_file_project_type::MediaFileProjectType`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = 'project'\n AND m.maybe_project_type IS NOT NULL\n AND m.maybe_project_type = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id > ?\nORDER BY m.id ASC\nLIMIT ?\n ",
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.maybe_project_type as `project_type!: enums::by_table::media_files::media_file_project_type::MediaFileProjectType`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = 'project'\n AND m.maybe_project_type IS NOT NULL\n AND m.maybe_project_type = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id < ?\nORDER BY m.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
@@ -85,6 +85,16 @@
},
{
"ordinal": 8,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 9,
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
@@ -94,7 +104,7 @@
}
},
{
"ordinal": 9,
"ordinal": 10,
"name": "maybe_creator_username",
"type_info": {
"type": "VarString",
@@ -104,7 +114,7 @@
}
},
{
"ordinal": 10,
"ordinal": 11,
"name": "maybe_creator_display_name",
"type_info": {
"type": "VarString",
@@ -114,7 +124,7 @@
}
},
{
"ordinal": 11,
"ordinal": 12,
"name": "maybe_creator_gravatar_hash",
"type_info": {
"type": "String",
@@ -124,7 +134,7 @@
}
},
{
"ordinal": 12,
"ordinal": 13,
"name": "maybe_file_cover_image_public_bucket_hash",
"type_info": {
"type": "VarString",
@@ -134,7 +144,7 @@
}
},
{
"ordinal": 13,
"ordinal": 14,
"name": "maybe_file_cover_image_public_bucket_prefix",
"type_info": {
"type": "VarString",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "maybe_file_cover_image_public_bucket_extension",
"type_info": {
"type": "VarString",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "creator_set_visibility: enums::common::visibility::Visibility",
"type_info": {
"type": "String",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -174,7 +184,7 @@
}
},
{
"ordinal": 17,
"ordinal": 18,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -184,7 +194,7 @@
}
},
{
"ordinal": 18,
"ordinal": 19,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -213,11 +223,12 @@
true,
true,
true,
true,
false,
true,
false,
false
]
},
"hash": "11cc7b91730784e9e8c8f073aeaf51a1f1660584e379b9a5be3aea370d84f692"
"hash": "770f5c0672653e82c40deade6ba7b1e769558d8ebcf40d736f798f30c7a75c68"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.maybe_origin_filename,\n m.maybe_duration_millis as `maybe_duration_millis: u64`,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_source_job_token = ?\n AND m.maybe_creator_user_token = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\nORDER BY m.id ASC\nLIMIT ?\n ",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.maybe_origin_filename,\n m.maybe_duration_millis as `maybe_duration_millis: u64`,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_source_job_token = ?\n AND m.maybe_creator_user_token = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\nORDER BY m.id ASC\nLIMIT ?\n ",
"describe": {
"columns": [
{
@@ -85,6 +85,16 @@
},
{
"ordinal": 8,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 9,
"name": "maybe_file_cover_image_public_bucket_hash",
"type_info": {
"type": "VarString",
@@ -94,7 +104,7 @@
}
},
{
"ordinal": 9,
"ordinal": 10,
"name": "maybe_file_cover_image_public_bucket_prefix",
"type_info": {
"type": "VarString",
@@ -104,7 +114,7 @@
}
},
{
"ordinal": 10,
"ordinal": 11,
"name": "maybe_file_cover_image_public_bucket_extension",
"type_info": {
"type": "VarString",
@@ -114,7 +124,7 @@
}
},
{
"ordinal": 11,
"ordinal": 12,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
@@ -124,7 +134,7 @@
}
},
{
"ordinal": 12,
"ordinal": 13,
"name": "maybe_duration_millis: u64",
"type_info": {
"type": "Long",
@@ -134,7 +144,7 @@
}
},
{
"ordinal": 13,
"ordinal": 14,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -171,9 +181,10 @@
true,
true,
true,
true,
false,
false
]
},
"hash": "cd04f4f7b48ae9da53b6a210111f8e3edff94dee30263e22b45d5e29dbe9245a"
"hash": "79f08e53fdacb526524810f481d22ca169b9f35e8874ed898b63cd2ba4b7453d"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_engine_category as `maybe_engine_category: enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory`,\n m.maybe_animation_type as `maybe_animation_type: enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType`,\n\n m.maybe_mime_type,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n m.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n\n m.maybe_style_transfer_source_media_file_token as `maybe_style_transfer_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n m.maybe_scene_source_media_file_token as `maybe_scene_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n\n\n m.maybe_title,\n m.maybe_text_transcript,\n\n m.maybe_origin_filename,\n\n m.maybe_duration_millis,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n prompts.maybe_other_args as maybe_other_prompt_args,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.is_user_upload,\n m.is_intermediate_system_file,\n\n model_weights.token as `maybe_model_weights_token: tokens::tokens::model_weights::ModelWeightToken`,\n model_weights.title as maybe_model_weights_title,\n model_weights.weights_type as `maybe_model_weights_type: enums::by_table::model_weights::weights_types::WeightsType`,\n model_weights.weights_category as `maybe_model_weights_category: enums::by_table::model_weights::weights_category::WeightsCategory`,\n\n model_weight_cover_image.public_bucket_directory_hash as maybe_model_cover_image_public_bucket_hash,\n model_weight_cover_image.maybe_public_bucket_prefix as maybe_model_cover_image_public_bucket_prefix,\n model_weight_cover_image.maybe_public_bucket_extension as maybe_model_cover_image_public_bucket_extension,\n\n model_weight_creator.token as `maybe_model_weight_creator_user_token: tokens::tokens::users::UserToken`,\n model_weight_creator.username as maybe_model_weight_creator_username,\n model_weight_creator.display_name as maybe_model_weight_creator_display_name,\n model_weight_creator.email_gravatar_hash as maybe_model_weight_creator_gravatar_hash,\n\n m.extra_file_modification_info,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n entity_stats.ratings_positive_count as maybe_ratings_positive_count,\n entity_stats.ratings_negative_count as maybe_ratings_negative_count,\n entity_stats.bookmark_count as maybe_bookmark_count,\n\n featured_items.entity_token IS NOT NULL AS is_featured,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN model_weights\n ON m.maybe_origin_model_token = model_weights.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nLEFT OUTER JOIN media_files as model_weight_cover_image\n ON model_weight_cover_image.token = model_weights.maybe_cover_image_media_file_token\nLEFT OUTER JOIN users as model_weight_creator\n ON model_weight_creator.token = model_weights.creator_user_token\nLEFT OUTER JOIN entity_stats\n ON entity_stats.entity_type = \"media_file\"\n AND entity_stats.entity_token = m.token\nLEFT OUTER JOIN prompts\n ON prompts.token = m.maybe_prompt_token\nLEFT OUTER JOIN featured_items\n ON featured_items.entity_type = 'media_file'\n AND featured_items.entity_token = m.token\n AND featured_items.deleted_at IS NULL\nWHERE\n m.token = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n ",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_engine_category as `maybe_engine_category: enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory`,\n m.maybe_animation_type as `maybe_animation_type: enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType`,\n\n m.maybe_mime_type,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n m.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n\n m.maybe_style_transfer_source_media_file_token as `maybe_style_transfer_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n m.maybe_scene_source_media_file_token as `maybe_scene_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n\n\n m.maybe_title,\n m.maybe_text_transcript,\n\n m.maybe_origin_filename,\n\n m.maybe_duration_millis,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n prompts.maybe_other_args as maybe_other_prompt_args,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.is_user_upload,\n m.is_intermediate_system_file,\n\n model_weights.token as `maybe_model_weights_token: tokens::tokens::model_weights::ModelWeightToken`,\n model_weights.title as maybe_model_weights_title,\n model_weights.weights_type as `maybe_model_weights_type: enums::by_table::model_weights::weights_types::WeightsType`,\n model_weights.weights_category as `maybe_model_weights_category: enums::by_table::model_weights::weights_category::WeightsCategory`,\n\n model_weight_cover_image.public_bucket_directory_hash as maybe_model_cover_image_public_bucket_hash,\n model_weight_cover_image.maybe_public_bucket_prefix as maybe_model_cover_image_public_bucket_prefix,\n model_weight_cover_image.maybe_public_bucket_extension as maybe_model_cover_image_public_bucket_extension,\n\n model_weight_creator.token as `maybe_model_weight_creator_user_token: tokens::tokens::users::UserToken`,\n model_weight_creator.username as maybe_model_weight_creator_username,\n model_weight_creator.display_name as maybe_model_weight_creator_display_name,\n model_weight_creator.email_gravatar_hash as maybe_model_weight_creator_gravatar_hash,\n\n m.extra_file_modification_info,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n entity_stats.ratings_positive_count as maybe_ratings_positive_count,\n entity_stats.ratings_negative_count as maybe_ratings_negative_count,\n entity_stats.bookmark_count as maybe_bookmark_count,\n\n featured_items.entity_token IS NOT NULL AS is_featured,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN model_weights\n ON m.maybe_origin_model_token = model_weights.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nLEFT OUTER JOIN media_files as model_weight_cover_image\n ON model_weight_cover_image.token = model_weights.maybe_cover_image_media_file_token\nLEFT OUTER JOIN users as model_weight_creator\n ON model_weight_creator.token = model_weights.creator_user_token\nLEFT OUTER JOIN entity_stats\n ON entity_stats.entity_type = \"media_file\"\n AND entity_stats.entity_token = m.token\nLEFT OUTER JOIN prompts\n ON prompts.token = m.maybe_prompt_token\nLEFT OUTER JOIN featured_items\n ON featured_items.entity_type = 'media_file'\n AND featured_items.entity_token = m.token\n AND featured_items.deleted_at IS NULL\nWHERE\n m.token = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n ",
"describe": {
"columns": [
{
@@ -415,6 +415,16 @@
},
{
"ordinal": 41,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 42,
"name": "maybe_ratings_positive_count",
"type_info": {
"type": "Long",
@@ -424,7 +434,7 @@
}
},
{
"ordinal": 42,
"ordinal": 43,
"name": "maybe_ratings_negative_count",
"type_info": {
"type": "Long",
@@ -434,7 +444,7 @@
}
},
{
"ordinal": 43,
"ordinal": 44,
"name": "maybe_bookmark_count",
"type_info": {
"type": "Long",
@@ -444,7 +454,7 @@
}
},
{
"ordinal": 44,
"ordinal": 45,
"name": "is_featured",
"type_info": {
"type": "LongLong",
@@ -454,7 +464,7 @@
}
},
{
"ordinal": 45,
"ordinal": 46,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -464,7 +474,7 @@
}
},
{
"ordinal": 46,
"ordinal": 47,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -522,10 +532,11 @@
true,
true,
true,
true,
false,
false,
false
]
},
"hash": "ad217c26087b2ebed6f2c9f79b16ead63727107dfce3366f3c1d12bc9d565c2c"
"hash": "7a499a8fabb4633ef1c26a2df29851a706d9b6d5e99d2dc57a42f3939c7c5ecd"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.media_class = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND NOT EXISTS (\n SELECT 1\n FROM folder_media_files fmf\n JOIN folders f\n ON f.token = fmf.folder_token\n AND f.maybe_deleted_at IS NULL\n WHERE fmf.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n mf.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.media_class = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND NOT EXISTS (\n SELECT 1\n FROM folder_media_files fmf\n JOIN folders f\n ON f.token = fmf.folder_token\n AND f.maybe_deleted_at IS NULL\n WHERE fmf.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
@@ -85,6 +85,16 @@
},
{
"ordinal": 8,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 9,
"name": "maybe_cover_public_bucket_directory_hash?",
"type_info": {
"type": "VarString",
@@ -94,7 +104,7 @@
}
},
{
"ordinal": 9,
"ordinal": 10,
"name": "maybe_cover_public_bucket_prefix?",
"type_info": {
"type": "VarString",
@@ -104,7 +114,7 @@
}
},
{
"ordinal": 10,
"ordinal": 11,
"name": "maybe_cover_public_bucket_extension?",
"type_info": {
"type": "VarString",
@@ -114,7 +124,7 @@
}
},
{
"ordinal": 11,
"ordinal": 12,
"name": "creator_set_visibility: Visibility",
"type_info": {
"type": "String",
@@ -124,7 +134,7 @@
}
},
{
"ordinal": 12,
"ordinal": 13,
"name": "is_user_upload: bool",
"type_info": {
"type": "Tiny",
@@ -134,7 +144,7 @@
}
},
{
"ordinal": 13,
"ordinal": 14,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "maybe_prompt_token: PromptToken",
"type_info": {
"type": "VarString",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_duration_millis",
"type_info": {
"type": "Long",
@@ -174,7 +184,7 @@
}
},
{
"ordinal": 17,
"ordinal": 18,
"name": "maybe_frame_width",
"type_info": {
"type": "Long",
@@ -184,7 +194,7 @@
}
},
{
"ordinal": 18,
"ordinal": 19,
"name": "maybe_frame_height",
"type_info": {
"type": "Long",
@@ -194,7 +204,7 @@
}
},
{
"ordinal": 19,
"ordinal": 20,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -204,7 +214,7 @@
}
},
{
"ordinal": 20,
"ordinal": 21,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -229,6 +239,7 @@
true,
true,
true,
true,
false,
false,
true,
@@ -241,5 +252,5 @@
false
]
},
"hash": "3681e931cc87714d71c95da44d6dd92568ce58aea252164ab3d5f8e96491169c"
"hash": "96d03f60d11b2303a8fe8ac16e17cd1ee58fec404049ee3f906ed7b5fcc9c3ed"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n fmf.created_at as `added_to_folder_at: DateTime<Utc>`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n mf.is_intermediate_system_file as `is_intermediate_system_file: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM folder_media_files fmf\nJOIN media_files mf\n ON mf.token = fmf.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE fmf.folder_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\nORDER BY mf.id DESC\nLIMIT ?\n ",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n fmf.created_at as `added_to_folder_at: DateTime<Utc>`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n mf.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n mf.is_intermediate_system_file as `is_intermediate_system_file: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM folder_media_files fmf\nJOIN media_files mf\n ON mf.token = fmf.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE fmf.folder_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\nORDER BY mf.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
@@ -95,6 +95,16 @@
},
{
"ordinal": 9,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 10,
"name": "maybe_cover_public_bucket_directory_hash?",
"type_info": {
"type": "VarString",
@@ -104,7 +114,7 @@
}
},
{
"ordinal": 10,
"ordinal": 11,
"name": "maybe_cover_public_bucket_prefix?",
"type_info": {
"type": "VarString",
@@ -114,7 +124,7 @@
}
},
{
"ordinal": 11,
"ordinal": 12,
"name": "maybe_cover_public_bucket_extension?",
"type_info": {
"type": "VarString",
@@ -124,7 +134,7 @@
}
},
{
"ordinal": 12,
"ordinal": 13,
"name": "creator_set_visibility: Visibility",
"type_info": {
"type": "String",
@@ -134,7 +144,7 @@
}
},
{
"ordinal": 13,
"ordinal": 14,
"name": "is_user_upload: bool",
"type_info": {
"type": "Tiny",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "is_intermediate_system_file: bool",
"type_info": {
"type": "Tiny",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_prompt_token: PromptToken",
"type_info": {
"type": "VarString",
@@ -174,7 +184,7 @@
}
},
{
"ordinal": 17,
"ordinal": 18,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
@@ -184,7 +194,7 @@
}
},
{
"ordinal": 18,
"ordinal": 19,
"name": "maybe_duration_millis",
"type_info": {
"type": "Long",
@@ -194,7 +204,7 @@
}
},
{
"ordinal": 19,
"ordinal": 20,
"name": "maybe_frame_width",
"type_info": {
"type": "Long",
@@ -204,7 +214,7 @@
}
},
{
"ordinal": 20,
"ordinal": 21,
"name": "maybe_frame_height",
"type_info": {
"type": "Long",
@@ -214,7 +224,7 @@
}
},
{
"ordinal": 21,
"ordinal": 22,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -224,7 +234,7 @@
}
},
{
"ordinal": 22,
"ordinal": 23,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -250,6 +260,7 @@
true,
true,
true,
true,
false,
false,
false,
@@ -263,5 +274,5 @@
false
]
},
"hash": "8aca9bc976b980ea1a3b3052bc8a45dd4d8db55c20fd1aede2a4415cc1395cf3"
"hash": "9bf5ccbfaaa7c1c4e9c4af4b3044b307976825383c9a737a22a877c89cd6db09"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.maybe_project_type as `project_type!: enums::by_table::media_files::media_file_project_type::MediaFileProjectType`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = 'project'\n AND m.maybe_project_type IS NOT NULL\n AND m.maybe_project_type = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id < ?\nORDER BY m.id DESC\nLIMIT ?\n ",
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.maybe_project_type as `project_type!: enums::by_table::media_files::media_file_project_type::MediaFileProjectType`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = 'project'\n AND m.maybe_project_type IS NOT NULL\n AND m.maybe_project_type = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id > ?\nORDER BY m.id ASC\nLIMIT ?\n ",
"describe": {
"columns": [
{
@@ -85,6 +85,16 @@
},
{
"ordinal": 8,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 9,
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
@@ -94,7 +104,7 @@
}
},
{
"ordinal": 9,
"ordinal": 10,
"name": "maybe_creator_username",
"type_info": {
"type": "VarString",
@@ -104,7 +114,7 @@
}
},
{
"ordinal": 10,
"ordinal": 11,
"name": "maybe_creator_display_name",
"type_info": {
"type": "VarString",
@@ -114,7 +124,7 @@
}
},
{
"ordinal": 11,
"ordinal": 12,
"name": "maybe_creator_gravatar_hash",
"type_info": {
"type": "String",
@@ -124,7 +134,7 @@
}
},
{
"ordinal": 12,
"ordinal": 13,
"name": "maybe_file_cover_image_public_bucket_hash",
"type_info": {
"type": "VarString",
@@ -134,7 +144,7 @@
}
},
{
"ordinal": 13,
"ordinal": 14,
"name": "maybe_file_cover_image_public_bucket_prefix",
"type_info": {
"type": "VarString",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "maybe_file_cover_image_public_bucket_extension",
"type_info": {
"type": "VarString",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "creator_set_visibility: enums::common::visibility::Visibility",
"type_info": {
"type": "String",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -174,7 +184,7 @@
}
},
{
"ordinal": 17,
"ordinal": 18,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -184,7 +194,7 @@
}
},
{
"ordinal": 18,
"ordinal": 19,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -213,11 +223,12 @@
true,
true,
true,
true,
false,
true,
false,
false
]
},
"hash": "6c7adbc8171bc3fdfb6109193ffb0a55413fa0942746d65037c2b1d39b361bd0"
"hash": "9c8c0ea8510cd66175014e4cf4ffaf403c27256ae1e39f06f1bc430dbcea5e62"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND NOT EXISTS (\n SELECT 1 FROM media_file_tags mft\n WHERE mft.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n mf.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND NOT EXISTS (\n SELECT 1 FROM media_file_tags mft\n WHERE mft.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
@@ -85,6 +85,16 @@
},
{
"ordinal": 8,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 9,
"name": "maybe_cover_public_bucket_directory_hash?",
"type_info": {
"type": "VarString",
@@ -94,7 +104,7 @@
}
},
{
"ordinal": 9,
"ordinal": 10,
"name": "maybe_cover_public_bucket_prefix?",
"type_info": {
"type": "VarString",
@@ -104,7 +114,7 @@
}
},
{
"ordinal": 10,
"ordinal": 11,
"name": "maybe_cover_public_bucket_extension?",
"type_info": {
"type": "VarString",
@@ -114,7 +124,7 @@
}
},
{
"ordinal": 11,
"ordinal": 12,
"name": "creator_set_visibility: Visibility",
"type_info": {
"type": "String",
@@ -124,7 +134,7 @@
}
},
{
"ordinal": 12,
"ordinal": 13,
"name": "is_user_upload: bool",
"type_info": {
"type": "Tiny",
@@ -134,7 +144,7 @@
}
},
{
"ordinal": 13,
"ordinal": 14,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "maybe_prompt_token: PromptToken",
"type_info": {
"type": "VarString",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_duration_millis",
"type_info": {
"type": "Long",
@@ -174,7 +184,7 @@
}
},
{
"ordinal": 17,
"ordinal": 18,
"name": "maybe_frame_width",
"type_info": {
"type": "Long",
@@ -184,7 +194,7 @@
}
},
{
"ordinal": 18,
"ordinal": 19,
"name": "maybe_frame_height",
"type_info": {
"type": "Long",
@@ -194,7 +204,7 @@
}
},
{
"ordinal": 19,
"ordinal": 20,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -204,7 +214,7 @@
}
},
{
"ordinal": 20,
"ordinal": 21,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -229,6 +239,7 @@
true,
true,
true,
true,
false,
false,
true,
@@ -241,5 +252,5 @@
false
]
},
"hash": "aa1e693babe6997e75935ec893e6eed46351ec72ce02a9144fd11d2fcfd37fdf"
"hash": "9f116e00964272b8389a766674177e7882a16ad6459785551bc2093e27404f80"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id < ?\nORDER BY m.id DESC\nLIMIT ?\n ",
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id < ?\nORDER BY m.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
@@ -75,6 +75,16 @@
},
{
"ordinal": 7,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 8,
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
@@ -84,7 +94,7 @@
}
},
{
"ordinal": 8,
"ordinal": 9,
"name": "maybe_creator_username",
"type_info": {
"type": "VarString",
@@ -94,7 +104,7 @@
}
},
{
"ordinal": 9,
"ordinal": 10,
"name": "maybe_creator_display_name",
"type_info": {
"type": "VarString",
@@ -104,7 +114,7 @@
}
},
{
"ordinal": 10,
"ordinal": 11,
"name": "maybe_creator_gravatar_hash",
"type_info": {
"type": "String",
@@ -114,7 +124,7 @@
}
},
{
"ordinal": 11,
"ordinal": 12,
"name": "maybe_file_cover_image_public_bucket_hash",
"type_info": {
"type": "VarString",
@@ -124,7 +134,7 @@
}
},
{
"ordinal": 12,
"ordinal": 13,
"name": "maybe_file_cover_image_public_bucket_prefix",
"type_info": {
"type": "VarString",
@@ -134,7 +144,7 @@
}
},
{
"ordinal": 13,
"ordinal": 14,
"name": "maybe_file_cover_image_public_bucket_extension",
"type_info": {
"type": "VarString",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "creator_set_visibility: enums::common::visibility::Visibility",
"type_info": {
"type": "String",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
"type_info": {
"type": "VarString",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -174,7 +184,7 @@
}
},
{
"ordinal": 17,
"ordinal": 18,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -184,7 +194,7 @@
}
},
{
"ordinal": 18,
"ordinal": 19,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -212,6 +222,7 @@
true,
true,
true,
true,
false,
true,
true,
@@ -219,5 +230,5 @@
false
]
},
"hash": "73540435892b7b4dbc11c6c63712abdb18fd8b3db2bcd56b8518c968b8169de7"
"hash": "b0d2f025aa7d137c0a9f50d0af4bf863e1d0741d9a34111bdfaaf18f10ec11f4"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.maybe_project_type as `project_type!: enums::by_table::media_files::media_file_project_type::MediaFileProjectType`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = 'project'\n AND m.maybe_project_type IS NOT NULL\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id < ?\nORDER BY m.id DESC\nLIMIT ?\n ",
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.maybe_project_type as `project_type!: enums::by_table::media_files::media_file_project_type::MediaFileProjectType`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = 'project'\n AND m.maybe_project_type IS NOT NULL\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id < ?\nORDER BY m.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
@@ -85,6 +85,16 @@
},
{
"ordinal": 8,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 9,
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
@@ -94,7 +104,7 @@
}
},
{
"ordinal": 9,
"ordinal": 10,
"name": "maybe_creator_username",
"type_info": {
"type": "VarString",
@@ -104,7 +114,7 @@
}
},
{
"ordinal": 10,
"ordinal": 11,
"name": "maybe_creator_display_name",
"type_info": {
"type": "VarString",
@@ -114,7 +124,7 @@
}
},
{
"ordinal": 11,
"ordinal": 12,
"name": "maybe_creator_gravatar_hash",
"type_info": {
"type": "String",
@@ -124,7 +134,7 @@
}
},
{
"ordinal": 12,
"ordinal": 13,
"name": "maybe_file_cover_image_public_bucket_hash",
"type_info": {
"type": "VarString",
@@ -134,7 +144,7 @@
}
},
{
"ordinal": 13,
"ordinal": 14,
"name": "maybe_file_cover_image_public_bucket_prefix",
"type_info": {
"type": "VarString",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "maybe_file_cover_image_public_bucket_extension",
"type_info": {
"type": "VarString",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "creator_set_visibility: enums::common::visibility::Visibility",
"type_info": {
"type": "String",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -174,7 +184,7 @@
}
},
{
"ordinal": 17,
"ordinal": 18,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -184,7 +194,7 @@
}
},
{
"ordinal": 18,
"ordinal": 19,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -213,11 +223,12 @@
true,
true,
true,
true,
false,
true,
false,
false
]
},
"hash": "97e4bc3ab037126e3f4e213cdb7535c2ad233809fbeb6607c34fb02ec45cfe47"
"hash": "d96bca438343b0a5a3f202ae48b51670ed3e660cdfaa0100b47893249d5d0c11"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND EXISTS (\n SELECT 1 FROM media_file_tags mft\n WHERE mft.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n mf.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND EXISTS (\n SELECT 1 FROM media_file_tags mft\n WHERE mft.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
@@ -85,6 +85,16 @@
},
{
"ordinal": 8,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 9,
"name": "maybe_cover_public_bucket_directory_hash?",
"type_info": {
"type": "VarString",
@@ -94,7 +104,7 @@
}
},
{
"ordinal": 9,
"ordinal": 10,
"name": "maybe_cover_public_bucket_prefix?",
"type_info": {
"type": "VarString",
@@ -104,7 +114,7 @@
}
},
{
"ordinal": 10,
"ordinal": 11,
"name": "maybe_cover_public_bucket_extension?",
"type_info": {
"type": "VarString",
@@ -114,7 +124,7 @@
}
},
{
"ordinal": 11,
"ordinal": 12,
"name": "creator_set_visibility: Visibility",
"type_info": {
"type": "String",
@@ -124,7 +134,7 @@
}
},
{
"ordinal": 12,
"ordinal": 13,
"name": "is_user_upload: bool",
"type_info": {
"type": "Tiny",
@@ -134,7 +144,7 @@
}
},
{
"ordinal": 13,
"ordinal": 14,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "maybe_prompt_token: PromptToken",
"type_info": {
"type": "VarString",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_duration_millis",
"type_info": {
"type": "Long",
@@ -174,7 +184,7 @@
}
},
{
"ordinal": 17,
"ordinal": 18,
"name": "maybe_frame_width",
"type_info": {
"type": "Long",
@@ -184,7 +194,7 @@
}
},
{
"ordinal": 18,
"ordinal": 19,
"name": "maybe_frame_height",
"type_info": {
"type": "Long",
@@ -194,7 +204,7 @@
}
},
{
"ordinal": 19,
"ordinal": 20,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -204,7 +214,7 @@
}
},
{
"ordinal": 20,
"ordinal": 21,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -229,6 +239,7 @@
true,
true,
true,
true,
false,
false,
true,
@@ -241,5 +252,5 @@
false
]
},
"hash": "76bcf698f40d852558f071a6909b05b1c960c1cf236831ae009f90de46d3aefb"
"hash": "dabd15fbe5fd3cc9f0ce921d9c804ea25956fc36bb7e677d29b869b31f2c6c1d"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_engine_category as `maybe_engine_category: enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory`,\n m.maybe_animation_type as `maybe_animation_type: enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType`,\n\n m.maybe_mime_type,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n m.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n\n m.maybe_style_transfer_source_media_file_token as `maybe_style_transfer_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n m.maybe_scene_source_media_file_token as `maybe_scene_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n\n m.maybe_title,\n m.maybe_text_transcript,\n\n m.maybe_origin_filename,\n\n m.maybe_duration_millis,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n prompts.maybe_other_args as maybe_other_prompt_args,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.is_user_upload,\n m.is_intermediate_system_file,\n\n model_weights.token as `maybe_model_weights_token: tokens::tokens::model_weights::ModelWeightToken`,\n model_weights.title as maybe_model_weights_title,\n model_weights.weights_type as `maybe_model_weights_type: enums::by_table::model_weights::weights_types::WeightsType`,\n model_weights.weights_category as `maybe_model_weights_category: enums::by_table::model_weights::weights_category::WeightsCategory`,\n\n model_weight_cover_image.public_bucket_directory_hash as maybe_model_cover_image_public_bucket_hash,\n model_weight_cover_image.maybe_public_bucket_prefix as maybe_model_cover_image_public_bucket_prefix,\n model_weight_cover_image.maybe_public_bucket_extension as maybe_model_cover_image_public_bucket_extension,\n\n model_weight_creator.token as `maybe_model_weight_creator_user_token: tokens::tokens::users::UserToken`,\n model_weight_creator.username as maybe_model_weight_creator_username,\n model_weight_creator.display_name as maybe_model_weight_creator_display_name,\n model_weight_creator.email_gravatar_hash as maybe_model_weight_creator_gravatar_hash,\n\n m.extra_file_modification_info,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n entity_stats.ratings_positive_count as maybe_ratings_positive_count,\n entity_stats.ratings_negative_count as maybe_ratings_negative_count,\n entity_stats.bookmark_count as maybe_bookmark_count,\n\n featured_items.entity_token IS NOT NULL AS is_featured,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN model_weights\n ON m.maybe_origin_model_token = model_weights.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nLEFT OUTER JOIN media_files as model_weight_cover_image\n ON model_weight_cover_image.token = model_weights.maybe_cover_image_media_file_token\nLEFT OUTER JOIN users as model_weight_creator\n ON model_weight_creator.token = model_weights.creator_user_token\nLEFT OUTER JOIN entity_stats\n ON entity_stats.entity_type = \"media_file\"\n AND entity_stats.entity_token = m.token\nLEFT OUTER JOIN prompts\n ON prompts.token = m.maybe_prompt_token\nLEFT OUTER JOIN featured_items\n ON featured_items.entity_type = 'media_file'\n AND featured_items.entity_token = m.token\n AND featured_items.deleted_at IS NULL\nWHERE\n m.token = ?\n ",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_engine_category as `maybe_engine_category: enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory`,\n m.maybe_animation_type as `maybe_animation_type: enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType`,\n\n m.maybe_mime_type,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n m.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n\n m.maybe_style_transfer_source_media_file_token as `maybe_style_transfer_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n m.maybe_scene_source_media_file_token as `maybe_scene_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n\n m.maybe_title,\n m.maybe_text_transcript,\n\n m.maybe_origin_filename,\n\n m.maybe_duration_millis,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n prompts.maybe_other_args as maybe_other_prompt_args,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.is_user_upload,\n m.is_intermediate_system_file,\n\n model_weights.token as `maybe_model_weights_token: tokens::tokens::model_weights::ModelWeightToken`,\n model_weights.title as maybe_model_weights_title,\n model_weights.weights_type as `maybe_model_weights_type: enums::by_table::model_weights::weights_types::WeightsType`,\n model_weights.weights_category as `maybe_model_weights_category: enums::by_table::model_weights::weights_category::WeightsCategory`,\n\n model_weight_cover_image.public_bucket_directory_hash as maybe_model_cover_image_public_bucket_hash,\n model_weight_cover_image.maybe_public_bucket_prefix as maybe_model_cover_image_public_bucket_prefix,\n model_weight_cover_image.maybe_public_bucket_extension as maybe_model_cover_image_public_bucket_extension,\n\n model_weight_creator.token as `maybe_model_weight_creator_user_token: tokens::tokens::users::UserToken`,\n model_weight_creator.username as maybe_model_weight_creator_username,\n model_weight_creator.display_name as maybe_model_weight_creator_display_name,\n model_weight_creator.email_gravatar_hash as maybe_model_weight_creator_gravatar_hash,\n\n m.extra_file_modification_info,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n entity_stats.ratings_positive_count as maybe_ratings_positive_count,\n entity_stats.ratings_negative_count as maybe_ratings_negative_count,\n entity_stats.bookmark_count as maybe_bookmark_count,\n\n featured_items.entity_token IS NOT NULL AS is_featured,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN model_weights\n ON m.maybe_origin_model_token = model_weights.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nLEFT OUTER JOIN media_files as model_weight_cover_image\n ON model_weight_cover_image.token = model_weights.maybe_cover_image_media_file_token\nLEFT OUTER JOIN users as model_weight_creator\n ON model_weight_creator.token = model_weights.creator_user_token\nLEFT OUTER JOIN entity_stats\n ON entity_stats.entity_type = \"media_file\"\n AND entity_stats.entity_token = m.token\nLEFT OUTER JOIN prompts\n ON prompts.token = m.maybe_prompt_token\nLEFT OUTER JOIN featured_items\n ON featured_items.entity_type = 'media_file'\n AND featured_items.entity_token = m.token\n AND featured_items.deleted_at IS NULL\nWHERE\n m.token = ?\n ",
"describe": {
"columns": [
{
@@ -415,6 +415,16 @@
},
{
"ordinal": 41,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 42,
"name": "maybe_ratings_positive_count",
"type_info": {
"type": "Long",
@@ -424,7 +434,7 @@
}
},
{
"ordinal": 42,
"ordinal": 43,
"name": "maybe_ratings_negative_count",
"type_info": {
"type": "Long",
@@ -434,7 +444,7 @@
}
},
{
"ordinal": 43,
"ordinal": 44,
"name": "maybe_bookmark_count",
"type_info": {
"type": "Long",
@@ -444,7 +454,7 @@
}
},
{
"ordinal": 44,
"ordinal": 45,
"name": "is_featured",
"type_info": {
"type": "LongLong",
@@ -454,7 +464,7 @@
}
},
{
"ordinal": 45,
"ordinal": 46,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -464,7 +474,7 @@
}
},
{
"ordinal": 46,
"ordinal": 47,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -522,10 +532,11 @@
true,
true,
true,
true,
false,
false,
false
]
},
"hash": "784ca68c6cb61d481b605bfe6ee3248a5b44499038c71b57fc9ea6c4722d170f"
"hash": "e6c3c16d96339ad84b65302f3610b4b798e7ea16c2ae9d03d6b706cb42989990"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_mime_type,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n m.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n\n m.maybe_title,\n m.maybe_text_transcript,\n\n m.maybe_origin_filename,\n\n m.maybe_duration_millis,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n prompts.maybe_other_args as maybe_other_prompt_args,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.is_user_upload,\n m.is_intermediate_system_file,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nLEFT OUTER JOIN prompts\n ON prompts.token = m.maybe_prompt_token\nWHERE\n m.maybe_batch_token = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n ",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_mime_type,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n m.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n\n m.maybe_title,\n m.maybe_text_transcript,\n\n m.maybe_origin_filename,\n\n m.maybe_duration_millis,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n prompts.maybe_other_args as maybe_other_prompt_args,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.is_user_upload,\n m.is_intermediate_system_file,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nLEFT OUTER JOIN prompts\n ON prompts.token = m.maybe_prompt_token\nWHERE\n m.maybe_batch_token = ?\n ",
"describe": {
"columns": [
{
@@ -255,6 +255,16 @@
},
{
"ordinal": 25,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 26,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -264,7 +274,7 @@
}
},
{
"ordinal": 26,
"ordinal": 27,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -303,9 +313,10 @@
false,
true,
true,
true,
false,
false
]
},
"hash": "f75fedb86a93ac2542315ddb7ef0d46aa42ecc25a3cf9856219114064183337a"
"hash": "ede1fc9893cb4309a89acc15a567b660faba1bb7eb0bf012605a9dfa15a53229"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND NOT EXISTS (\n SELECT 1\n FROM folder_media_files fmf\n JOIN folders f\n ON f.token = fmf.folder_token\n AND f.maybe_deleted_at IS NULL\n WHERE fmf.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n mf.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND NOT EXISTS (\n SELECT 1\n FROM folder_media_files fmf\n JOIN folders f\n ON f.token = fmf.folder_token\n AND f.maybe_deleted_at IS NULL\n WHERE fmf.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
@@ -85,6 +85,16 @@
},
{
"ordinal": 8,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 9,
"name": "maybe_cover_public_bucket_directory_hash?",
"type_info": {
"type": "VarString",
@@ -94,7 +104,7 @@
}
},
{
"ordinal": 9,
"ordinal": 10,
"name": "maybe_cover_public_bucket_prefix?",
"type_info": {
"type": "VarString",
@@ -104,7 +114,7 @@
}
},
{
"ordinal": 10,
"ordinal": 11,
"name": "maybe_cover_public_bucket_extension?",
"type_info": {
"type": "VarString",
@@ -114,7 +124,7 @@
}
},
{
"ordinal": 11,
"ordinal": 12,
"name": "creator_set_visibility: Visibility",
"type_info": {
"type": "String",
@@ -124,7 +134,7 @@
}
},
{
"ordinal": 12,
"ordinal": 13,
"name": "is_user_upload: bool",
"type_info": {
"type": "Tiny",
@@ -134,7 +144,7 @@
}
},
{
"ordinal": 13,
"ordinal": 14,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "maybe_prompt_token: PromptToken",
"type_info": {
"type": "VarString",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_duration_millis",
"type_info": {
"type": "Long",
@@ -174,7 +184,7 @@
}
},
{
"ordinal": 17,
"ordinal": 18,
"name": "maybe_frame_width",
"type_info": {
"type": "Long",
@@ -184,7 +194,7 @@
}
},
{
"ordinal": 18,
"ordinal": 19,
"name": "maybe_frame_height",
"type_info": {
"type": "Long",
@@ -194,7 +204,7 @@
}
},
{
"ordinal": 19,
"ordinal": 20,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -204,7 +214,7 @@
}
},
{
"ordinal": 20,
"ordinal": 21,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -229,6 +239,7 @@
true,
true,
true,
true,
false,
false,
true,
@@ -241,5 +252,5 @@
false
]
},
"hash": "6f077c5d35b19319aba4e9f4c59610822334fd304ba9133ba8e099056c426f2a"
"hash": "f392e28b5f4287625ccb1ee0668089f9049940368da50b7f0e0b8370f112305c"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_file_tags mft\nJOIN media_files mf\n ON mf.token = mft.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mft.tag_token = ?\n AND mft.user_token = ?\n AND mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n mf.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM media_file_tags mft\nJOIN media_files mf\n ON mf.token = mft.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mft.tag_token = ?\n AND mft.user_token = ?\n AND mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
"describe": {
"columns": [
{
@@ -85,6 +85,16 @@
},
{
"ordinal": 8,
"name": "maybe_thumbnail_version: u8",
"type_info": {
"type": "Tiny",
"flags": "UNSIGNED",
"char_set": 63,
"max_size": 3
}
},
{
"ordinal": 9,
"name": "maybe_cover_public_bucket_directory_hash?",
"type_info": {
"type": "VarString",
@@ -94,7 +104,7 @@
}
},
{
"ordinal": 9,
"ordinal": 10,
"name": "maybe_cover_public_bucket_prefix?",
"type_info": {
"type": "VarString",
@@ -104,7 +114,7 @@
}
},
{
"ordinal": 10,
"ordinal": 11,
"name": "maybe_cover_public_bucket_extension?",
"type_info": {
"type": "VarString",
@@ -114,7 +124,7 @@
}
},
{
"ordinal": 11,
"ordinal": 12,
"name": "creator_set_visibility: Visibility",
"type_info": {
"type": "String",
@@ -124,7 +134,7 @@
}
},
{
"ordinal": 12,
"ordinal": 13,
"name": "is_user_upload: bool",
"type_info": {
"type": "Tiny",
@@ -134,7 +144,7 @@
}
},
{
"ordinal": 13,
"ordinal": 14,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "maybe_prompt_token: PromptToken",
"type_info": {
"type": "VarString",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_duration_millis",
"type_info": {
"type": "Long",
@@ -174,7 +184,7 @@
}
},
{
"ordinal": 17,
"ordinal": 18,
"name": "maybe_frame_width",
"type_info": {
"type": "Long",
@@ -184,7 +194,7 @@
}
},
{
"ordinal": 18,
"ordinal": 19,
"name": "maybe_frame_height",
"type_info": {
"type": "Long",
@@ -194,7 +204,7 @@
}
},
{
"ordinal": 19,
"ordinal": 20,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -204,7 +214,7 @@
}
},
{
"ordinal": 20,
"ordinal": 21,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -229,6 +239,7 @@
true,
true,
true,
true,
false,
false,
true,
@@ -241,5 +252,5 @@
false
]
},
"hash": "f36c7238231353882b5b2ac39616dcdc6adebf7c7e1198d9c2af4674ba5d982c"
"hash": "fbdbb627018a7664499834149d5f571457ba4313b1666152197cdbea92c8068c"
}
@@ -15,7 +15,8 @@ pub struct MediaLinks {
/// video previews, which, in turn, have their own thumbnail templates.)
pub maybe_thumbnail_template: Option<String>,
/// Video preview images (still and animated gif) for mp4 video files.
/// Video preview images (still, animated webp or gif, and a Catalina gif
/// fallback) for mp4 video files.
/// These are only set for video media files.
/// These are not set for "cover images" that are gif or webms, but rather video files.
pub maybe_video_previews: Option<VideoPreviews>,
@@ -26,7 +27,9 @@ pub struct VideoPreviews {
/// A static single frame preview image of the video.
pub still: Url,
/// An animated gif preview of the video.
/// An animated preview of the video: an animated webp for videos with
/// thumbnail version 2 (2026-09+), otherwise a gif. Clients that can't
/// render animated webp should use `maybe_catalina_fallback_animated_gif`.
pub animated: Url,
/// A template used to construct the still thumbnail URL.
@@ -36,4 +39,21 @@ pub struct VideoPreviews {
/// A template used to construct the animated thumbnail URL.
/// Replace the string `{WIDTH}` with the desired width.
pub animated_thumbnail_template: String,
/// An animated gif preview of the video, for clients that can't render the
/// webp in `animated`: ArtCraft desktop on macOS 10.15 (Catalina) runs on a
/// WKWebView without animated webp support. Since thumbnail version 2
/// (2026-09) the thumbnail job writes this gif beside the webp; for older
/// videos it's the same file as `animated`. See `video_thumbnail_suffixes`
/// in `bucket_paths` for the version history.
///
/// Always set by the server. Optional (and defaulted) only so newer clients
/// can still read responses from older servers.
#[serde(default)]
pub maybe_catalina_fallback_animated_gif: Option<Url>,
/// A template used to construct the Catalina fallback gif thumbnail URL.
/// Replace the string `{WIDTH}` with the desired width.
#[serde(default)]
pub maybe_catalina_fallback_animated_gif_thumbnail_template: Option<String>,
}
@@ -68,7 +68,8 @@ pub struct FolderThumbnail {
/// which, in turn, have their own thumbnail templates.)
pub maybe_thumbnail_template: Option<String>,
/// Video preview images (still and animated gif) for mp4 video files.
/// Video preview images (still, animated webp or gif, and a Catalina gif
/// fallback) for mp4 video files.
/// These are only set for video media files.
pub maybe_video_previews: Option<FolderThumbnailVideoPreviews>,
}
@@ -81,7 +82,9 @@ pub struct FolderThumbnailVideoPreviews {
/// A static single frame preview image of the video.
pub still: Url,
/// An animated gif preview of the video.
/// An animated preview of the video: an animated webp for videos with
/// thumbnail version 2 (2026-09+), otherwise a gif. Clients that can't
/// render animated webp should use `maybe_catalina_fallback_animated_gif`.
pub animated: Url,
/// A template used to construct the still thumbnail URL.
@@ -91,4 +94,21 @@ pub struct FolderThumbnailVideoPreviews {
/// A template used to construct the animated thumbnail URL.
/// Replace the string `{WIDTH}` with the desired width.
pub animated_thumbnail_template: String,
/// An animated gif preview of the video, for clients that can't render the
/// webp in `animated`: ArtCraft desktop on macOS 10.15 (Catalina) runs on a
/// WKWebView without animated webp support. Since thumbnail version 2
/// (2026-09) the thumbnail job writes this gif beside the webp; for older
/// videos it's the same file as `animated`. See `video_thumbnail_suffixes`
/// in `bucket_paths` for the version history.
///
/// Always set by the server. Optional (and defaulted) only so newer clients
/// can still read responses from older servers.
#[serde(default)]
pub maybe_catalina_fallback_animated_gif: Option<Url>,
/// A template used to construct the Catalina fallback gif thumbnail URL.
/// Replace the string `{WIDTH}` with the desired width.
#[serde(default)]
pub maybe_catalina_fallback_animated_gif_thumbnail_template: Option<String>,
}
@@ -0,0 +1,145 @@
use log::info;
use std::path::Path;
use std::process::Command;
use errors::AnyhowResult;
use crate::ffmpeg::run_thumbnail_ffmpeg::run_thumbnail_ffmpeg;
use crate::ffmpeg::webp_ffmpeg_binary::webp_ffmpeg;
/// Lossy WebP quality (0-100). Started at 60 on 2026-09-28; on the test video
/// this was ~50x smaller than the GIF preview (23 KB vs 1.1 MB). Safe to tune:
/// it only affects newly generated previews.
const WEBP_QUALITY: &str = "60";
pub struct FfmpegVideoWebpPreviewArgs<I: AsRef<Path>, O: AsRef<Path>> {
pub input_video_path: I,
pub output_webp_path: O,
}
/// Extract a short, endlessly looping animated WebP preview from a video file.
///
/// Uses the same frames as the GIF preview (`ffmpeg_video_gif_preview`): the
/// first 5 seconds, resampled to 10 fps, scaled to fit within 360x360
/// (preserving aspect ratio). Runs the ffmpeg chosen by
/// `webp_ffmpeg_binary::resolve_webp_ffmpeg` (call it at startup).
pub fn ffmpeg_video_webp_preview<I: AsRef<Path>, O: AsRef<Path>>(
args: FfmpegVideoWebpPreviewArgs<I, O>,
) -> AnyhowResult<()> {
let mut command = Command::new(webp_ffmpeg()?);
command
.arg("-nostdin")
.arg("-y")
.arg("-loglevel").arg("error")
.arg("-nostats")
.arg("-threads").arg("1")
.arg("-filter_complex_threads").arg("1")
.arg("-ss").arg("0")
.arg("-to").arg("5")
.arg("-i").arg(args.input_video_path.as_ref())
.arg("-threads").arg("1")
.arg("-filter_complex")
.arg("fps=10,scale=360:360:force_original_aspect_ratio=decrease")
.arg("-frames:v").arg("50")
.arg("-an")
.arg("-c:v").arg("libwebp_anim")
.arg("-lossless").arg("0")
.arg("-quality").arg(WEBP_QUALITY)
// NB: The webp muxer plays animations once by default; 0 loops forever like the GIF.
.arg("-loop").arg("0")
.arg(args.output_webp_path.as_ref());
info!("Calling ffmpeg (webp preview)...");
run_thumbnail_ffmpeg(command)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ffmpeg::webp_ffmpeg_binary::resolve_webp_ffmpeg;
use tempdir::TempDir;
use test_utils::test_file_path::test_file_path;
#[test]
fn test_extract_webp_preview_from_mp4() {
if !libwebp_anim_available() {
return;
}
let input_path = test_file_path("test_data/video/mp4/golden_sun_garoh.mp4")
.expect("test video should exist");
let temp_dir = TempDir::new_in("/tmp", "ffmpeg_webp_preview_test")
.expect("should create temp dir");
let output_path = temp_dir.path().join("preview.webp");
ffmpeg_video_webp_preview(FfmpegVideoWebpPreviewArgs {
input_video_path: &input_path,
output_webp_path: &output_path,
}).expect("ffmpeg should succeed");
let bytes = std::fs::read(&output_path).expect("output webp should exist");
// RIFF container holding a WebP file.
assert_eq!(&bytes[0..4], b"RIFF");
assert_eq!(&bytes[8..12], b"WEBP");
// Animated WebP: an ANIM chunk with a loop count of 0 (loop forever).
let anim = find_chunk(&bytes, b"ANIM").expect("webp should be animated");
let loop_count = u16::from_le_bytes([bytes[anim + 12], bytes[anim + 13]]);
assert_eq!(loop_count, 0, "preview should loop forever");
// More than one frame, and no more than the 50 we asked for.
let frames = count_chunks(&bytes, b"ANMF");
assert!(frames > 1 && frames <= 50, "unexpected frame count: {}", frames);
// 640x480 input fits within 360x360 as 360x270. The VP8X chunk stores the
// canvas width-1 and height-1 as 24-bit little-endian values. (Frames
// after the first may be smaller sub-rectangles, so check the canvas.)
let vp8x = find_chunk(&bytes, b"VP8X").expect("animated webp should have a VP8X header");
let width = u24_le(&bytes[vp8x + 12..vp8x + 15]) + 1;
let height = u24_le(&bytes[vp8x + 15..vp8x + 18]) + 1;
assert_eq!((width, height), (360, 270));
}
/// Resolves like a development job would (including the macOS `ffmpeg-full`
/// fallback). Skips if this machine has no webp-capable ffmpeg at all.
fn libwebp_anim_available() -> bool {
match resolve_webp_ffmpeg(true) {
Ok(_) => true,
Err(err) => {
eprintln!("SKIPPED: {}", err);
false
}
}
}
/// Offset of the first RIFF chunk with this FourCC, walking top-level chunks
/// after the 12-byte RIFF/WEBP header.
fn find_chunk(bytes: &[u8], fourcc: &[u8; 4]) -> Option<usize> {
chunk_offsets(bytes).into_iter().find(|&offset| &bytes[offset..offset + 4] == fourcc)
}
fn count_chunks(bytes: &[u8], fourcc: &[u8; 4]) -> usize {
chunk_offsets(bytes).into_iter().filter(|&offset| &bytes[offset..offset + 4] == fourcc).count()
}
fn chunk_offsets(bytes: &[u8]) -> Vec<usize> {
let mut offsets = Vec::new();
let mut offset = 12;
while offset + 8 <= bytes.len() {
offsets.push(offset);
let size = u32::from_le_bytes([bytes[offset + 4], bytes[offset + 5], bytes[offset + 6], bytes[offset + 7]]) as usize;
// Chunk payloads are padded to an even length.
offset += 8 + size + (size & 1);
}
offsets
}
fn u24_le(bytes: &[u8]) -> u32 {
u32::from(bytes[0]) | (u32::from(bytes[1]) << 8) | (u32::from(bytes[2]) << 16)
}
}
@@ -3,4 +3,6 @@ pub mod ffmpeg_transcode_to_mp4;
pub mod ffmpeg_trim_and_resample;
pub mod ffmpeg_video_first_frame_to_jpg_thumbnail;
pub mod ffmpeg_video_gif_preview;
pub mod ffmpeg_video_webp_preview;
mod run_thumbnail_ffmpeg;
pub mod webp_ffmpeg_binary;
@@ -0,0 +1,156 @@
//! Which `ffmpeg` binary to use for animated webp previews.
//!
//! Animated webp needs ffmpeg's `libwebp_anim` encoder. Ubuntu's `ffmpeg`
//! package (production) has it. Homebrew's slimmed `ffmpeg` formula doesn't
//! (as of 2026-09); its full build is the keg-only `ffmpeg-full` formula,
//! which isn't on the PATH. So on macOS development machines we fall back to
//! `ffmpeg-full` when the `ffmpeg` on the PATH can't encode webp.
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::OnceLock;
use anyhow::anyhow;
use log::{info, warn};
use errors::AnyhowResult;
/// The `ffmpeg` on the PATH.
const PATH_FFMPEG: &str = "ffmpeg";
/// Homebrew's keg-only `ffmpeg-full` (Apple Silicon, then Intel prefixes).
const HOMEBREW_FFMPEG_FULL_CANDIDATES: [&str; 2] = [
"/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg",
"/usr/local/opt/ffmpeg-full/bin/ffmpeg",
];
/// The process-wide decision. Resolved once (the first caller probes the
/// binaries while concurrent callers wait), then shared by every thread.
/// Failures are cached too: a missing encoder won't appear mid-process.
static WEBP_FFMPEG: OnceLock<Result<PathBuf, String>> = OnceLock::new();
/// Resolve (once per process, then cached) the `ffmpeg` binary that can
/// encode animated webp.
///
/// Uses the `ffmpeg` on the PATH if it has `libwebp_anim`. Otherwise, only when
/// `is_development` and on macOS, falls back to Homebrew's `ffmpeg-full` and
/// logs a warning. Call this at startup with the server environment; the first
/// call decides for the whole process, and later calls return the cached
/// decision (whatever `is_development` they pass).
pub fn resolve_webp_ffmpeg(is_development: bool) -> AnyhowResult<&'static Path> {
let decision = WEBP_FFMPEG.get_or_init(|| {
let allow_macos_fallback = is_development && cfg!(target_os = "macos");
let decision = choose_webp_ffmpeg(
has_webp_encoder(Path::new(PATH_FFMPEG)),
allow_macos_fallback,
find_homebrew_ffmpeg_full,
);
match &decision {
Ok(WebpFfmpegChoice::Path) =>
info!("Using the ffmpeg on the PATH for webp previews."),
Ok(WebpFfmpegChoice::HomebrewFallback(binary)) =>
warn!("The ffmpeg on the PATH has no libwebp_anim encoder; falling back to {} for webp previews \
(macOS development only).", binary.display()),
Err(err) =>
warn!("No ffmpeg can encode webp previews: {}", err),
}
decision.map(WebpFfmpegChoice::into_binary)
});
decision.as_deref().map_err(|err| anyhow!("{}", err))
}
/// The binary resolved by [`resolve_webp_ffmpeg`], or production behavior
/// (the PATH only) if nothing resolved it yet.
pub(crate) fn webp_ffmpeg() -> AnyhowResult<&'static Path> {
resolve_webp_ffmpeg(false)
}
#[derive(Debug, PartialEq, Eq)]
enum WebpFfmpegChoice {
Path,
HomebrewFallback(PathBuf),
}
impl WebpFfmpegChoice {
fn into_binary(self) -> PathBuf {
match self {
WebpFfmpegChoice::Path => PathBuf::from(PATH_FFMPEG),
WebpFfmpegChoice::HomebrewFallback(binary) => binary,
}
}
}
/// The decision, separated from probing binaries so it can be tested.
fn choose_webp_ffmpeg(
path_ffmpeg_has_webp: bool,
allow_macos_fallback: bool,
find_fallback: impl FnOnce() -> Option<PathBuf>,
) -> Result<WebpFfmpegChoice, String> {
if path_ffmpeg_has_webp {
return Ok(WebpFfmpegChoice::Path);
}
if !allow_macos_fallback {
return Err("the ffmpeg on the PATH has no libwebp_anim encoder, which animated webp thumbnails need".to_string());
}
find_fallback()
.map(WebpFfmpegChoice::HomebrewFallback)
.ok_or_else(|| "neither the ffmpeg on the PATH nor Homebrew's ffmpeg-full has a libwebp_anim encoder. \
Run `brew install ffmpeg-full`.".to_string())
}
/// The first installed Homebrew `ffmpeg-full` that has the webp encoder.
fn find_homebrew_ffmpeg_full() -> Option<PathBuf> {
HOMEBREW_FFMPEG_FULL_CANDIDATES.iter()
.map(PathBuf::from)
.find(|binary| binary.exists() && has_webp_encoder(binary))
}
/// Whether `binary -encoders` lists `libwebp_anim`. False if it can't run.
fn has_webp_encoder(binary: &Path) -> bool {
Command::new(binary)
.args(["-hide_banner", "-encoders"])
.output()
.map(|output| String::from_utf8_lossy(&output.stdout)
.split_whitespace()
.any(|word| word == "libwebp_anim"))
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
const FALLBACK: &str = "/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg";
#[test]
fn uses_path_ffmpeg_when_it_has_webp() {
let choice = choose_webp_ffmpeg(true, true, || panic!("shouldn't look for a fallback"));
assert_eq!(choice, Ok(WebpFfmpegChoice::Path));
}
#[test]
fn falls_back_to_ffmpeg_full_when_allowed() {
let choice = choose_webp_ffmpeg(false, true, || Some(PathBuf::from(FALLBACK)));
assert_eq!(choice, Ok(WebpFfmpegChoice::HomebrewFallback(PathBuf::from(FALLBACK))));
}
#[test]
fn never_falls_back_outside_macos_development() {
let choice = choose_webp_ffmpeg(false, false, || panic!("shouldn't look for a fallback"));
assert!(choice.is_err());
}
#[test]
fn errors_when_the_fallback_is_missing() {
let choice = choose_webp_ffmpeg(false, true, || None);
let err = choice.unwrap_err();
assert!(err.contains("brew install ffmpeg-full"), "{}", err);
}
#[test]
fn choice_binaries() {
assert_eq!(WebpFfmpegChoice::Path.into_binary(), PathBuf::from("ffmpeg"));
assert_eq!(WebpFfmpegChoice::HomebrewFallback(PathBuf::from(FALLBACK)).into_binary(), PathBuf::from(FALLBACK));
}
}
@@ -1,16 +1,176 @@
//! Shared config for thumbnails.
//! Shared config for video thumbnails (the still and animated previews stored
//! beside each video file in the public bucket).
//!
//! # Thumbnail versions (`media_files.maybe_thumbnail_version`)
//!
//! | Version | Since | Files beside `<video>.mp4` |
//! |---------|--------------------------|------------------------------------------------------------|
//! | `NULL` | 2024-02-28 | `-thumb.jpg`, `-thumb.gif` (legacy, or not yet generated) |
//! | `1` | 2026-03-18 | `-thumb.jpg`, `-thumb.gif` |
//! | `2` | 2026-09 (see below) | `-thumb.jpg`, `-thumb.webp`, `-thumb.gif` |
//!
//! * **`NULL`**: No version recorded. Clients have linked `-thumb.gif` and
//! `-thumb.jpg` since 2024-02-28 ("video card thumbnails", e7a427289f), so
//! older rows may have GIF/JPG files from earlier pipelines, or none at all.
//! A `NULL` row may also just be waiting for the thumbnail job. Serving uses
//! the `created_at` cutover in [`animated_preview_format`] to pick GIF or WebP.
//! * **`1`**: Written by `video-thumbnail-job` since it launched on 2026-03-18
//! (#1157). A first-frame JPG and an animated GIF: the first 5 seconds at
//! 10 fps, fit within 360x360.
//! * **`2`**: Same JPG, plus an animated WebP (quality 60) of the same frames,
//! which is typically 6-50x smaller than the GIF. The job still writes the
//! GIF too ("dual writes"); see "macOS Catalina" below. Deployed in late
//! September 2026. TODO(bt): Replace "2026-09" above with the exact deploy date.
//!
//! # macOS Catalina and GIF dual writes
//!
//! Animated WebP needs WebKit on macOS 11 (Big Sur) or newer. ArtCraft desktop
//! (Tauri) uses the system WKWebView, so on macOS 10.15 (Catalina) a WebP in
//! an `<img>` doesn't render. Version 2 therefore keeps writing `-thumb.gif`
//! beside the WebP, and the API exposes it as the Catalina fallback so the
//! desktop app can fall back to it. We may keep the GIF dual writes forever;
//! drop them only once no supported client needs them.
//!
//! # Changing this scheme
//!
//! These suffixes are how files are physically named in the public bucket and
//! are shared between services. Never change or reuse a suffix or a version
//! number. Add a new version instead, and update [`animated_preview_format`].
/// NB: DO NOT CHANGE WITHOUT MIGRATION - THIS IS SHARED BETWEEN SERVICES
/// Video media files have conventional gif thumbnails with this suffix.
/// This is how files are physically named in the public bucket.
pub const VIDEO_ANIMATED_GIF_THUMBNAIL_SUFFIX: &str = "-thumb.gif";
/// NB: DO NOT CHANGE WITHOUT MIGRATION - THIS IS SHARED BETWEEN SERVICES
/// Video media files with thumbnail version 2+ have animated webp thumbnails
/// with this suffix. This is how files are physically named in the public bucket.
pub const VIDEO_ANIMATED_WEBP_THUMBNAIL_SUFFIX: &str = "-thumb.webp";
/// NB: DO NOT CHANGE WITHOUT MIGRATION - THIS IS SHARED BETWEEN SERVICES
/// Video media files have conventional static jpg thumbnails with this suffix.
/// This is how files are physically named in the public bucket.
pub const VIDEO_STATIC_JPG_THUMBNAIL_SUFFIX: &str = "-thumb.jpg";
/// The current thumbnail format is a dual jpg + gif of a set size.
/// We write this as version "1". If we change the thumbnails in the behavior,
/// we may increment this number so that we can backfill.
pub const CURRENT_VIDEO_THUMBNAIL_VERSION : u8 = 1;
/// Version 1 (2026-03-18): first-frame jpg + animated gif.
pub const VIDEO_THUMBNAIL_VERSION_GIF: u8 = 1;
/// Version 2 (2026-09): first-frame jpg + animated webp, plus the animated gif
/// as a dual write for macOS Catalina.
pub const VIDEO_THUMBNAIL_VERSION_WEBP: u8 = 2;
/// The version `video-thumbnail-job` writes for newly processed videos.
pub const CURRENT_VIDEO_THUMBNAIL_VERSION: u8 = VIDEO_THUMBNAIL_VERSION_WEBP;
/// Videos with a `NULL` thumbnail version created at or after this instant
/// (2026-10-10T00:00:00Z) are served WebP previews; older `NULL` rows get GIF.
///
/// TODO(bt): Remove this cutover; it shouldn't live forever. It exists
/// because `NULL` means two things today: "legacy row that only ever had a
/// GIF" and "new row the thumbnail job hasn't reached yet (it will write a
/// WebP)". To remove it:
/// 1. Wait until the thumbnail job's lookback window has passed since
/// 2026-10-10, so every row created after the cutover has been processed.
/// 2. Backfill legacy rows older than the lookback window, eg.
/// `UPDATE media_files SET maybe_thumbnail_version = 1
/// WHERE maybe_thumbnail_version IS NULL AND media_class = 'video'
/// AND created_at < '2026-10-10'`.
/// (Rows inside the lookback window must be left `NULL`, or the job
/// will skip them.)
/// 3. `NULL` then only means "not generated yet", so serve the current
/// format for it: delete this constant and the `created_at` argument to
/// [`animated_preview_format`], and stop selecting `created_at` for it.
pub const NULL_THUMBNAIL_VERSION_WEBP_CUTOVER_UNIX_SECONDS: i64 = 1_791_590_400;
/// The format of a video's animated preview file.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum AnimatedPreviewFormat {
Gif,
Webp,
}
impl AnimatedPreviewFormat {
/// The bucket file suffix, eg. `-thumb.webp`.
pub const fn suffix(self) -> &'static str {
match self {
Self::Gif => VIDEO_ANIMATED_GIF_THUMBNAIL_SUFFIX,
Self::Webp => VIDEO_ANIMATED_WEBP_THUMBNAIL_SUFFIX,
}
}
}
/// Which animated preview to serve for a video, from its
/// `media_files.maybe_thumbnail_version` and `created_at` (as Unix seconds).
/// See the module docs for the version history.
pub fn animated_preview_format(
maybe_thumbnail_version: Option<u8>,
created_at_unix_seconds: i64,
) -> AnimatedPreviewFormat {
match maybe_thumbnail_version {
Some(version) if version >= VIDEO_THUMBNAIL_VERSION_WEBP => AnimatedPreviewFormat::Webp,
Some(_) => AnimatedPreviewFormat::Gif,
None if created_at_unix_seconds >= NULL_THUMBNAIL_VERSION_WEBP_CUTOVER_UNIX_SECONDS => AnimatedPreviewFormat::Webp,
None => AnimatedPreviewFormat::Gif,
}
}
#[cfg(test)]
mod tests {
use super::*;
// 2026-10-09T23:59:59Z and 2026-10-10T00:00:00Z
const JUST_BEFORE_CUTOVER: i64 = 1_791_590_399;
const AT_CUTOVER: i64 = 1_791_590_400;
// 2024-05-01T00:00:00Z
const LEGACY_ERA: i64 = 1_714_521_600;
#[test]
fn suffixes_are_stable() {
assert_eq!(VIDEO_ANIMATED_GIF_THUMBNAIL_SUFFIX, "-thumb.gif");
assert_eq!(VIDEO_ANIMATED_WEBP_THUMBNAIL_SUFFIX, "-thumb.webp");
assert_eq!(VIDEO_STATIC_JPG_THUMBNAIL_SUFFIX, "-thumb.jpg");
assert_eq!(AnimatedPreviewFormat::Gif.suffix(), "-thumb.gif");
assert_eq!(AnimatedPreviewFormat::Webp.suffix(), "-thumb.webp");
}
#[test]
fn versions_are_stable() {
assert_eq!(VIDEO_THUMBNAIL_VERSION_GIF, 1);
assert_eq!(VIDEO_THUMBNAIL_VERSION_WEBP, 2);
assert_eq!(CURRENT_VIDEO_THUMBNAIL_VERSION, 2);
}
mod animated_preview_format_tests {
use super::*;
#[test]
fn version_1_is_gif_regardless_of_date() {
assert_eq!(animated_preview_format(Some(1), LEGACY_ERA), AnimatedPreviewFormat::Gif);
assert_eq!(animated_preview_format(Some(1), AT_CUTOVER), AnimatedPreviewFormat::Gif);
}
#[test]
fn version_2_is_webp_regardless_of_date() {
assert_eq!(animated_preview_format(Some(2), LEGACY_ERA), AnimatedPreviewFormat::Webp);
assert_eq!(animated_preview_format(Some(2), AT_CUTOVER), AnimatedPreviewFormat::Webp);
}
#[test]
fn null_before_cutover_is_gif() {
assert_eq!(animated_preview_format(None, LEGACY_ERA), AnimatedPreviewFormat::Gif);
assert_eq!(animated_preview_format(None, JUST_BEFORE_CUTOVER), AnimatedPreviewFormat::Gif);
}
#[test]
fn null_at_or_after_cutover_is_webp() {
assert_eq!(animated_preview_format(None, AT_CUTOVER), AnimatedPreviewFormat::Webp);
assert_eq!(animated_preview_format(None, AT_CUTOVER + 86_400), AnimatedPreviewFormat::Webp);
}
#[test]
fn cutover_is_2026_10_10_utc() {
// 20_736 days from 1970-01-01 to 2026-10-10.
assert_eq!(NULL_THUMBNAIL_VERSION_WEBP_CUTOVER_UNIX_SECONDS, 20_736 * 86_400);
}
}
}
@@ -34,6 +34,8 @@ pub struct FolderMediaFileRow {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
/// Which video preview files exist. See `video_thumbnail_suffixes` in `bucket_paths`.
pub maybe_thumbnail_version: Option<u8>,
// Cover image's bucket fields, from a LEFT JOIN on
// mf.maybe_cover_image_media_file_token. All three are Some-or-None
@@ -102,6 +104,7 @@ SELECT
mf.public_bucket_directory_hash,
mf.maybe_public_bucket_prefix,
mf.maybe_public_bucket_extension,
mf.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,
cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,
@@ -150,6 +153,7 @@ LIMIT ?
public_bucket_directory_hash: r.public_bucket_directory_hash,
maybe_public_bucket_prefix: r.maybe_public_bucket_prefix,
maybe_public_bucket_extension: r.maybe_public_bucket_extension,
maybe_thumbnail_version: r.maybe_thumbnail_version,
maybe_cover_public_bucket_directory_hash: r.maybe_cover_public_bucket_directory_hash,
maybe_cover_public_bucket_prefix: r.maybe_cover_public_bucket_prefix,
maybe_cover_public_bucket_extension: r.maybe_cover_public_bucket_extension,
@@ -183,6 +187,7 @@ SELECT
mf.public_bucket_directory_hash,
mf.maybe_public_bucket_prefix,
mf.maybe_public_bucket_extension,
mf.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,
cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,
@@ -229,6 +234,7 @@ LIMIT ?
public_bucket_directory_hash: r.public_bucket_directory_hash,
maybe_public_bucket_prefix: r.maybe_public_bucket_prefix,
maybe_public_bucket_extension: r.maybe_public_bucket_extension,
maybe_thumbnail_version: r.maybe_thumbnail_version,
maybe_cover_public_bucket_directory_hash: r.maybe_cover_public_bucket_directory_hash,
maybe_cover_public_bucket_prefix: r.maybe_cover_public_bucket_prefix,
maybe_cover_public_bucket_extension: r.maybe_cover_public_bucket_extension,
@@ -98,6 +98,7 @@ SELECT
mf.public_bucket_directory_hash,
mf.maybe_public_bucket_prefix,
mf.maybe_public_bucket_extension,
mf.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,
cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,
@@ -168,6 +169,7 @@ SELECT
mf.public_bucket_directory_hash,
mf.maybe_public_bucket_prefix,
mf.maybe_public_bucket_extension,
mf.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,
cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,
@@ -27,6 +27,7 @@ pub struct JobMediaFileRecord {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_thumbnail_version: Option<u8>,
pub maybe_file_cover_image_public_bucket_hash: Option<String>,
pub maybe_file_cover_image_public_bucket_prefix: Option<String>,
@@ -78,6 +79,7 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,
media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,
@@ -117,6 +117,8 @@ SELECT
media_files.public_bucket_directory_hash as maybe_media_file_public_bucket_directory_hash,
media_files.maybe_public_bucket_prefix as maybe_media_file_public_bucket_prefix,
media_files.maybe_public_bucket_extension as maybe_media_file_public_bucket_extension,
media_files.maybe_thumbnail_version as maybe_media_file_thumbnail_version,
media_files.created_at as maybe_media_file_created_at,
jobs.assigned_worker as maybe_assigned_worker,
jobs.assigned_cluster as maybe_assigned_cluster,
@@ -246,6 +248,8 @@ fn raw_records_to_public_result(records: Vec<RawGenericInferenceJobStatus>) -> V
public_bucket_location_or_hash: public_bucket_hash.to_string(),
maybe_media_file_public_bucket_prefix: record.maybe_media_file_public_bucket_prefix.clone(),
maybe_media_file_public_bucket_extension: record.maybe_media_file_public_bucket_extension.clone(),
maybe_media_file_thumbnail_version: record.maybe_media_file_thumbnail_version,
maybe_media_file_created_at: record.maybe_media_file_created_at,
public_bucket_location_is_hash: bucket_path_is_hash,
maybe_successfully_completed_at: record.maybe_successfully_completed_at,
}
@@ -320,6 +324,8 @@ struct RawGenericInferenceJobStatus {
pub maybe_media_file_public_bucket_directory_hash: Option<String>, // NB: This is the bucket directory hash
pub maybe_media_file_public_bucket_prefix: Option<String>,
pub maybe_media_file_public_bucket_extension: Option<String>,
pub maybe_media_file_thumbnail_version: Option<u8>,
pub maybe_media_file_created_at: Option<DateTime<Utc>>,
pub maybe_assigned_worker: Option<String>,
pub maybe_assigned_cluster: Option<String>,
@@ -377,6 +383,8 @@ impl FromRow<'_, MySqlRow> for RawGenericInferenceJobStatus {
maybe_media_file_public_bucket_directory_hash: row.try_get("maybe_media_file_public_bucket_directory_hash")?,
maybe_media_file_public_bucket_prefix: row.try_get("maybe_media_file_public_bucket_prefix")?,
maybe_media_file_public_bucket_extension: row.try_get("maybe_media_file_public_bucket_extension")?,
maybe_media_file_thumbnail_version: row.try_get("maybe_media_file_thumbnail_version")?,
maybe_media_file_created_at: row.try_get("maybe_media_file_created_at")?,
maybe_assigned_worker: row.try_get("maybe_assigned_worker")?,
maybe_assigned_cluster: row.try_get("maybe_assigned_cluster")?,
maybe_frontend_failure_category: row.try_get("maybe_frontend_failure_category")?,
@@ -75,6 +75,8 @@ SELECT
media_files.public_bucket_directory_hash as maybe_media_file_public_bucket_directory_hash,
media_files.maybe_public_bucket_prefix as maybe_media_file_public_bucket_prefix,
media_files.maybe_public_bucket_extension as maybe_media_file_public_bucket_extension,
media_files.maybe_thumbnail_version as `maybe_media_file_thumbnail_version?: u8`,
media_files.created_at as `maybe_media_file_created_at?: DateTime<Utc>`,
jobs.assigned_worker as maybe_assigned_worker,
jobs.assigned_cluster as maybe_assigned_cluster,
@@ -221,6 +223,8 @@ fn raw_record_to_public_result(record: RawGenericInferenceJobStatus) -> GenericI
public_bucket_location_or_hash: public_bucket_hash.to_string(),
maybe_media_file_public_bucket_prefix: record.maybe_media_file_public_bucket_prefix.clone(),
maybe_media_file_public_bucket_extension: record.maybe_media_file_public_bucket_extension.clone(),
maybe_media_file_thumbnail_version: record.maybe_media_file_thumbnail_version,
maybe_media_file_created_at: record.maybe_media_file_created_at,
public_bucket_location_is_hash: bucket_path_is_hash,
maybe_successfully_completed_at: record.maybe_successfully_completed_at,
}
@@ -293,6 +297,8 @@ struct RawGenericInferenceJobStatus {
pub maybe_media_file_public_bucket_directory_hash: Option<String>, // NB: This is the bucket directory hash
pub maybe_media_file_public_bucket_prefix: Option<String>,
pub maybe_media_file_public_bucket_extension: Option<String>,
pub maybe_media_file_thumbnail_version: Option<u8>,
pub maybe_media_file_created_at: Option<DateTime<Utc>>,
pub maybe_assigned_worker: Option<String>,
pub maybe_assigned_cluster: Option<String>,
@@ -79,6 +79,8 @@ pub struct ResultDetails {
pub public_bucket_location_or_hash: String,
pub maybe_media_file_public_bucket_prefix: Option<String>,
pub maybe_media_file_public_bucket_extension: Option<String>,
pub maybe_media_file_thumbnail_version: Option<u8>,
pub maybe_media_file_created_at: Option<DateTime<Utc>>,
/// Whether the location is a full path (for tts) or a hash (for vc) that
/// needs to be reconstructed into a path.
@@ -183,6 +183,8 @@ SELECT
media_files.public_bucket_directory_hash as maybe_media_file_public_bucket_directory_hash,
media_files.maybe_public_bucket_prefix as maybe_media_file_public_bucket_prefix,
media_files.maybe_public_bucket_extension as maybe_media_file_public_bucket_extension,
media_files.maybe_thumbnail_version as maybe_media_file_thumbnail_version,
media_files.created_at as maybe_media_file_created_at,
jobs.assigned_worker as maybe_assigned_worker,
jobs.assigned_cluster as maybe_assigned_cluster,
@@ -312,6 +314,8 @@ fn raw_records_to_public_result(records: Vec<RawGenericInferenceJobStatus>) -> V
public_bucket_location_or_hash: public_bucket_hash.to_string(),
maybe_media_file_public_bucket_prefix: record.maybe_media_file_public_bucket_prefix.clone(),
maybe_media_file_public_bucket_extension: record.maybe_media_file_public_bucket_extension.clone(),
maybe_media_file_thumbnail_version: record.maybe_media_file_thumbnail_version,
maybe_media_file_created_at: record.maybe_media_file_created_at,
public_bucket_location_is_hash: bucket_path_is_hash,
maybe_successfully_completed_at: record.maybe_successfully_completed_at,
}
@@ -386,6 +390,8 @@ struct RawGenericInferenceJobStatus {
pub maybe_media_file_public_bucket_directory_hash: Option<String>, // NB: This is the bucket directory hash
pub maybe_media_file_public_bucket_prefix: Option<String>,
pub maybe_media_file_public_bucket_extension: Option<String>,
pub maybe_media_file_thumbnail_version: Option<u8>,
pub maybe_media_file_created_at: Option<DateTime<Utc>>,
pub maybe_assigned_worker: Option<String>,
pub maybe_assigned_cluster: Option<String>,
@@ -443,6 +449,8 @@ impl FromRow<'_, MySqlRow> for RawGenericInferenceJobStatus {
maybe_media_file_public_bucket_directory_hash: row.try_get("maybe_media_file_public_bucket_directory_hash")?,
maybe_media_file_public_bucket_prefix: row.try_get("maybe_media_file_public_bucket_prefix")?,
maybe_media_file_public_bucket_extension: row.try_get("maybe_media_file_public_bucket_extension")?,
maybe_media_file_thumbnail_version: row.try_get("maybe_media_file_thumbnail_version")?,
maybe_media_file_created_at: row.try_get("maybe_media_file_created_at")?,
maybe_assigned_worker: row.try_get("maybe_assigned_worker")?,
maybe_assigned_cluster: row.try_get("maybe_assigned_cluster")?,
maybe_frontend_failure_category: row.try_get("maybe_frontend_failure_category")?,
@@ -1,5 +1,6 @@
use std::marker::PhantomData;
use chrono::{DateTime, Utc};
use sqlx::{Executor, MySql, QueryBuilder};
use enums::by_table::media_files::media_file_class::MediaFileClass;
@@ -15,6 +16,9 @@ pub struct MediaFileThumbnailRow {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
/// Which video preview files exist. See `video_thumbnail_suffixes` in `bucket_paths`.
pub maybe_thumbnail_version: Option<u8>,
pub created_at: DateTime<Utc>,
}
pub struct BatchGetMediaFileThumbnailsByTokensArgs<'e, 'c, E>
@@ -49,7 +53,9 @@ where
media_type, \
public_bucket_directory_hash, \
maybe_public_bucket_prefix, \
maybe_public_bucket_extension \
maybe_public_bucket_extension, \
maybe_thumbnail_version, \
created_at \
FROM media_files \
WHERE user_deleted_at IS NULL \
AND mod_deleted_at IS NULL \
@@ -74,6 +80,8 @@ where
public_bucket_directory_hash: r.public_bucket_directory_hash,
maybe_public_bucket_prefix: r.maybe_public_bucket_prefix,
maybe_public_bucket_extension: r.maybe_public_bucket_extension,
maybe_thumbnail_version: r.maybe_thumbnail_version,
created_at: r.created_at,
}).collect())
}
@@ -88,4 +96,6 @@ struct RawRow {
public_bucket_directory_hash: String,
maybe_public_bucket_prefix: Option<String>,
maybe_public_bucket_extension: Option<String>,
maybe_thumbnail_version: Option<u8>,
created_at: DateTime<Utc>,
}
@@ -90,6 +90,7 @@ pub struct MediaFile {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_thumbnail_version: Option<u8>,
pub maybe_ratings_positive_count: Option<u32>,
pub maybe_ratings_negative_count: Option<u32>,
@@ -171,6 +172,7 @@ pub struct MediaFileRaw {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_thumbnail_version: Option<u8>,
//pub model_is_mod_approved: bool, // converted
//pub maybe_mod_user_token: Option<String>,
@@ -270,6 +272,7 @@ pub async fn batch_get_media_files(
public_bucket_directory_hash: record.public_bucket_directory_hash,
maybe_public_bucket_prefix: record.maybe_public_bucket_prefix,
maybe_public_bucket_extension: record.maybe_public_bucket_extension,
maybe_thumbnail_version: record.maybe_thumbnail_version,
maybe_ratings_positive_count: record.maybe_ratings_positive_count,
maybe_ratings_negative_count: record.maybe_ratings_negative_count,
maybe_bookmark_count: record.maybe_bookmark_count,
@@ -336,6 +339,7 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version,
entity_stats.ratings_positive_count as maybe_ratings_positive_count,
entity_stats.ratings_negative_count as maybe_ratings_negative_count,
@@ -427,6 +431,7 @@ impl FromRow<'_, MySqlRow> for MediaFileRaw {
public_bucket_directory_hash: row.try_get("public_bucket_directory_hash")?,
maybe_public_bucket_prefix: row.try_get("maybe_public_bucket_prefix")?,
maybe_public_bucket_extension: row.try_get("maybe_public_bucket_extension")?,
maybe_thumbnail_version: row.try_get("maybe_thumbnail_version")?,
maybe_ratings_positive_count: row.try_get("maybe_ratings_positive_count")?,
maybe_ratings_negative_count: row.try_get("maybe_ratings_negative_count")?,
maybe_bookmark_count: row.try_get("maybe_bookmark_count")?,
@@ -42,6 +42,9 @@ pub struct MediaFilesByTokensRecord {
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
/// Which video preview files exist. See `video_thumbnail_suffixes` in `bucket_paths`.
pub maybe_thumbnail_version: Option<u8>,
pub maybe_creator_user_token: Option<UserToken>,
pub maybe_creator_username: Option<String>,
pub maybe_creator_display_name: Option<String>,
@@ -145,6 +148,7 @@ async fn get_raw_media_files_by_tokens(
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version,
m.is_user_upload,
m.is_intermediate_system_file,
@@ -211,6 +215,7 @@ async fn get_raw_media_files_by_tokens(
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version,
m.is_user_upload,
m.is_intermediate_system_file,
@@ -315,6 +320,7 @@ fn map_to_media_files(dataset:Vec<RawMediaFileJoinUser>) -> Vec<MediaFilesByToke
public_bucket_directory_hash: media_file.public_bucket_directory_hash,
maybe_public_bucket_prefix: media_file.maybe_public_bucket_prefix,
maybe_public_bucket_extension: media_file.maybe_public_bucket_extension,
maybe_thumbnail_version: media_file.maybe_thumbnail_version,
is_user_upload: i8_to_bool(media_file.is_user_upload),
is_intermediate_system_file: i8_to_bool(media_file.is_intermediate_system_file),
@@ -371,6 +377,7 @@ fn map_to_media_files(dataset:Vec<RawMediaFileJoinUser>) -> Vec<MediaFilesByToke
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_thumbnail_version: Option<u8>,
pub maybe_ratings_positive_count: Option<u32>,
pub maybe_ratings_negative_count: Option<u32>,
@@ -427,6 +434,7 @@ impl FromRow<'_, MySqlRow> for RawMediaFileJoinUser {
public_bucket_directory_hash: row.try_get("public_bucket_directory_hash")?,
maybe_public_bucket_prefix: row.try_get("maybe_public_bucket_prefix")?,
maybe_public_bucket_extension: row.try_get("maybe_public_bucket_extension")?,
maybe_thumbnail_version: row.try_get("maybe_thumbnail_version")?,
maybe_ratings_positive_count: row.try_get("maybe_ratings_positive_count")?,
maybe_ratings_negative_count: row.try_get("maybe_ratings_negative_count")?,
maybe_bookmark_count: row.try_get("maybe_bookmark_count")?,
@@ -96,6 +96,7 @@ pub struct MediaFile {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_thumbnail_version: Option<u8>,
pub maybe_ratings_positive_count: Option<u32>,
pub maybe_ratings_negative_count: Option<u32>,
@@ -184,6 +185,7 @@ pub struct MediaFileRaw {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_thumbnail_version: Option<u8>,
//pub model_is_mod_approved: bool, // converted
//pub maybe_mod_user_token: Option<String>,
@@ -296,6 +298,7 @@ pub async fn get_media_file_with_transactor(
public_bucket_directory_hash: record.public_bucket_directory_hash,
maybe_public_bucket_prefix: record.maybe_public_bucket_prefix,
maybe_public_bucket_extension: record.maybe_public_bucket_extension,
maybe_thumbnail_version: record.maybe_thumbnail_version,
maybe_ratings_positive_count: record.maybe_ratings_positive_count,
maybe_ratings_negative_count: record.maybe_ratings_negative_count,
maybe_bookmark_count: record.maybe_bookmark_count,
@@ -373,6 +376,7 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
entity_stats.ratings_positive_count as maybe_ratings_positive_count,
entity_stats.ratings_negative_count as maybe_ratings_negative_count,
@@ -495,6 +499,7 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
entity_stats.ratings_positive_count as maybe_ratings_positive_count,
entity_stats.ratings_negative_count as maybe_ratings_negative_count,
@@ -66,6 +66,7 @@ pub struct MediaFile {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_thumbnail_version: Option<u8>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
@@ -114,6 +115,7 @@ pub struct MediaFileRaw {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_thumbnail_version: Option<u8>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
@@ -189,6 +191,7 @@ pub async fn list_batch_generated_redux_media_files_with_transactor(
public_bucket_directory_hash: record.public_bucket_directory_hash,
maybe_public_bucket_prefix: record.maybe_public_bucket_prefix,
maybe_public_bucket_extension: record.maybe_public_bucket_extension,
maybe_thumbnail_version: record.maybe_thumbnail_version,
created_at: record.created_at,
updated_at: record.updated_at,
}
@@ -246,6 +249,7 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
m.created_at,
m.updated_at
@@ -337,6 +341,7 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
m.created_at,
m.updated_at
@@ -57,6 +57,7 @@ pub struct FeaturedMediaFileListItem {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_thumbnail_version: Option<u8>,
pub maybe_creator_user_token: Option<UserToken>,
pub maybe_creator_username: Option<String>,
@@ -145,6 +146,7 @@ pub async fn list_featured_media_files(args: ListFeaturedMediaFilesArgs<'_>) ->
public_bucket_directory_hash: record.public_bucket_directory_hash,
maybe_public_bucket_prefix: record.maybe_public_bucket_prefix,
maybe_public_bucket_extension: record.maybe_public_bucket_extension,
maybe_thumbnail_version: record.maybe_thumbnail_version,
maybe_creator_user_token: record.maybe_creator_user_token,
maybe_creator_username: record.maybe_creator_username,
maybe_creator_display_name: record.maybe_creator_display_name,
@@ -221,6 +223,7 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version,
m.maybe_creator_user_token,
u.username as maybe_creator_username,
@@ -463,6 +466,7 @@ struct MediaFileListItemInternal {
public_bucket_directory_hash: String,
maybe_public_bucket_prefix: Option<String>,
maybe_public_bucket_extension: Option<String>,
maybe_thumbnail_version: Option<u8>,
maybe_creator_user_token: Option<UserToken>,
maybe_creator_username: Option<String>,
@@ -526,6 +530,7 @@ impl FromRow<'_, MySqlRow> for MediaFileListItemInternal {
public_bucket_directory_hash: row.try_get("public_bucket_directory_hash")?,
maybe_public_bucket_prefix: row.try_get("maybe_public_bucket_prefix")?,
maybe_public_bucket_extension: row.try_get("maybe_public_bucket_extension")?,
maybe_thumbnail_version: row.try_get("maybe_thumbnail_version")?,
maybe_creator_user_token,
maybe_creator_username: row.try_get("maybe_creator_username")?,
maybe_creator_display_name: row.try_get("maybe_creator_display_name")?,
@@ -54,6 +54,7 @@ pub struct MediaFileListItem {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_thumbnail_version: Option<u8>,
pub maybe_creator_user_token: Option<UserToken>,
pub maybe_creator_username: Option<String>,
@@ -142,6 +143,7 @@ pub async fn list_media_files(args: ListMediaFilesArgs<'_>) -> AnyhowResult<Medi
public_bucket_directory_hash: record.public_bucket_directory_hash,
maybe_public_bucket_prefix: record.maybe_public_bucket_prefix,
maybe_public_bucket_extension: record.maybe_public_bucket_extension,
maybe_thumbnail_version: record.maybe_thumbnail_version,
maybe_creator_user_token: record.maybe_creator_user_token,
maybe_creator_username: record.maybe_creator_username,
maybe_creator_display_name: record.maybe_creator_display_name,
@@ -218,6 +220,7 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version,
m.maybe_creator_user_token,
u.username as maybe_creator_username,
@@ -443,6 +446,7 @@ struct MediaFileListItemInternal {
public_bucket_directory_hash: String,
maybe_public_bucket_prefix: Option<String>,
maybe_public_bucket_extension: Option<String>,
maybe_thumbnail_version: Option<u8>,
maybe_creator_user_token: Option<UserToken>,
maybe_creator_username: Option<String>,
@@ -506,6 +510,7 @@ impl FromRow<'_, MySqlRow> for MediaFileListItemInternal {
public_bucket_directory_hash: row.try_get("public_bucket_directory_hash")?,
maybe_public_bucket_prefix: row.try_get("maybe_public_bucket_prefix")?,
maybe_public_bucket_extension: row.try_get("maybe_public_bucket_extension")?,
maybe_thumbnail_version: row.try_get("maybe_thumbnail_version")?,
maybe_creator_user_token,
maybe_creator_username: row.try_get("maybe_creator_username")?,
maybe_creator_display_name: row.try_get("maybe_creator_display_name")?,
@@ -49,6 +49,7 @@ pub struct MediaFileListItem {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_thumbnail_version: Option<u8>,
pub creator_set_visibility: Visibility,
@@ -129,6 +130,7 @@ pub async fn list_media_files_by_batch_token(args: ListMediaFileByBatchArgs<'_>)
public_bucket_directory_hash: record.public_bucket_directory_hash,
maybe_public_bucket_prefix: record.maybe_public_bucket_prefix,
maybe_public_bucket_extension: record.maybe_public_bucket_extension,
maybe_thumbnail_version: record.maybe_thumbnail_version,
creator_set_visibility: record.creator_set_visibility,
is_user_upload: i8_to_bool(record.is_user_upload),
is_intermediate_system_file: i8_to_bool(record.is_intermediate_system_file),
@@ -180,6 +182,7 @@ fn select_result_fields() -> String {
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version,
m.creator_set_visibility,
@@ -290,6 +293,7 @@ struct MediaFileListItemInternal {
public_bucket_directory_hash: String,
maybe_public_bucket_prefix: Option<String>,
maybe_public_bucket_extension: Option<String>,
maybe_thumbnail_version: Option<u8>,
creator_set_visibility: Visibility,
@@ -340,6 +344,7 @@ impl FromRow<'_, MySqlRow> for MediaFileListItemInternal {
public_bucket_directory_hash: row.try_get("public_bucket_directory_hash")?,
maybe_public_bucket_prefix: row.try_get("maybe_public_bucket_prefix")?,
maybe_public_bucket_extension: row.try_get("maybe_public_bucket_extension")?,
maybe_thumbnail_version: row.try_get("maybe_thumbnail_version")?,
creator_set_visibility: Visibility::try_from_mysql_row(row, "creator_set_visibility")?,
is_user_upload: row.try_get("is_user_upload")?,
is_intermediate_system_file: row.try_get("is_intermediate_system_file")?,
@@ -25,6 +25,7 @@ const RESULT_FIELDS: &str = r#"
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version,
m.maybe_prompt_token,
@@ -30,6 +30,7 @@ pub struct SessionMediaFileListItem {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_thumbnail_version: Option<u8>,
pub maybe_creator_user_token: Option<UserToken>,
pub maybe_creator_username: Option<String>,
@@ -141,6 +142,7 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,
users.username as maybe_creator_username,
@@ -206,6 +208,7 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,
users.username as maybe_creator_username,
@@ -31,6 +31,7 @@ pub struct ProjectMediaFileListItem {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_thumbnail_version: Option<u8>,
pub maybe_creator_user_token: Option<UserToken>,
pub maybe_creator_username: Option<String>,
@@ -148,6 +149,7 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,
users.username as maybe_creator_username,
@@ -205,6 +207,7 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,
users.username as maybe_creator_username,
@@ -274,6 +277,7 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,
users.username as maybe_creator_username,
@@ -331,6 +335,7 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,
users.username as maybe_creator_username,
@@ -32,6 +32,8 @@ pub struct MediaFileListRow {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
/// Which video preview files exist. See `video_thumbnail_suffixes` in `bucket_paths`.
pub maybe_thumbnail_version: Option<u8>,
// Cover image's bucket fields, from a LEFT JOIN on
// mf.maybe_cover_image_media_file_token. All three are Some-or-None
@@ -38,6 +38,7 @@ pub struct MediaFileListItem {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_thumbnail_version: Option<u8>,
pub maybe_prompt_token: Option<PromptToken>,
@@ -90,6 +91,7 @@ pub(super) struct MediaFileListItemInternal {
public_bucket_directory_hash: String,
maybe_public_bucket_prefix: Option<String>,
maybe_public_bucket_extension: Option<String>,
maybe_thumbnail_version: Option<u8>,
maybe_prompt_token: Option<PromptToken>,
@@ -146,6 +148,7 @@ impl FromRow<'_, MySqlRow> for MediaFileListItemInternal {
public_bucket_directory_hash: row.try_get("public_bucket_directory_hash")?,
maybe_public_bucket_prefix: row.try_get("maybe_public_bucket_prefix")?,
maybe_public_bucket_extension: row.try_get("maybe_public_bucket_extension")?,
maybe_thumbnail_version: row.try_get("maybe_thumbnail_version")?,
maybe_prompt_token: PromptToken::try_from_mysql_row_nullable(row, "maybe_prompt_token")?,
creator_set_visibility: Visibility::try_from_mysql_row(row, "creator_set_visibility")?,
is_user_upload: row.try_get("is_user_upload")?,
@@ -180,6 +183,7 @@ impl From<MediaFileListItemInternal> for MediaFileListItem {
public_bucket_directory_hash: record.public_bucket_directory_hash,
maybe_public_bucket_prefix: record.maybe_public_bucket_prefix,
maybe_public_bucket_extension: record.maybe_public_bucket_extension,
maybe_thumbnail_version: record.maybe_thumbnail_version,
maybe_prompt_token: record.maybe_prompt_token,
creator_set_visibility: record.creator_set_visibility,
is_user_upload: i8_to_bool(record.is_user_upload),
@@ -1,3 +1,4 @@
use chrono::{DateTime, Utc};
use sqlx::mysql::MySqlRow;
use sqlx::pool::PoolConnection;
use sqlx::{FromRow, MySql, QueryBuilder, Row};
@@ -16,6 +17,9 @@ pub struct BatchPromptContextItem {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
/// Which video preview files exist. See `video_thumbnail_suffixes` in `bucket_paths`.
pub maybe_thumbnail_version: Option<u8>,
pub created_at: DateTime<Utc>,
}
impl FromRow<'_, MySqlRow> for BatchPromptContextItem {
@@ -27,6 +31,8 @@ impl FromRow<'_, MySqlRow> for BatchPromptContextItem {
public_bucket_directory_hash: row.try_get("public_bucket_directory_hash")?,
maybe_public_bucket_prefix: row.try_get("maybe_public_bucket_prefix")?,
maybe_public_bucket_extension: row.try_get("maybe_public_bucket_extension")?,
maybe_thumbnail_version: row.try_get("maybe_thumbnail_version")?,
created_at: row.try_get("created_at")?,
})
}
}
@@ -49,7 +55,9 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version,
m.created_at
FROM prompt_context_items pci
JOIN media_files m ON pci.media_token = m.token
@@ -14,6 +14,9 @@ pub struct PromptContextItem {
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
/// Which video preview files exist. See `video_thumbnail_suffixes` in `bucket_paths`.
pub maybe_thumbnail_version: Option<u8>,
pub created_at: DateTime<Utc>,
}
struct RawPromptContextItem {
@@ -22,6 +25,8 @@ struct RawPromptContextItem {
public_bucket_directory_hash: String,
maybe_public_bucket_prefix: Option<String>,
maybe_public_bucket_extension: Option<String>,
maybe_thumbnail_version: Option<u8>,
created_at: DateTime<Utc>,
}
pub async fn list_prompt_context_items(
@@ -37,7 +42,9 @@ SELECT
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension
m.maybe_public_bucket_extension,
m.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
m.created_at as `created_at: DateTime<Utc>`
FROM prompt_context_items pci
JOIN media_files m
@@ -61,6 +68,8 @@ ORDER BY pci.id ASC
public_bucket_directory_hash: item.public_bucket_directory_hash,
maybe_public_bucket_prefix: item.maybe_public_bucket_prefix,
maybe_public_bucket_extension: item.maybe_public_bucket_extension,
maybe_thumbnail_version: item.maybe_thumbnail_version,
created_at: item.created_at,
}
}).collect::<Vec<PromptContextItem>>())
}
@@ -61,6 +61,7 @@ SELECT
mf.public_bucket_directory_hash,
mf.maybe_public_bucket_prefix,
mf.maybe_public_bucket_extension,
mf.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,
cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,
@@ -54,6 +54,7 @@ SELECT
mf.public_bucket_directory_hash,
mf.maybe_public_bucket_prefix,
mf.maybe_public_bucket_extension,
mf.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,
cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,
@@ -54,6 +54,7 @@ SELECT
mf.public_bucket_directory_hash,
mf.maybe_public_bucket_prefix,
mf.maybe_public_bucket_extension,
mf.maybe_thumbnail_version as `maybe_thumbnail_version: u8`,
cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,
cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,
@@ -60,6 +60,8 @@ fn select_result_fields() -> &'static str {
media_files.public_bucket_directory_hash as maybe_media_file_public_bucket_hash,
media_files.maybe_public_bucket_prefix as maybe_media_file_public_bucket_prefix,
media_files.maybe_public_bucket_extension as maybe_media_file_public_bucket_extension,
media_files.maybe_thumbnail_version as maybe_media_file_thumbnail_version,
media_files.created_at as maybe_media_file_created_at,
media_file_creator.token as maybe_media_file_creator_user_token,
media_file_creator.username as maybe_media_file_creator_username,
media_file_creator.display_name as maybe_media_file_creator_display_name,
@@ -22,6 +22,8 @@ pub struct UserBookmark {
pub maybe_media_file_public_bucket_hash: Option<String>,
pub maybe_media_file_public_bucket_prefix: Option<String>,
pub maybe_media_file_public_bucket_extension: Option<String>,
pub maybe_media_file_thumbnail_version: Option<u8>,
pub maybe_media_file_created_at: Option<DateTime<Utc>>,
pub maybe_media_file_creator_user_token: Option<UserToken>,
pub maybe_media_file_creator_username: Option<String>,
@@ -108,6 +110,8 @@ pub struct RawUserBookmarkRecord {
pub(crate) maybe_media_file_public_bucket_hash: Option<String>,
pub(crate) maybe_media_file_public_bucket_prefix: Option<String>,
pub(crate) maybe_media_file_public_bucket_extension: Option<String>,
pub(crate) maybe_media_file_thumbnail_version: Option<u8>,
pub(crate) maybe_media_file_created_at: Option<DateTime<Utc>>,
pub(crate) maybe_media_file_cover_image_public_bucket_hash: Option<String>,
pub(crate) maybe_media_file_cover_image_public_bucket_prefix: Option<String>,
@@ -148,6 +152,8 @@ impl RawUserBookmarkRecord {
maybe_media_file_public_bucket_hash: self.maybe_media_file_public_bucket_hash,
maybe_media_file_public_bucket_prefix: self.maybe_media_file_public_bucket_prefix,
maybe_media_file_public_bucket_extension: self.maybe_media_file_public_bucket_extension,
maybe_media_file_thumbnail_version: self.maybe_media_file_thumbnail_version,
maybe_media_file_created_at: self.maybe_media_file_created_at,
maybe_media_file_creator_user_token: self.maybe_media_file_creator_user_token,
maybe_media_file_creator_username: self.maybe_media_file_creator_username,
maybe_media_file_creator_display_name: self.maybe_media_file_creator_display_name,
@@ -248,6 +254,8 @@ impl FromRow<'_, MySqlRow> for RawUserBookmarkRecord {
maybe_media_file_public_bucket_hash: row.try_get("maybe_media_file_public_bucket_hash")?,
maybe_media_file_public_bucket_prefix: row.try_get("maybe_media_file_public_bucket_prefix")?,
maybe_media_file_public_bucket_extension: row.try_get("maybe_media_file_public_bucket_extension")?,
maybe_media_file_thumbnail_version: row.try_get("maybe_media_file_thumbnail_version")?,
maybe_media_file_created_at: row.try_get("maybe_media_file_created_at")?,
maybe_media_file_cover_image_public_bucket_hash: row.try_get("maybe_media_file_cover_image_public_bucket_hash")?,
maybe_media_file_cover_image_public_bucket_prefix: row.try_get("maybe_media_file_cover_image_public_bucket_prefix")?,
maybe_media_file_cover_image_public_bucket_extension: row.try_get("maybe_media_file_cover_image_public_bucket_extension")?,
@@ -6,9 +6,10 @@ use log::{error, info};
use tempdir::TempDir;
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
use bucket_paths::path_conventions::video_thumbnail_suffixes::{CURRENT_VIDEO_THUMBNAIL_VERSION, VIDEO_ANIMATED_GIF_THUMBNAIL_SUFFIX, VIDEO_STATIC_JPG_THUMBNAIL_SUFFIX};
use bucket_paths::path_conventions::video_thumbnail_suffixes::{CURRENT_VIDEO_THUMBNAIL_VERSION, VIDEO_ANIMATED_GIF_THUMBNAIL_SUFFIX, VIDEO_ANIMATED_WEBP_THUMBNAIL_SUFFIX, VIDEO_STATIC_JPG_THUMBNAIL_SUFFIX};
use ffmpeg_utils::ffmpeg::ffmpeg_video_first_frame_to_jpg_thumbnail::{ffmpeg_video_first_frame_to_jpg_thumbnail, FfmpegVideoFirstFrameToJpgThumbnailArgs};
use ffmpeg_utils::ffmpeg::ffmpeg_video_gif_preview::{ffmpeg_video_gif_preview, FfmpegVideoGifPreviewArgs};
use ffmpeg_utils::ffmpeg::ffmpeg_video_webp_preview::{ffmpeg_video_webp_preview, FfmpegVideoWebpPreviewArgs};
use mysql_queries::queries::media_files::thumbnails::list_video_media_files_without_thumbnails_for_job::VideoMediaFileWithoutThumbnail;
use mysql_queries::queries::media_files::thumbnails::update_video_media_file_with_thumbnail::update_video_media_file_with_thumbnail;
@@ -24,6 +25,10 @@ pub struct DownloadedFile {
}
/// Download the source video from the bucket, generate thumbnails, and upload them.
///
/// Writes thumbnail version 2 (see `video_thumbnail_suffixes`): a first-frame
/// jpg, an animated webp, and an animated gif. The gif is a dual write kept for
/// ArtCraft desktop on macOS Catalina, which can't render animated webp.
pub async fn process_single_media_file(
deps: &JobDependencies,
media_file: &VideoMediaFileWithoutThumbnail,
@@ -91,7 +96,7 @@ pub async fn process_single_media_file(
info!("Uploaded JPG thumbnail to {}", jpg_object_path);
// Generate gif thumbnail
// Generate gif thumbnail (dual write for macOS Catalina; see `video_thumbnail_suffixes`)
let gif_path = downloaded.temp_dir.path().join("thumbnail.gif");
let stage_started_at = Instant::now();
@@ -123,6 +128,39 @@ pub async fn process_single_media_file(
info!("Uploaded GIF preview to {}", gif_object_path);
// Generate webp thumbnail
let webp_path = downloaded.temp_dir.path().join("thumbnail.webp");
let stage_started_at = Instant::now();
let source = Arc::clone(&downloaded);
let destination = webp_path.clone();
let result = tokio::task::spawn_blocking(move || {
ffmpeg_video_webp_preview(FfmpegVideoWebpPreviewArgs {
input_video_path: &source.file_path,
output_webp_path: &destination,
})
}).await.map_err(anyhow::Error::from).and_then(|result| result);
timing.log_stage("webp_generation", stage_started_at, result.is_ok());
if let Err(err) = result {
error!("Failed to generate WebP preview for {}: {:?}", media_file.token.as_str(), err);
return alert_pager_and_return_err(&deps.pager, "WebP preview generation failed", err.into());
}
info!("Generated WebP preview for {}", media_file.token.as_str());
let webp_object_path = format!("{video_object_path}{VIDEO_ANIMATED_WEBP_THUMBNAIL_SUFFIX}");
let stage_started_at = Instant::now();
// NB: Explicit content type; the jpg and gif uploads above use the bucket default.
let result = deps.public_bucket_client.upload_filename_with_content_type(&webp_object_path, &webp_path, "image/webp").await;
timing.log_stage("webp_upload", stage_started_at, result.is_ok());
if let Err(err) = result {
error!("Failed to upload WebP preview for {}: {:?}", media_file.token.as_str(), err);
return alert_pager_and_return_err(&deps.pager, "WebP preview upload failed", err);
}
info!("Uploaded WebP preview to {}", webp_object_path);
info!("Marking thumbnail job for {:?} done", media_file.token);
// Mark the media file as having a thumbnail in the database.
@@ -21,6 +21,7 @@ use cloud_storage::legacy_bucket_client::LegacyBucketClient;
use concurrency::relaxed_atomic_bool::RelaxedAtomicBool;
use shared_env_var_config::logging::DEFAULT_RUST_LOG;
use errors::AnyhowResult;
use ffmpeg_utils::ffmpeg::webp_ffmpeg_binary::resolve_webp_ffmpeg;
use jobs_common::job_stats::JobStats;
use server_environment::ServerEnvironment;
use shared_env_var_config::mysql::env_get_mysql_connection_string_or_default;
@@ -74,6 +75,11 @@ async fn main() -> AnyhowResult<()> {
)
.ok_or(anyhow!("invalid server environment"))?;
// Thumbnail version 2 writes animated webp previews; fail fast if no ffmpeg can.
// (In development on macOS, this may fall back to Homebrew's ffmpeg-full.)
let webp_ffmpeg = resolve_webp_ffmpeg(server_environment.is_development())?;
info!("Webp previews will use ffmpeg at: {}", webp_ffmpeg.display());
// Bucket setup
let access_key = easyenv::get_env_string_required(ENV_ACCESS_KEY)?;
let secret_key = easyenv::get_env_string_required(ENV_SECRET_KEY)?;
@@ -6,6 +6,7 @@ use server_environment::ServerEnvironment;
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
/// Build the `MediaLinks` + `MediaFileCoverImageDetails` pair for one
/// media-file list row. Shared by every list endpoint that returns
@@ -26,6 +27,7 @@ pub fn build_media_links_and_cover(
media_domain,
server_environment,
&bucket_path,
VideoThumbnailInfo::new(row.maybe_thumbnail_version, row.created_at),
);
let cover_image = MediaFileCoverImageDetails::from_optional_db_fields(
@@ -2,10 +2,10 @@ use crate::http_server::common_responses::media::cdn_link::{get_cdn_host, new_cd
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use artcraft_api_defs::common::responses::media_links::{MediaLinks, VideoPreviews};
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
use bucket_paths::path_conventions::video_thumbnail_suffixes::{VIDEO_ANIMATED_GIF_THUMBNAIL_SUFFIX, VIDEO_STATIC_JPG_THUMBNAIL_SUFFIX};
use bucket_paths::path_conventions::video_thumbnail_suffixes::{animated_preview_format, AnimatedPreviewFormat, VIDEO_STATIC_JPG_THUMBNAIL_SUFFIX};
use chrono::{DateTime, Utc};
use server_environment::ServerEnvironment;
use url::Url;
use utoipa::ToSchema;
// TODO(bt,2024-09-05): Worth reducing the quality at all?
const QUALITY : u8 = 95;
@@ -16,28 +16,48 @@ const QUALITY : u8 = 95;
/// This migrates the constructors to the `artcraft_api_defs` crate
pub struct MediaLinksBuilder {}
/// The `media_files` columns that decide which video preview files exist.
/// Required for every media file (not just videos) so no read site can build
/// video previews without them. See `video_thumbnail_suffixes` in
/// `bucket_paths` for the thumbnail version history.
#[derive(Copy, Clone, Debug)]
pub struct VideoThumbnailInfo {
/// `media_files.maybe_thumbnail_version`
pub maybe_thumbnail_version: Option<u8>,
/// `media_files.created_at` (decides `NULL` versions around the cutover)
pub created_at: DateTime<Utc>,
}
impl VideoThumbnailInfo {
pub fn new(maybe_thumbnail_version: Option<u8>, created_at: DateTime<Utc>) -> Self {
Self { maybe_thumbnail_version, created_at }
}
}
impl MediaLinksBuilder {
pub fn from_media_path_and_env(
domain: MediaDomain,
server_environment: ServerEnvironment,
bucket_path: &MediaFileBucketPath,
thumbnail_info: VideoThumbnailInfo,
) -> MediaLinks {
let rooted_path = bucket_path.get_full_object_path_str();
Self::from_rooted_path_and_env(domain, server_environment, rooted_path)
Self::from_rooted_path_and_env(domain, server_environment, rooted_path, thumbnail_info)
}
pub fn from_rooted_path_and_env(
domain: MediaDomain,
server_environment: ServerEnvironment,
rooted_path: &str,
thumbnail_info: VideoThumbnailInfo,
) -> MediaLinks {
let mut cdn_url = new_cdn_url(domain, server_environment);
cdn_url.set_path(rooted_path);
MediaLinks {
cdn_url,
maybe_thumbnail_template: thumbnail_template(domain, server_environment, rooted_path),
maybe_video_previews: VideoPreviewsBuilder::from_rooted_path(domain, server_environment, rooted_path),
maybe_video_previews: VideoPreviewsBuilder::from_rooted_path(domain, server_environment, rooted_path, thumbnail_info),
}
}
}
@@ -49,30 +69,47 @@ impl VideoPreviewsBuilder {
domain: MediaDomain,
server_environment: ServerEnvironment,
rooted_path: &str,
thumbnail_info: VideoThumbnailInfo,
) -> Option<VideoPreviews> {
if !rooted_path.ends_with(".mp4") {
return None;
}
let animated = PreviewType::Animated(animated_preview_format(
thumbnail_info.maybe_thumbnail_version,
thumbnail_info.created_at.timestamp()));
// Written for every thumbnail version (dual write since version 2).
let catalina_fallback = PreviewType::Animated(AnimatedPreviewFormat::Gif);
Some(VideoPreviews {
still: video_preview(domain, server_environment, rooted_path, PreviewType::Jpg),
animated: video_preview(domain, server_environment, rooted_path, PreviewType::Gif),
still_thumbnail_template: video_preview_thumbnail_template(domain, server_environment, rooted_path, PreviewType::Jpg),
animated_thumbnail_template: video_preview_thumbnail_template(domain, server_environment, rooted_path, PreviewType::Gif),
still: video_preview(domain, server_environment, rooted_path, PreviewType::Still),
animated: video_preview(domain, server_environment, rooted_path, animated),
still_thumbnail_template: video_preview_thumbnail_template(domain, server_environment, rooted_path, PreviewType::Still),
animated_thumbnail_template: video_preview_thumbnail_template(domain, server_environment, rooted_path, animated),
maybe_catalina_fallback_animated_gif: Some(video_preview(domain, server_environment, rooted_path, catalina_fallback)),
maybe_catalina_fallback_animated_gif_thumbnail_template: Some(video_preview_thumbnail_template(domain, server_environment, rooted_path, catalina_fallback)),
})
}
}
#[derive(Copy, Clone)]
enum PreviewType {
Gif,
Jpg
/// First-frame jpg.
Still,
/// Animated gif or webp.
Animated(AnimatedPreviewFormat),
}
/// Returns a jpeg or gif preview of the video.
fn video_preview(media_domain: MediaDomain, server_environment: ServerEnvironment, rooted_path: &str, thumbnail_type: PreviewType) -> Url {
let rooted_path = match thumbnail_type {
PreviewType::Gif => format!("{rooted_path}{VIDEO_ANIMATED_GIF_THUMBNAIL_SUFFIX}"),
PreviewType::Jpg => format!("{rooted_path}{VIDEO_STATIC_JPG_THUMBNAIL_SUFFIX}"),
};
impl PreviewType {
fn suffix(self) -> &'static str {
match self {
PreviewType::Still => VIDEO_STATIC_JPG_THUMBNAIL_SUFFIX,
PreviewType::Animated(format) => format.suffix(),
}
}
}
/// Returns a still or animated preview of the video.
fn video_preview(media_domain: MediaDomain, server_environment: ServerEnvironment, rooted_path: &str, preview_type: PreviewType) -> Url {
let rooted_path = format!("{rooted_path}{}", preview_type.suffix());
let mut url = new_cdn_url(media_domain, server_environment);
url.set_path(&rooted_path);
url
@@ -96,12 +133,15 @@ fn thumbnail_template(media_domain: MediaDomain, server_environment: ServerEnvir
}
/// Returns a thumbnail template for video
fn video_preview_thumbnail_template(media_domain: MediaDomain, server_environment: ServerEnvironment, rooted_path: &str, thumbnail_type: PreviewType) -> String {
///
/// TODO(bt,2026-09-28): Verify Cloudflare's `/cdn-cgi/image` resizing keeps
/// animated webp previews (thumbnail version 2) animated, at a few widths,
/// once real version 2 thumbnails exist in production. Cloudflare's docs
/// didn't confirm animated webp support when this was written. If it doesn't
/// work, serve webp previews without `/cdn-cgi/image` (they're already small).
fn video_preview_thumbnail_template(media_domain: MediaDomain, server_environment: ServerEnvironment, rooted_path: &str, preview_type: PreviewType) -> String {
let host = get_cdn_host(media_domain, server_environment);
let rooted_path = match thumbnail_type {
PreviewType::Gif => format!("{rooted_path}{VIDEO_ANIMATED_GIF_THUMBNAIL_SUFFIX}"),
PreviewType::Jpg => format!("{rooted_path}{VIDEO_STATIC_JPG_THUMBNAIL_SUFFIX}"),
};
let rooted_path = format!("{rooted_path}{}", preview_type.suffix());
// NB: Development doesn't support cdn-cgi image resizing.
match server_environment {
ServerEnvironment::Development => format!("{host}{rooted_path}"),
@@ -113,8 +153,9 @@ fn video_preview_thumbnail_template(media_domain: MediaDomain, server_environmen
#[cfg(test)]
mod tests {
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::{MediaLinksBuilder, VideoThumbnailInfo};
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
use chrono::{DateTime, TimeZone, Utc};
use server_environment::ServerEnvironment;
const DEV_CDN: &str = "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev";
@@ -132,7 +173,7 @@ mod tests {
#[test]
fn wav_file() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.wav");
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.wav", version_1());
assert_eq!(links.cdn_url.as_str(), format!("{PROD_CDN}/foo/bar.wav"));
assert_eq!(links.maybe_thumbnail_template, None);
assert_eq!(links.maybe_video_previews, None);
@@ -140,7 +181,7 @@ mod tests {
#[test]
fn glb_file() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.glb");
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.glb", version_1());
assert_eq!(links.cdn_url.as_str(), format!("{PROD_CDN}/foo/bar.glb"));
assert_eq!(links.maybe_thumbnail_template, None);
assert_eq!(links.maybe_video_previews, None);
@@ -148,7 +189,7 @@ mod tests {
#[test]
fn jpg_image() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.jpg");
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.jpg", version_1());
assert_eq!(links.cdn_url.as_str(), format!("{PROD_CDN}/foo/bar.jpg"));
assert_eq!(links.maybe_thumbnail_template, Some(format!("{PROD_CDN}/cdn-cgi/image/width={{WIDTH}},quality=95/foo/bar.jpg")));
assert_eq!(links.maybe_video_previews, None);
@@ -156,7 +197,7 @@ mod tests {
#[test]
fn mp4_video() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.mp4");
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.mp4", version_1());
assert_eq!(links.cdn_url.as_str(), format!("{PROD_CDN}/foo/bar.mp4"));
assert_eq!(links.maybe_thumbnail_template, None);
let video_previews = links.maybe_video_previews.expect("should have previews");
@@ -169,7 +210,7 @@ mod tests {
#[test]
fn mp4_video_media_path() {
let media_path = MediaFileBucketPath::from_object_hash("t6cnyw4g3e8k7carkk2bvrt6nd3fycjv", Some("storyteller_"), Some(".mp4"));
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path);
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path, version_1());
assert_eq!(links.cdn_url.as_str(), format!("{PROD_CDN}/media/t/6/c/n/y/t6cnyw4g3e8k7carkk2bvrt6nd3fycjv/storyteller_t6cnyw4g3e8k7carkk2bvrt6nd3fycjv.mp4"));
let video_previews = links.maybe_video_previews.expect("should have previews");
assert_eq!(video_previews.still.as_str(), format!("{PROD_CDN}/media/t/6/c/n/y/t6cnyw4g3e8k7carkk2bvrt6nd3fycjv/storyteller_t6cnyw4g3e8k7carkk2bvrt6nd3fycjv.mp4-thumb.jpg"));
@@ -181,7 +222,7 @@ mod tests {
#[test]
fn wav_media_path() {
let media_path = MediaFileBucketPath::from_object_hash("94a27nmbd0bqmd10tg0pp3hz45zytf67", Some("fakeyou_"), Some(".wav"));
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path);
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path, version_1());
assert_eq!(links.cdn_url.as_str(), format!("{PROD_CDN}/media/9/4/a/2/7/94a27nmbd0bqmd10tg0pp3hz45zytf67/fakeyou_94a27nmbd0bqmd10tg0pp3hz45zytf67.wav"));
assert_eq!(links.maybe_thumbnail_template, None);
assert_eq!(links.maybe_video_previews, None);
@@ -190,7 +231,7 @@ mod tests {
#[test]
fn png_media_path() {
let media_path = MediaFileBucketPath::from_object_hash("37mb3gh8fmj85y21thvbv08bzv24atjt", Some("upload_"), Some(".png"));
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path);
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path, version_1());
assert_eq!(links.cdn_url.as_str(), format!("{PROD_CDN}/media/3/7/m/b/3/37mb3gh8fmj85y21thvbv08bzv24atjt/upload_37mb3gh8fmj85y21thvbv08bzv24atjt.png"));
assert_eq!(links.maybe_thumbnail_template, Some(format!("{PROD_CDN}/cdn-cgi/image/width={{WIDTH}},quality=95/media/3/7/m/b/3/37mb3gh8fmj85y21thvbv08bzv24atjt/upload_37mb3gh8fmj85y21thvbv08bzv24atjt.png")));
assert_eq!(links.maybe_video_previews, None);
@@ -204,7 +245,7 @@ mod tests {
#[test]
fn wav_file() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.wav");
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.wav", version_1());
assert_eq!(links.cdn_url.as_str(), format!("{DEV_CDN}/foo/bar.wav"));
assert_eq!(links.maybe_thumbnail_template, None);
assert_eq!(links.maybe_video_previews, None);
@@ -212,7 +253,7 @@ mod tests {
#[test]
fn jpg_image() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.jpg");
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.jpg", version_1());
assert_eq!(links.cdn_url.as_str(), format!("{DEV_CDN}/foo/bar.jpg"));
// NB: Development doesn't support cdn-cgi, so serve the full image.
assert_eq!(links.maybe_thumbnail_template, Some(format!("{DEV_CDN}/foo/bar.jpg")));
@@ -221,7 +262,7 @@ mod tests {
#[test]
fn mp4_video() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.mp4");
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.mp4", version_1());
assert_eq!(links.cdn_url.as_str(), format!("{DEV_CDN}/foo/bar.mp4"));
assert_eq!(links.maybe_thumbnail_template, None);
let video_previews = links.maybe_video_previews.expect("should have previews");
@@ -235,7 +276,7 @@ mod tests {
#[test]
fn mp4_video_media_path() {
let media_path = MediaFileBucketPath::from_object_hash("t6cnyw4g3e8k7carkk2bvrt6nd3fycjv", Some("storyteller_"), Some(".mp4"));
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path);
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path, version_1());
assert_eq!(links.cdn_url.as_str(), format!("{DEV_CDN}/media/t/6/c/n/y/t6cnyw4g3e8k7carkk2bvrt6nd3fycjv/storyteller_t6cnyw4g3e8k7carkk2bvrt6nd3fycjv.mp4"));
let video_previews = links.maybe_video_previews.expect("should have previews");
assert_eq!(video_previews.still.as_str(), format!("{DEV_CDN}/media/t/6/c/n/y/t6cnyw4g3e8k7carkk2bvrt6nd3fycjv/storyteller_t6cnyw4g3e8k7carkk2bvrt6nd3fycjv.mp4-thumb.jpg"));
@@ -258,7 +299,7 @@ mod tests {
#[test]
fn wav_file() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.wav");
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.wav", version_1());
assert_eq!(links.cdn_url.as_str(), format!("{PROD_CDN}/foo/bar.wav"));
assert_eq!(links.maybe_thumbnail_template, None);
assert_eq!(links.maybe_video_previews, None);
@@ -266,7 +307,7 @@ mod tests {
#[test]
fn glb_file() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.glb");
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.glb", version_1());
assert_eq!(links.cdn_url.as_str(), format!("{PROD_CDN}/foo/bar.glb"));
assert_eq!(links.maybe_thumbnail_template, None);
assert_eq!(links.maybe_video_previews, None);
@@ -274,7 +315,7 @@ mod tests {
#[test]
fn jpg_image() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.jpg");
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.jpg", version_1());
assert_eq!(links.cdn_url.as_str(), format!("{PROD_CDN}/foo/bar.jpg"));
assert_eq!(links.maybe_thumbnail_template, Some(format!("{PROD_CDN}/cdn-cgi/image/width={{WIDTH}},quality=95/foo/bar.jpg")));
assert_eq!(links.maybe_video_previews, None);
@@ -282,7 +323,7 @@ mod tests {
#[test]
fn mp4_video() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.mp4");
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.mp4", version_1());
assert_eq!(links.cdn_url.as_str(), format!("{PROD_CDN}/foo/bar.mp4"));
assert_eq!(links.maybe_thumbnail_template, None);
let video_previews = links.maybe_video_previews.expect("should have previews");
@@ -295,7 +336,7 @@ mod tests {
#[test]
fn mp4_video_media_path() {
let media_path = MediaFileBucketPath::from_object_hash("t6cnyw4g3e8k7carkk2bvrt6nd3fycjv", Some("storyteller_"), Some(".mp4"));
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path);
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path, version_1());
assert_eq!(links.cdn_url.as_str(), format!("{PROD_CDN}/media/t/6/c/n/y/t6cnyw4g3e8k7carkk2bvrt6nd3fycjv/storyteller_t6cnyw4g3e8k7carkk2bvrt6nd3fycjv.mp4"));
let video_previews = links.maybe_video_previews.expect("should have previews");
assert_eq!(video_previews.still.as_str(), format!("{PROD_CDN}/media/t/6/c/n/y/t6cnyw4g3e8k7carkk2bvrt6nd3fycjv/storyteller_t6cnyw4g3e8k7carkk2bvrt6nd3fycjv.mp4-thumb.jpg"));
@@ -307,7 +348,7 @@ mod tests {
#[test]
fn wav_media_path() {
let media_path = MediaFileBucketPath::from_object_hash("94a27nmbd0bqmd10tg0pp3hz45zytf67", Some("fakeyou_"), Some(".wav"));
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path);
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path, version_1());
assert_eq!(links.cdn_url.as_str(), format!("{PROD_CDN}/media/9/4/a/2/7/94a27nmbd0bqmd10tg0pp3hz45zytf67/fakeyou_94a27nmbd0bqmd10tg0pp3hz45zytf67.wav"));
assert_eq!(links.maybe_thumbnail_template, None);
assert_eq!(links.maybe_video_previews, None);
@@ -316,7 +357,7 @@ mod tests {
#[test]
fn png_media_path() {
let media_path = MediaFileBucketPath::from_object_hash("37mb3gh8fmj85y21thvbv08bzv24atjt", Some("upload_"), Some(".png"));
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path);
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path, version_1());
assert_eq!(links.cdn_url.as_str(), format!("{PROD_CDN}/media/3/7/m/b/3/37mb3gh8fmj85y21thvbv08bzv24atjt/upload_37mb3gh8fmj85y21thvbv08bzv24atjt.png"));
assert_eq!(links.maybe_thumbnail_template, Some(format!("{PROD_CDN}/cdn-cgi/image/width={{WIDTH}},quality=95/media/3/7/m/b/3/37mb3gh8fmj85y21thvbv08bzv24atjt/upload_37mb3gh8fmj85y21thvbv08bzv24atjt.png")));
assert_eq!(links.maybe_video_previews, None);
@@ -330,7 +371,7 @@ mod tests {
#[test]
fn wav_file() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.wav");
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.wav", version_1());
assert_eq!(links.cdn_url.as_str(), format!("{DEV_CDN}/foo/bar.wav"));
assert_eq!(links.maybe_thumbnail_template, None);
assert_eq!(links.maybe_video_previews, None);
@@ -338,7 +379,7 @@ mod tests {
#[test]
fn jpg_image() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.jpg");
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.jpg", version_1());
assert_eq!(links.cdn_url.as_str(), format!("{DEV_CDN}/foo/bar.jpg"));
assert_eq!(links.maybe_thumbnail_template, Some(format!("{DEV_CDN}/foo/bar.jpg")));
assert_eq!(links.maybe_video_previews, None);
@@ -346,7 +387,7 @@ mod tests {
#[test]
fn mp4_video() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.mp4");
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, "/foo/bar.mp4", version_1());
assert_eq!(links.cdn_url.as_str(), format!("{DEV_CDN}/foo/bar.mp4"));
assert_eq!(links.maybe_thumbnail_template, None);
let video_previews = links.maybe_video_previews.expect("should have previews");
@@ -359,7 +400,7 @@ mod tests {
#[test]
fn mp4_video_media_path() {
let media_path = MediaFileBucketPath::from_object_hash("t6cnyw4g3e8k7carkk2bvrt6nd3fycjv", Some("storyteller_"), Some(".mp4"));
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path);
let links = MediaLinksBuilder::from_media_path_and_env(DOMAIN, ENV, &media_path, version_1());
assert_eq!(links.cdn_url.as_str(), format!("{DEV_CDN}/media/t/6/c/n/y/t6cnyw4g3e8k7carkk2bvrt6nd3fycjv/storyteller_t6cnyw4g3e8k7carkk2bvrt6nd3fycjv.mp4"));
let video_previews = links.maybe_video_previews.expect("should have previews");
assert_eq!(video_previews.still.as_str(), format!("{DEV_CDN}/media/t/6/c/n/y/t6cnyw4g3e8k7carkk2bvrt6nd3fycjv/storyteller_t6cnyw4g3e8k7carkk2bvrt6nd3fycjv.mp4-thumb.jpg"));
@@ -369,4 +410,108 @@ mod tests {
}
}
}
/// Picking the animated preview by thumbnail version. See
/// `video_thumbnail_suffixes` in `bucket_paths` for the version history.
mod thumbnail_versions {
use super::*;
const DOMAIN : MediaDomain = MediaDomain::FakeYou;
const ENV : ServerEnvironment = ServerEnvironment::Production;
const VIDEO : &str = "/foo/bar.mp4";
#[test]
fn version_1_serves_gif() {
assert_animated_suffix(info(Some(1), after_cutover()), "-thumb.gif");
}
#[test]
fn version_2_serves_webp() {
assert_animated_suffix(info(Some(2), before_cutover()), "-thumb.webp");
}
#[test]
fn null_before_cutover_serves_gif() {
assert_animated_suffix(info(None, before_cutover()), "-thumb.gif");
}
#[test]
fn null_at_cutover_serves_webp() {
assert_animated_suffix(info(None, cutover()), "-thumb.webp");
}
#[test]
fn still_is_jpg_for_every_version() {
for thumbnail_info in [info(None, before_cutover()), info(None, cutover()), info(Some(1), cutover()), info(Some(2), cutover())] {
let previews = previews(thumbnail_info);
assert_eq!(previews.still.as_str(), format!("{PROD_CDN}/foo/bar.mp4-thumb.jpg"));
assert_eq!(previews.still_thumbnail_template, format!("{PROD_CDN}/cdn-cgi/image/width={{WIDTH}},quality=95/foo/bar.mp4-thumb.jpg"));
}
}
#[test]
fn catalina_fallback_is_always_the_gif() {
for thumbnail_info in [info(None, before_cutover()), info(None, cutover()), info(Some(1), cutover()), info(Some(2), cutover())] {
let previews = previews(thumbnail_info);
assert_eq!(
previews.maybe_catalina_fallback_animated_gif.as_ref().map(|url| url.as_str()),
Some(format!("{PROD_CDN}/foo/bar.mp4-thumb.gif").as_str()));
assert_eq!(
previews.maybe_catalina_fallback_animated_gif_thumbnail_template,
Some(format!("{PROD_CDN}/cdn-cgi/image/width={{WIDTH}},quality=95/foo/bar.mp4-thumb.gif")));
}
}
#[test]
fn development_webp_is_served_directly() {
let links = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ServerEnvironment::Development, VIDEO, info(Some(2), cutover()));
let previews = links.maybe_video_previews.unwrap();
assert_eq!(previews.animated.as_str(), format!("{DEV_CDN}/foo/bar.mp4-thumb.webp"));
assert_eq!(previews.animated_thumbnail_template, format!("{DEV_CDN}/foo/bar.mp4-thumb.webp"));
assert_eq!(previews.maybe_catalina_fallback_animated_gif_thumbnail_template, Some(format!("{DEV_CDN}/foo/bar.mp4-thumb.gif")));
}
#[test]
fn non_videos_ignore_the_version() {
for path in ["/foo/bar.png", "/foo/bar.gif", "/foo/bar.wav"] {
let version_1 = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, path, info(Some(1), cutover()));
let version_2 = MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, path, info(Some(2), cutover()));
assert_eq!(version_1, version_2);
assert_eq!(version_2.maybe_video_previews, None);
}
}
fn assert_animated_suffix(thumbnail_info: VideoThumbnailInfo, suffix: &str) {
let previews = previews(thumbnail_info);
assert_eq!(previews.animated.as_str(), format!("{PROD_CDN}/foo/bar.mp4{suffix}"));
assert_eq!(previews.animated_thumbnail_template, format!("{PROD_CDN}/cdn-cgi/image/width={{WIDTH}},quality=95/foo/bar.mp4{suffix}"));
}
fn previews(thumbnail_info: VideoThumbnailInfo) -> artcraft_api_defs::common::responses::media_links::VideoPreviews {
MediaLinksBuilder::from_rooted_path_and_env(DOMAIN, ENV, VIDEO, thumbnail_info)
.maybe_video_previews
.expect("mp4 should have previews")
}
fn cutover() -> DateTime<Utc> {
Utc.with_ymd_and_hms(2026, 10, 10, 0, 0, 0).unwrap()
}
fn before_cutover() -> DateTime<Utc> {
Utc.with_ymd_and_hms(2026, 10, 9, 23, 59, 59).unwrap()
}
fn after_cutover() -> DateTime<Utc> {
Utc.with_ymd_and_hms(2027, 1, 1, 0, 0, 0).unwrap()
}
}
/// Version 1 (gif) thumbnail info, for the path and CDN tests above.
fn version_1() -> VideoThumbnailInfo {
info(Some(1), Utc.with_ymd_and_hms(2026, 4, 1, 0, 0, 0).unwrap())
}
fn info(maybe_thumbnail_version: Option<u8>, created_at: DateTime<Utc>) -> VideoThumbnailInfo {
VideoThumbnailInfo { maybe_thumbnail_version, created_at }
}
}
@@ -16,6 +16,7 @@ use tokens::tokens::media_files::MediaFileToken;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::state::server_state::ServerState;
@@ -99,5 +100,5 @@ fn resolve_media_links(
record.maybe_public_bucket_prefix.as_deref(),
record.maybe_public_bucket_extension.as_deref(),
);
Some(MediaLinksBuilder::from_media_path_and_env(media_domain, server_environment, &path))
Some(MediaLinksBuilder::from_media_path_and_env(media_domain, server_environment, &path, VideoThumbnailInfo::new(record.maybe_thumbnail_version, record.created_at)))
}
@@ -15,6 +15,7 @@ use tokens::tokens::media_files::MediaFileToken;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::user_lookup::api_or_web_session::require_api_or_web_session::require_api_or_web_session;
use crate::state::server_state::ServerState;
@@ -121,5 +122,5 @@ fn resolve_media_links(
record.maybe_public_bucket_prefix.as_deref(),
record.maybe_public_bucket_extension.as_deref(),
);
Some(MediaLinksBuilder::from_media_path_and_env(media_domain, server_environment, &path))
Some(MediaLinksBuilder::from_media_path_and_env(media_domain, server_environment, &path, VideoThumbnailInfo::new(record.maybe_thumbnail_version, record.created_at)))
}
@@ -15,6 +15,7 @@ use tokens::tokens::media_files::MediaFileToken;
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
/// Gather every media-file token referenced by the given folder rows
/// (the four `last_media_*` slots + the custom cover) and batch-fetch
@@ -129,6 +130,7 @@ fn build_folder_thumbnail(
media_domain,
server_environment,
&bucket_path,
VideoThumbnailInfo::new(row.maybe_thumbnail_version, row.created_at),
);
// Only video media files have previews; `MediaLinksBuilder` returns
@@ -139,6 +141,8 @@ fn build_folder_thumbnail(
animated: previews.animated,
still_thumbnail_template: previews.still_thumbnail_template,
animated_thumbnail_template: previews.animated_thumbnail_template,
maybe_catalina_fallback_animated_gif: previews.maybe_catalina_fallback_animated_gif,
maybe_catalina_fallback_animated_gif_thumbnail_template: previews.maybe_catalina_fallback_animated_gif_thumbnail_template,
});
FolderThumbnail {
@@ -155,6 +159,7 @@ fn build_folder_thumbnail(
mod tests {
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_type::MediaFileType;
use chrono::{TimeZone, Utc};
use super::*;
@@ -163,7 +168,7 @@ mod tests {
#[test]
fn mp4_video_gets_video_previews() {
let thumbnail = build_thumbnail_for_extension(MediaFileClass::Video, MediaFileType::Mp4, ".mp4");
let thumbnail = build_thumbnail_for_extension(MediaFileClass::Video, MediaFileType::Mp4, ".mp4", Some(1));
assert_eq!(thumbnail.maybe_thumbnail_template, None);
@@ -182,9 +187,25 @@ mod tests {
format!("{PROD_CDN}/cdn-cgi/image/width={{WIDTH}},quality=95/media/t/6/c/n/y/{OBJECT_HASH}/storyteller_{OBJECT_HASH}.mp4-thumb.gif"));
}
#[test]
fn version_2_mp4_gets_webp_preview_and_catalina_gif_fallback() {
let thumbnail = build_thumbnail_for_extension(MediaFileClass::Video, MediaFileType::Mp4, ".mp4", Some(2));
let previews = thumbnail.maybe_video_previews.expect("video should have previews");
assert_eq!(
previews.animated.as_str(),
format!("{PROD_CDN}/media/t/6/c/n/y/{OBJECT_HASH}/storyteller_{OBJECT_HASH}.mp4-thumb.webp"));
assert_eq!(
previews.maybe_catalina_fallback_animated_gif.as_ref().map(|url| url.as_str()),
Some(format!("{PROD_CDN}/media/t/6/c/n/y/{OBJECT_HASH}/storyteller_{OBJECT_HASH}.mp4-thumb.gif").as_str()));
assert_eq!(
previews.maybe_catalina_fallback_animated_gif_thumbnail_template,
Some(format!("{PROD_CDN}/cdn-cgi/image/width={{WIDTH}},quality=95/media/t/6/c/n/y/{OBJECT_HASH}/storyteller_{OBJECT_HASH}.mp4-thumb.gif")));
}
#[test]
fn png_image_gets_thumbnail_template_but_no_video_previews() {
let thumbnail = build_thumbnail_for_extension(MediaFileClass::Image, MediaFileType::Png, ".png");
let thumbnail = build_thumbnail_for_extension(MediaFileClass::Image, MediaFileType::Png, ".png", None);
assert_eq!(
thumbnail.maybe_thumbnail_template,
@@ -196,6 +217,7 @@ mod tests {
media_class: MediaFileClass,
media_type: MediaFileType,
extension: &str,
maybe_thumbnail_version: Option<u8>,
) -> FolderThumbnail {
let row = MediaFileThumbnailRow {
token: MediaFileToken::new_from_str("m_test"),
@@ -204,6 +226,8 @@ mod tests {
public_bucket_directory_hash: OBJECT_HASH.to_string(),
maybe_public_bucket_prefix: Some("storyteller_".to_string()),
maybe_public_bucket_extension: Some(extension.to_string()),
maybe_thumbnail_version,
created_at: Utc.with_ymd_and_hms(2026, 4, 1, 0, 0, 0).unwrap(),
};
build_folder_thumbnail(row, MediaDomain::FakeYou, ServerEnvironment::Production)
@@ -31,6 +31,7 @@ use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::user_lookup::user_session::require_user_session::require_user_session;
use crate::state::server_state::ServerState;
@@ -203,6 +204,7 @@ fn folder_media_file_row_to_list_item(
media_domain,
server_environment,
&bucket_path,
VideoThumbnailInfo::new(row.maybe_thumbnail_version, row.created_at),
);
let cover_image = MediaFileCoverImageDetails::from_optional_db_fields(
@@ -50,6 +50,7 @@ use tokens::tokens::media_files::MediaFileToken;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::util::http_download_url_to_tempfile::http_download_url_to_tempfile;
use crate::util::lookup::lookup_media_files_as_cdn_url_list_and_map::apply_media_cdn_override;
@@ -143,7 +144,8 @@ pub async fn fetch_reference_video_sources(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_environment,
&bucket_path);
&bucket_path,
VideoThumbnailInfo::new(file.maybe_thumbnail_version, file.created_at));
let cdn_url = apply_media_cdn_override(
&media_links.cdn_url,
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type;
@@ -120,7 +121,8 @@ pub async fn remove_image_background_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&bucket_path);
&bucket_path,
VideoThumbnailInfo::new(media_file.maybe_thumbnail_version, media_file.created_at));
info!("Fal webhook URL: {}", server_state.inference_providers.fal.webhook_url);
@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::billing::wallets::temporary_test_wallet_deduction::temporary_test_wallet_deduction;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
@@ -131,7 +132,8 @@ pub async fn flux_pro_kontext_max_edit_image_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path);
&public_bucket_path,
VideoThumbnailInfo::new(image_media_file.maybe_thumbnail_version, image_media_file.created_at));
media_links.cdn_url.to_string()
};
@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
@@ -127,7 +128,8 @@ pub async fn gemini_25_flash_edit_image_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path);
&public_bucket_path,
VideoThumbnailInfo::new(file.maybe_thumbnail_version, file.created_at));
media_links.cdn_url.to_string()
})
@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
@@ -129,7 +130,8 @@ pub async fn gpt_image_1_edit_image_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path);
&public_bucket_path,
VideoThumbnailInfo::new(file.maybe_thumbnail_version, file.created_at));
media_links.cdn_url.to_string()
})
@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
@@ -124,7 +125,8 @@ pub async fn qwen_edit_image_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path);
&public_bucket_path,
VideoThumbnailInfo::new(image_media_file.maybe_thumbnail_version, image_media_file.created_at));
media_links.cdn_url.to_string()
};
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
@@ -123,7 +124,8 @@ pub async fn seededit_3_edit_image_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path);
&public_bucket_path,
VideoThumbnailInfo::new(image_media_file.maybe_thumbnail_version, image_media_file.created_at));
media_links.cdn_url.to_string()
};
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
@@ -135,7 +136,8 @@ pub async fn flux_dev_juggernaut_inpaint_image_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path);
&public_bucket_path,
VideoThumbnailInfo::new(image_media_file.maybe_thumbnail_version, image_media_file.created_at));
media_links.cdn_url.to_string()
};
@@ -149,7 +151,8 @@ pub async fn flux_dev_juggernaut_inpaint_image_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path);
&public_bucket_path,
VideoThumbnailInfo::new(mask_media_file.maybe_thumbnail_version, mask_media_file.created_at));
media_links.cdn_url.to_string()
};
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
@@ -135,7 +136,8 @@ pub async fn flux_pro_1_inpaint_image_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path);
&public_bucket_path,
VideoThumbnailInfo::new(image_media_file.maybe_thumbnail_version, image_media_file.created_at));
media_links.cdn_url.to_string()
};
@@ -149,7 +151,8 @@ pub async fn flux_pro_1_inpaint_image_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path);
&public_bucket_path,
VideoThumbnailInfo::new(mask_media_file.maybe_thumbnail_version, mask_media_file.created_at));
media_links.cdn_url.to_string()
};
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type;
@@ -128,7 +129,8 @@ pub async fn generate_hunyuan_2_0_image_to_3d_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&bucket_path);
&bucket_path,
VideoThumbnailInfo::new(media_file.maybe_thumbnail_version, media_file.created_at));
info!("Fal webhook URL: {}", server_state.inference_providers.fal.webhook_url);
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type;
@@ -127,7 +128,8 @@ pub async fn generate_hunyuan_2_1_image_to_3d_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&bucket_path);
&bucket_path,
VideoThumbnailInfo::new(media_file.maybe_thumbnail_version, media_file.created_at));
info!("Fal webhook URL: {}", server_state.inference_providers.fal.webhook_url);
@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
@@ -130,7 +131,8 @@ pub async fn generate_kling_2_1_master_video_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&bucket_path);
&bucket_path,
VideoThumbnailInfo::new(media_file.maybe_thumbnail_version, media_file.created_at));
info!("Fal webhook URL: {}", server_state.inference_providers.fal.webhook_url);
@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::analytics::log_browser_session_handler::LogBrowserSessionError;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
@@ -137,7 +138,8 @@ pub async fn generate_veo_2_image_to_video_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&bucket_path);
&bucket_path,
VideoThumbnailInfo::new(media_file.maybe_thumbnail_version, media_file.created_at));
info!("Fal webhook URL: {}", server_state.inference_providers.fal.webhook_url);
@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::analytics::log_browser_session_handler::LogBrowserSessionError;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
@@ -129,7 +130,8 @@ pub async fn generate_veo_3_fast_image_to_video_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&bucket_path);
&bucket_path,
VideoThumbnailInfo::new(media_file.maybe_thumbnail_version, media_file.created_at));
info!("Fal webhook URL: {}", server_state.inference_providers.fal.webhook_url);
@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::analytics::log_browser_session_handler::LogBrowserSessionError;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
@@ -132,7 +133,8 @@ pub async fn generate_veo_3_image_to_video_handler(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&bucket_path);
&bucket_path,
VideoThumbnailInfo::new(media_file.maybe_thumbnail_version, media_file.created_at));
info!("Fal webhook URL: {}", server_state.inference_providers.fal.webhook_url);
@@ -4,6 +4,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::inference_job::utils::estimates::estimate_job_progress::estimate_job_progress;
use crate::http_server::endpoints::inference_job::utils::extractors::extract_lipsync_details::extract_lipsync_details;
use crate::http_server::endpoints::inference_job::utils::extractors::extract_live_portrait_details::extract_live_portrait_details;
@@ -219,6 +220,8 @@ fn db_record_to_response_payload(
server_environment: ServerEnvironment,
media_domain: MediaDomain,
) -> BatchInferenceJobStatusResponsePayload {
// NB: Copied out so the result closure below doesn't borrow `record` (edition 2018).
let job_created_at = record.created_at;
let inference_category = record.request_details.inference_category;
// NB: Fail open. We don't want to fail the request if we can't extract the args.
@@ -326,7 +329,12 @@ fn db_record_to_response_payload(
media_links: MediaLinksBuilder::from_rooted_path_and_env(
media_domain,
server_environment,
&public_bucket_media_path
&public_bucket_media_path,
VideoThumbnailInfo::new(
result_details.maybe_media_file_thumbnail_version,
// NB: Non-media-file results (legacy tts/vc) have no media file row; they're
// never mp4s, so the job's creation time is only a placeholder for them.
result_details.maybe_media_file_created_at.unwrap_or(job_created_at))
),
maybe_public_bucket_media_path: Some(public_bucket_media_path),
maybe_successfully_completed_at: result_details.maybe_successfully_completed_at,
@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::inference_job::utils::estimates::estimate_job_progress::estimate_job_progress;
use crate::http_server::endpoints::tts::enqueue_infer_tts_handler::enqueue_infer_tts_handler::SYNTHETIC_INFERENCE_JOB_TOKEN;
use crate::http_server::endpoints::inference_job::utils::extractors::extract_lipsync_details::extract_lipsync_details;
@@ -242,6 +243,8 @@ fn record_to_payload(
server_environment: ServerEnvironment,
media_domain: MediaDomain,
) -> InferenceJobStatusResponsePayload {
// NB: Copied out so the result closure below doesn't borrow `record` (edition 2018).
let job_created_at = record.created_at;
let inference_category = record.request_details.inference_category;
// NB: Fail open. We don't want to fail the request if we can't extract the args.
@@ -349,7 +352,12 @@ fn record_to_payload(
media_links: MediaLinksBuilder::from_rooted_path_and_env(
media_domain,
server_environment,
&public_bucket_media_path
&public_bucket_media_path,
VideoThumbnailInfo::new(
result_details.maybe_media_file_thumbnail_version,
// NB: Non-media-file results (legacy tts/vc) have no media file row; they're
// never mp4s, so the job's creation time is only a placeholder for them.
result_details.maybe_media_file_created_at.unwrap_or(job_created_at))
),
maybe_public_bucket_media_path: Some(public_bucket_media_path),
maybe_successfully_completed_at: result_details.maybe_successfully_completed_at,
@@ -4,6 +4,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::inference_job::utils::estimates::estimate_job_progress::estimate_job_progress;
use crate::http_server::endpoints::inference_job::utils::extractors::extract_lipsync_details::extract_lipsync_details;
use crate::http_server::endpoints::inference_job::utils::extractors::extract_live_portrait_details::extract_live_portrait_details;
@@ -165,6 +166,8 @@ fn db_record_to_response_payload(
server_environment: ServerEnvironment,
media_domain: MediaDomain,
) -> ListSessionJobsItem {
// NB: Copied out so the result closure below doesn't borrow `record` (edition 2018).
let job_created_at = record.created_at;
let inference_category = record.request_details.inference_category;
// NB: Fail open. We don't want to fail the request if we can't extract the args.
@@ -292,7 +295,12 @@ fn db_record_to_response_payload(
media_links: MediaLinksBuilder::from_rooted_path_and_env(
media_domain,
server_environment,
&public_bucket_media_path
&public_bucket_media_path,
VideoThumbnailInfo::new(
result_details.maybe_media_file_thumbnail_version,
// NB: Non-media-file results (legacy tts/vc) have no media file row; they're
// never mp4s, so the job's creation time is only a placeholder for them.
result_details.maybe_media_file_created_at.unwrap_or(job_created_at))
),
maybe_public_bucket_media_path: Some(public_bucket_media_path),
maybe_successfully_completed_at: result_details.maybe_successfully_completed_at,
@@ -25,6 +25,7 @@ use mysql_queries::queries::prompts::get_prompt::get_prompt_from_connection;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::web_utils::require_internal_api_key::require_internal_api_key;
use crate::state::server_state::ServerState;
@@ -170,7 +171,8 @@ async fn build_prompt_details(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&bucket_path);
&bucket_path,
VideoThumbnailInfo::new(item.maybe_thumbnail_version, item.created_at));
MinimaxJobMediaReference {
media_file_token: item.media_token,
@@ -26,6 +26,7 @@ use utoipa::{IntoParams, ToSchema};
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats;
use crate::http_server::common_responses::user_details_lite::UserDetailsLight;
use crate::http_server::endpoints::media_files::common_responses::live_portrait::MediaFileLivePortraitDetails;
@@ -284,7 +285,8 @@ pub async fn batch_get_media_files_handler(
media_links: MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path
&public_bucket_path,
VideoThumbnailInfo::new(result.maybe_thumbnail_version, result.created_at)
),
public_bucket_path: public_bucket_path
.get_full_object_path_str()
@@ -4,6 +4,7 @@ use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats;
use crate::http_server::common_responses::user_details_lite::UserDetailsLight;
use crate::http_server::endpoints::media_files::common_responses::live_portrait::MediaFileLivePortraitDetails;
@@ -314,7 +315,11 @@ async fn modern_media_file_lookup(
maybe_engine_extension,
maybe_batch_token: result.maybe_batch_token,
maybe_scene_source_media_file_token: result.maybe_scene_source_media_file_token,
media_links: MediaLinksBuilder::from_media_path_and_env(media_domain, server_state.server_environment, &public_bucket_path),
media_links: MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path,
VideoThumbnailInfo::new(result.maybe_thumbnail_version, result.created_at)),
public_bucket_url: bucket_url_from_media_path(&public_bucket_path, media_domain, server_state.server_environment)
.map_err(|err| {
warn!("error creating URL: {:?}", err);
@@ -451,7 +456,13 @@ async fn emulate_media_file_with_legacy_tts_result_lookup(
maybe_engine_extension: None,
maybe_batch_token: None,
maybe_scene_source_media_file_token: None,
media_links: MediaLinksBuilder::from_rooted_path_and_env(media_domain, server_state.server_environment, &public_bucket_path),
// NB: Legacy TTS results are wav files (never videos) in a table without a
// thumbnail version, so there are no video previews to version.
media_links: MediaLinksBuilder::from_rooted_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path,
VideoThumbnailInfo::new(None, result.created_at)),
public_bucket_url: bucket_url_from_str_path(&public_bucket_path, media_domain, server_state.server_environment)
.map_err(|err| {
warn!("error creating URL: {:?}", err);
@@ -18,6 +18,7 @@ use utoipa::ToSchema;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_file_cover_image_details_builder::MediaFileCoverImageDetailsBuilder;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::user_lookup::api_or_web_session::require_api_or_web_session::require_api_or_web_session;
use crate::state::server_state::ServerState;
@@ -93,6 +94,7 @@ pub async fn list_media_files_by_job_handler(
media_domain,
server_state.server_environment,
&public_bucket_path,
VideoThumbnailInfo::new(record.maybe_thumbnail_version, record.created_at),
),
cover_image: MediaFileCoverImageDetailsBuilder::from_optional_db_fields(
&record.token,
@@ -16,6 +16,7 @@ use mysql_queries::queries::media_files::list::list_session_media_files_by_class
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_file_cover_image_details_builder::MediaFileCoverImageDetailsBuilder;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::common_responses::user_details_lite_builder::UserDetailsLightBuilder;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::user_lookup::user_session::require_user_session::require_user_session;
@@ -102,6 +103,7 @@ pub async fn list_session_media_files_of_class(
media_domain,
server_state.server_environment,
&public_bucket_path,
VideoThumbnailInfo::new(record.maybe_thumbnail_version, record.created_at),
),
cover_image: MediaFileCoverImageDetailsBuilder::from_optional_db_fields(
&record.token,
@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_file_cover_image_details_builder::MediaFileCoverImageDetailsBuilder;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::common_responses::user_details_lite_builder::UserDetailsLightBuilder;
use crate::http_server::endpoints::media_files::get::get_media_file_handler::GetMediaFilePathInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
@@ -91,7 +92,8 @@ pub async fn list_batch_generated_redux_media_files_handler(
media_links: MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path
&public_bucket_path,
VideoThumbnailInfo::new(result.maybe_thumbnail_version, result.created_at)
),
cover_image: MediaFileCoverImageDetailsBuilder::from_optional_db_fields(
&result.token,
@@ -24,6 +24,7 @@ use utoipa::{IntoParams, ToSchema};
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::common_responses::media_file_origin_details::MediaFileOriginDetails;
use crate::http_server::common_responses::pagination_cursors::PaginationCursors;
use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats;
@@ -261,7 +262,8 @@ pub async fn list_featured_media_files_handler(
media_links: MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path
&public_bucket_path,
VideoThumbnailInfo::new(m.maybe_thumbnail_version, m.created_at)
),
public_bucket_path: public_bucket_path
.get_full_object_path_str()
@@ -24,6 +24,7 @@ use utoipa::{IntoParams, ToSchema};
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::common_responses::media_file_origin_details::MediaFileOriginDetails;
use crate::http_server::common_responses::pagination_page::PaginationPage;
use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats;
@@ -246,7 +247,8 @@ pub async fn list_media_files_by_batch_token_handler(
media_links: MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path
&public_bucket_path,
VideoThumbnailInfo::new(record.maybe_thumbnail_version, record.created_at)
),
public_bucket_path: public_bucket_path
.get_full_object_path_str()
@@ -23,6 +23,7 @@ use utoipa::{IntoParams, ToSchema};
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::common_responses::media_file_origin_details::MediaFileOriginDetails;
use crate::http_server::common_responses::pagination_cursors::PaginationCursors;
use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats;
@@ -300,7 +301,8 @@ pub async fn list_media_files_handler(
media_links: MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path
&public_bucket_path,
VideoThumbnailInfo::new(record.maybe_thumbnail_version, record.created_at)
),
public_bucket_path: public_bucket_path
.get_full_object_path_str()
@@ -22,6 +22,7 @@ use utoipa::ToSchema;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::common_responses::media_file_origin_details::MediaFileOriginDetails;
use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats;
use crate::http_server::common_responses::user_details_lite::UserDetailsLight;
@@ -197,7 +198,8 @@ pub async fn list_pinned_media_files_handler(
media_links: MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path
&public_bucket_path,
VideoThumbnailInfo::new(m.maybe_thumbnail_version, m.created_at)
),
public_bucket_path: public_bucket_path
.get_full_object_path_str()
@@ -13,6 +13,7 @@ use mysql_queries::queries::media_files::list::list_session_project_media_files:
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_file_cover_image_details_builder::MediaFileCoverImageDetailsBuilder;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::common_responses::user_details_lite_builder::UserDetailsLightBuilder;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::user_lookup::user_session::require_user_session::require_user_session;
@@ -111,6 +112,7 @@ pub async fn list_session_project_media_files_handler(
media_domain,
server_state.server_environment,
&public_bucket_path,
VideoThumbnailInfo::new(record.maybe_thumbnail_version, record.created_at),
),
cover_image: MediaFileCoverImageDetailsBuilder::from_optional_db_fields(
&record.token,
@@ -14,6 +14,7 @@ use server_environment::ServerEnvironment;
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::common_responses::media_file_origin_details::{MediaFileModelDetails, MediaFileOriginDetails};
use crate::http_server::web_utils::bucket_urls::bucket_url_string_from_media_path::bucket_url_string_from_media_path;
@@ -79,7 +80,11 @@ pub(super) fn build_user_media_file_response(args: UserMediaFileResponseArgs) ->
origin_product_category: record.origin_product_category,
maybe_origin_model_type: record.maybe_origin_model_type,
maybe_origin_model_token: record.maybe_origin_model_token,
media_links: MediaLinksBuilder::from_media_path_and_env(media_domain, server_environment, &public_bucket_path),
media_links: MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_environment,
&public_bucket_path,
VideoThumbnailInfo::new(record.maybe_thumbnail_version, record.created_at)),
maybe_prompt_token: record.maybe_prompt_token,
public_bucket_path: public_bucket_path.get_full_object_path_str().to_string(),
public_bucket_url: bucket_url_string_from_media_path(&public_bucket_path, media_domain, server_environment),
@@ -20,6 +20,7 @@ use utoipa::{IntoParams, ToSchema};
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::common_responses::user_details_lite::UserDetailsLight;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::endpoints::media_files::helpers::get_scoped_engine_categories::get_scoped_engine_categories;
@@ -28,6 +29,7 @@ use crate::http_server::endpoints::media_files::helpers::get_scoped_media_types:
use crate::http_server::web_utils::bucket_urls::bucket_url_string_from_media_path::bucket_url_string_from_media_path;
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
use crate::state::server_state::ServerState;
use crate::util::lookup::lookup_video_thumbnail_info_by_tokens::lookup_video_thumbnail_info_by_tokens;
use crate::util::allowed_explore_media_access::allowed_explore_media_access;
#[derive(Deserialize, ToSchema, IntoParams)]
@@ -183,6 +185,13 @@ pub async fn search_featured_media_files_handler(
}
};
// NB: Search documents don't carry the thumbnail version, so look it up for videos.
let video_tokens = results.iter()
.filter(|result| result.maybe_public_bucket_extension.as_deref() == Some(".mp4"))
.map(|result| result.token.clone())
.collect::<Vec<_>>();
let thumbnail_infos = lookup_video_thumbnail_info_by_tokens(&video_tokens, &server_state.mysql_pool).await;
let media_domain = get_media_domain(&http_request);
let results = results.into_iter()
@@ -200,7 +209,10 @@ pub async fn search_featured_media_files_handler(
media_links: MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path
&public_bucket_path,
thumbnail_infos.get(&result.token)
.copied()
.unwrap_or_else(|| VideoThumbnailInfo::new(None, result.created_at))
),
public_bucket_path: public_bucket_path
.get_full_object_path_str()
@@ -21,6 +21,7 @@ use utoipa::{IntoParams, ToSchema};
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::common_responses::user_details_lite::UserDetailsLight;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::endpoints::media_files::helpers::get_scoped_engine_categories::get_scoped_engine_categories;
@@ -29,6 +30,7 @@ use crate::http_server::endpoints::media_files::helpers::get_scoped_media_types:
use crate::http_server::web_utils::bucket_urls::bucket_url_string_from_media_path::bucket_url_string_from_media_path;
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
use crate::state::server_state::ServerState;
use crate::util::lookup::lookup_video_thumbnail_info_by_tokens::lookup_video_thumbnail_info_by_tokens;
use crate::util::allowed_explore_media_access::allowed_explore_media_access;
#[derive(Deserialize, ToSchema, IntoParams)]
@@ -210,6 +212,13 @@ pub async fn search_session_media_files_handler(
}
};
// NB: Search documents don't carry the thumbnail version, so look it up for videos.
let video_tokens = results.iter()
.filter(|result| result.maybe_public_bucket_extension.as_deref() == Some(".mp4"))
.map(|result| result.token.clone())
.collect::<Vec<_>>();
let thumbnail_infos = lookup_video_thumbnail_info_by_tokens(&video_tokens, &server_state.mysql_pool).await;
let media_domain = get_media_domain(&http_request);
let results = results.into_iter()
@@ -227,7 +236,10 @@ pub async fn search_session_media_files_handler(
media_links: MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
&public_bucket_path
&public_bucket_path,
thumbnail_infos.get(&result.token)
.copied()
.unwrap_or_else(|| VideoThumbnailInfo::new(None, result.created_at))
),
public_bucket_path: public_bucket_path
.get_full_object_path_str()
@@ -9,6 +9,7 @@ use server_environment::ServerEnvironment;
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::inference_job::utils::estimates::estimate_job_progress::estimate_job_progress;
use crate::http_server::endpoints::inference_job::utils::extractors::extract_polymorphic_inference_args::extract_polymorphic_inference_args;
use crate::http_server::web_utils::filter_model_name::maybe_filter_model_name;
@@ -23,6 +24,8 @@ pub fn record_to_payload(
server_environment: ServerEnvironment,
media_domain: MediaDomain,
) -> OmniApiJobStatusPayload {
// NB: Copied out so the result closure below doesn't borrow `record` (edition 2018).
let job_created_at = record.created_at;
let inference_category = record.request_details.inference_category;
// NB: Fail open. We don't want to fail the request if we can't extract the args.
@@ -117,6 +120,11 @@ pub fn record_to_payload(
media_domain,
server_environment,
&public_bucket_media_path,
VideoThumbnailInfo::new(
result_details.maybe_media_file_thumbnail_version,
// NB: Non-media-file results (legacy tts/vc) have no media file row; they're
// never mp4s, so the job's creation time is only a placeholder for them.
result_details.maybe_media_file_created_at.unwrap_or(job_created_at))
),
maybe_successfully_completed_at: result_details.maybe_successfully_completed_at,
}
@@ -17,6 +17,7 @@ use tokens::tokens::prompts::PromptToken;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::state::server_state::ServerState;
@@ -112,6 +113,7 @@ pub async fn batch_get_prompts_handler(
media_domain,
server_state.server_environment,
&bucket_path,
VideoThumbnailInfo::new(item.maybe_thumbnail_version, item.created_at),
),
};
@@ -14,6 +14,7 @@ use mysql_queries::queries::prompts::get_prompt::get_prompt_from_connection;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::state::server_state::ServerState;
@@ -136,6 +137,7 @@ pub async fn get_prompt_handler(
media_domain,
server_state.server_environment,
&bucket_path,
VideoThumbnailInfo::new(item.maybe_thumbnail_version, item.created_at),
),
})
}).collect::<Vec<GetPromptImageContextItem>>();
@@ -8,6 +8,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::common_responses::media::weights_cover_image_details::WeightsCoverImageDetails;
use crate::http_server::common_responses::pagination_page::PaginationPage;
use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats;
@@ -228,11 +229,14 @@ pub async fn list_user_bookmarks_for_user_handler(
user_bookmark.maybe_media_file_public_bucket_extension.as_deref())
});
// NB: The bucket hash and created_at come from the same joined media file row.
let maybe_media_file_media_links = maybe_media_file_bucket_path.as_ref()
.map(|bucket_path| MediaLinksBuilder::from_media_path_and_env(
media_domain,
.zip(user_bookmark.maybe_media_file_created_at)
.map(|(bucket_path, created_at)| MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_state.server_environment,
bucket_path));
bucket_path,
VideoThumbnailInfo::new(user_bookmark.maybe_media_file_thumbnail_version, created_at)));
let mut maybe_media_file_cover = None;
let mut maybe_model_weight_cover = None;
@@ -1,5 +1,6 @@
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use actix_web::HttpRequest;
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
@@ -62,7 +63,8 @@ pub async fn lookup_image_urls_as_map(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_environment,
&public_bucket_path);
&public_bucket_path,
VideoThumbnailInfo::new(file.maybe_thumbnail_version, file.created_at));
(file.token, media_links.cdn_url.to_string())
})
@@ -1,5 +1,6 @@
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use actix_web::HttpRequest;
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
@@ -62,7 +63,8 @@ pub async fn lookup_image_urls_as_optional_list(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_environment,
&public_bucket_path);
&public_bucket_path,
VideoThumbnailInfo::new(file.maybe_thumbnail_version, file.created_at));
media_links.cdn_url.to_string()
})
@@ -1,5 +1,6 @@
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::media::media_links_builder::VideoThumbnailInfo;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use actix_web::HttpRequest;
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
@@ -63,7 +64,8 @@ pub async fn lookup_media_file_urls_as_map(
let media_links = MediaLinksBuilder::from_media_path_and_env(
media_domain,
server_environment,
&public_bucket_path);
&public_bucket_path,
VideoThumbnailInfo::new(file.maybe_thumbnail_version, file.created_at));
(file.token, media_links.cdn_url)
})
Loaded 100 of 190 files, more files were not shown because too many files have changed in this diff. Show more