feat(drafts): reuse localRefPath and encode compressed flag for videos
Update video serialization to: - Reuse existing localRefPath when editing a draft (avoid creating new files) - Encode compressed flag in localRefPath: video:mimeType:c:id.ext Add parseVideoLocalRef() to extract mimeType and compressed flag from localRefPath, with backwards compatibility for legacy formats. This enables skipping re-compression when restoring drafts that already have a compressed video stored. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,21 +31,50 @@ export type RestoredVideo = {
|
|||||||
altText: string
|
altText: string
|
||||||
mimeType: string
|
mimeType: string
|
||||||
localRefPath: string
|
localRefPath: string
|
||||||
|
/** Whether the stored video is already compressed (can skip compression step) */
|
||||||
|
compressed: boolean
|
||||||
captions: Array<{lang: string; content: string}>
|
captions: Array<{lang: string; content: string}>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse mime type from video localRefPath.
|
* Parse video metadata from localRefPath.
|
||||||
* Format: `video:${mimeType}:${nanoid()}` (new) or `video:${nanoid()}` (legacy)
|
* Format: `video:${mimeType}:c:${nanoid()}.ext` (compressed, new)
|
||||||
|
* or `video:${mimeType}:u:${nanoid()}.ext` (uncompressed, new)
|
||||||
|
* or `video:${mimeType}:${nanoid()}.ext` (legacy format 2)
|
||||||
|
* or `video:${nanoid()}` (legacy format 1)
|
||||||
|
*
|
||||||
|
* Returns mimeType and whether the video was stored compressed.
|
||||||
*/
|
*/
|
||||||
function parseVideoMimeType(localRefPath: string): string {
|
export function parseVideoLocalRef(localRefPath: string): {
|
||||||
|
mimeType: string
|
||||||
|
compressed: boolean
|
||||||
|
} {
|
||||||
const parts = localRefPath.split(':')
|
const parts = localRefPath.split(':')
|
||||||
// New format: video:video/mp4:abc123 -> parts[1] is mime type
|
|
||||||
// Legacy format: video:abc123 -> no mime type, default to video/mp4
|
// New format with compressed flag: video:mimeType:c|u:id.ext
|
||||||
if (parts.length >= 3 && parts[1].includes('/')) {
|
if (parts.length >= 4 && parts[1].includes('/')) {
|
||||||
return parts[1]
|
const flag = parts[2]
|
||||||
|
if (flag === 'c' || flag === 'u') {
|
||||||
|
return {
|
||||||
|
mimeType: parts[1],
|
||||||
|
compressed: flag === 'c',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy format 2: video:mimeType:id.ext (no compressed flag)
|
||||||
|
if (parts.length >= 3 && parts[1].includes('/')) {
|
||||||
|
return {
|
||||||
|
mimeType: parts[1],
|
||||||
|
compressed: false, // Assume uncompressed for backwards compat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy format 1: video:id (no mime type, no compressed flag)
|
||||||
|
return {
|
||||||
|
mimeType: 'video/mp4',
|
||||||
|
compressed: false,
|
||||||
}
|
}
|
||||||
return 'video/mp4' // Default for legacy drafts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -187,7 +216,9 @@ function serializeImages(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize video to server format with localRef path.
|
* Serialize video to server format with localRef path.
|
||||||
* The localRef path encodes the mime type: `video:${mimeType}:${nanoid()}`
|
* The localRef path encodes the mime type and compressed flag:
|
||||||
|
* `video:${mimeType}:c:${nanoid()}.ext` (compressed video stored)
|
||||||
|
* Reuses existing localRefPath if present (when editing a draft).
|
||||||
*/
|
*/
|
||||||
async function serializeVideo(
|
async function serializeVideo(
|
||||||
videoState: VideoState,
|
videoState: VideoState,
|
||||||
@@ -198,12 +229,21 @@ async function serializeVideo(
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode mime type in the path for restoration
|
// Encode mime type and compressed flag in the path for restoration
|
||||||
|
// Reuse existing localRefPath if present (editing draft), otherwise generate new
|
||||||
const mimeType = videoState.video.mimeType || 'video/mp4'
|
const mimeType = videoState.video.mimeType || 'video/mp4'
|
||||||
const ext = mimeToExt(mimeType)
|
const ext = mimeToExt(mimeType)
|
||||||
const localRefPath = `video:${mimeType}:${nanoid()}.${ext}`
|
// Always mark as compressed since we only save after compression
|
||||||
|
const localRefPath =
|
||||||
|
videoState.localRefPath || `video:${mimeType}:c:${nanoid()}.${ext}`
|
||||||
localRefPaths.set(localRefPath, videoState.video.uri)
|
localRefPaths.set(localRefPath, videoState.video.uri)
|
||||||
|
|
||||||
|
logger.debug('serializing video', {
|
||||||
|
localRefPath,
|
||||||
|
isReusing: !!videoState.localRefPath,
|
||||||
|
sourcePath: videoState.video.uri,
|
||||||
|
})
|
||||||
|
|
||||||
// Read caption file contents as text
|
// Read caption file contents as text
|
||||||
const captions: AppBskyDraftDefs.DraftEmbedCaption[] = []
|
const captions: AppBskyDraftDefs.DraftEmbedCaption[] = []
|
||||||
for (const caption of videoState.captions) {
|
for (const caption of videoState.captions) {
|
||||||
@@ -501,12 +541,13 @@ export async function draftToComposerPosts(
|
|||||||
const vid = post.embedVideos[0]
|
const vid = post.embedVideos[0]
|
||||||
const videoUri = loadedMedia.get(vid.localRef.path)
|
const videoUri = loadedMedia.get(vid.localRef.path)
|
||||||
if (videoUri) {
|
if (videoUri) {
|
||||||
const mimeType = parseVideoMimeType(vid.localRef.path)
|
const {mimeType, compressed} = parseVideoLocalRef(vid.localRef.path)
|
||||||
logger.debug('found video to restore', {
|
logger.debug('found video to restore', {
|
||||||
localRefPath: vid.localRef.path,
|
localRefPath: vid.localRef.path,
|
||||||
videoUri,
|
videoUri,
|
||||||
altText: vid.alt,
|
altText: vid.alt,
|
||||||
mimeType,
|
mimeType,
|
||||||
|
compressed,
|
||||||
captionCount: vid.captions?.length ?? 0,
|
captionCount: vid.captions?.length ?? 0,
|
||||||
})
|
})
|
||||||
restoredVideos.set(index, {
|
restoredVideos.set(index, {
|
||||||
@@ -514,6 +555,7 @@ export async function draftToComposerPosts(
|
|||||||
altText: vid.alt || '',
|
altText: vid.alt || '',
|
||||||
mimeType,
|
mimeType,
|
||||||
localRefPath: vid.localRef.path,
|
localRefPath: vid.localRef.path,
|
||||||
|
compressed,
|
||||||
captions:
|
captions:
|
||||||
vid.captions?.map(c => ({lang: c.lang, content: c.content})) ??
|
vid.captions?.map(c => ({lang: c.lang, content: c.content})) ??
|
||||||
[],
|
[],
|
||||||
|
|||||||
Reference in New Issue
Block a user