save/restore captions
This commit is contained in:
@@ -340,6 +340,7 @@ export const ComposePost = ({
|
|||||||
postId,
|
postId,
|
||||||
videoUri: videoInfo.uri,
|
videoUri: videoInfo.uri,
|
||||||
altText: videoInfo.altText,
|
altText: videoInfo.altText,
|
||||||
|
captionCount: videoInfo.captions.length,
|
||||||
})
|
})
|
||||||
|
|
||||||
let asset: ImagePickerAsset
|
let asset: ImagePickerAsset
|
||||||
@@ -391,6 +392,28 @@ export const ComposePost = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore captions (web only - captions use File objects)
|
||||||
|
if (IS_WEB && videoInfo.captions.length > 0) {
|
||||||
|
const captionTracks = videoInfo.captions.map(c => ({
|
||||||
|
lang: c.lang,
|
||||||
|
file: new File([c.content], `caption-${c.lang}.vtt`, {
|
||||||
|
type: 'text/vtt',
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
composerDispatch({
|
||||||
|
type: 'update_post',
|
||||||
|
postId,
|
||||||
|
postAction: {
|
||||||
|
type: 'embed_update_video',
|
||||||
|
videoAction: {
|
||||||
|
type: 'update_captions',
|
||||||
|
updater: () => captionTracks,
|
||||||
|
signal: abortController.signal,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Start video compression and upload
|
// Start video compression and upload
|
||||||
processVideo(
|
processVideo(
|
||||||
asset,
|
asset,
|
||||||
|
|||||||
@@ -19,13 +19,14 @@ const TENOR_HOSTNAME = 'media.tenor.com'
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Video data from a draft that needs to be restored by re-processing.
|
* Video data from a draft that needs to be restored by re-processing.
|
||||||
* Contains the local file URI, alt text, and mime type to restore.
|
* Contains the local file URI, alt text, mime type, and captions to restore.
|
||||||
*/
|
*/
|
||||||
export type RestoredVideo = {
|
export type RestoredVideo = {
|
||||||
uri: string
|
uri: string
|
||||||
altText: string
|
altText: string
|
||||||
mimeType: string
|
mimeType: string
|
||||||
localRefPath: string
|
localRefPath: string
|
||||||
|
captions: Array<{lang: string; content: string}>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,15 +47,17 @@ function parseVideoMimeType(localRefPath: string): string {
|
|||||||
* Convert ComposerState to server Draft format for saving.
|
* Convert ComposerState to server Draft format for saving.
|
||||||
* Returns both the draft and a map of localRef paths to their source paths.
|
* Returns both the draft and a map of localRef paths to their source paths.
|
||||||
*/
|
*/
|
||||||
export function composerStateToDraft(state: ComposerState): {
|
export async function composerStateToDraft(state: ComposerState): Promise<{
|
||||||
draft: AppBskyDraftDefs.Draft
|
draft: AppBskyDraftDefs.Draft
|
||||||
localRefPaths: Map<string, string>
|
localRefPaths: Map<string, string>
|
||||||
} {
|
}> {
|
||||||
const localRefPaths = new Map<string, string>()
|
const localRefPaths = new Map<string, string>()
|
||||||
|
|
||||||
const posts: AppBskyDraftDefs.DraftPost[] = state.thread.posts.map(post => {
|
const posts: AppBskyDraftDefs.DraftPost[] = await Promise.all(
|
||||||
return postDraftToServerPost(post, localRefPaths)
|
state.thread.posts.map(post => {
|
||||||
})
|
return postDraftToServerPost(post, localRefPaths)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
// Convert threadgate settings to server format
|
// Convert threadgate settings to server format
|
||||||
const threadgateAllow: AppBskyDraftDefs.Draft['threadgateAllow'] = []
|
const threadgateAllow: AppBskyDraftDefs.Draft['threadgateAllow'] = []
|
||||||
@@ -92,10 +95,10 @@ export function composerStateToDraft(state: ComposerState): {
|
|||||||
/**
|
/**
|
||||||
* Convert a single PostDraft to server DraftPost format.
|
* Convert a single PostDraft to server DraftPost format.
|
||||||
*/
|
*/
|
||||||
function postDraftToServerPost(
|
async function postDraftToServerPost(
|
||||||
post: PostDraft,
|
post: PostDraft,
|
||||||
localRefPaths: Map<string, string>,
|
localRefPaths: Map<string, string>,
|
||||||
): AppBskyDraftDefs.DraftPost {
|
): Promise<AppBskyDraftDefs.DraftPost> {
|
||||||
const draftPost: AppBskyDraftDefs.DraftPost = {
|
const draftPost: AppBskyDraftDefs.DraftPost = {
|
||||||
$type: 'app.bsky.draft.defs#draftPost',
|
$type: 'app.bsky.draft.defs#draftPost',
|
||||||
text: post.richtext.text,
|
text: post.richtext.text,
|
||||||
@@ -117,7 +120,7 @@ function postDraftToServerPost(
|
|||||||
localRefPaths,
|
localRefPaths,
|
||||||
)
|
)
|
||||||
} else if (post.embed.media.type === 'video') {
|
} else if (post.embed.media.type === 'video') {
|
||||||
const video = serializeVideo(post.embed.media.video, localRefPaths)
|
const video = await serializeVideo(post.embed.media.video, localRefPaths)
|
||||||
if (video) {
|
if (video) {
|
||||||
draftPost.embedVideos = [video]
|
draftPost.embedVideos = [video]
|
||||||
}
|
}
|
||||||
@@ -192,10 +195,10 @@ 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: `video:${mimeType}:${nanoid()}`
|
||||||
*/
|
*/
|
||||||
function serializeVideo(
|
async function serializeVideo(
|
||||||
videoState: VideoState,
|
videoState: VideoState,
|
||||||
localRefPaths: Map<string, string>,
|
localRefPaths: Map<string, string>,
|
||||||
): AppBskyDraftDefs.DraftEmbedVideo | undefined {
|
): Promise<AppBskyDraftDefs.DraftEmbedVideo | undefined> {
|
||||||
// Only save videos that have been compressed (have a video file)
|
// Only save videos that have been compressed (have a video file)
|
||||||
if (!videoState.video) {
|
if (!videoState.video) {
|
||||||
return undefined
|
return undefined
|
||||||
@@ -206,6 +209,19 @@ function serializeVideo(
|
|||||||
const localRefPath = `video:${mimeType}:${nanoid()}`
|
const localRefPath = `video:${mimeType}:${nanoid()}`
|
||||||
localRefPaths.set(localRefPath, videoState.video.uri)
|
localRefPaths.set(localRefPath, videoState.video.uri)
|
||||||
|
|
||||||
|
// Read caption file contents as text
|
||||||
|
const captions: AppBskyDraftDefs.DraftEmbedCaption[] = []
|
||||||
|
for (const caption of videoState.captions) {
|
||||||
|
if (caption.lang) {
|
||||||
|
const content = await caption.file.text()
|
||||||
|
captions.push({
|
||||||
|
$type: 'app.bsky.draft.defs#draftEmbedCaption',
|
||||||
|
lang: caption.lang,
|
||||||
|
content,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
$type: 'app.bsky.draft.defs#draftEmbedVideo',
|
$type: 'app.bsky.draft.defs#draftEmbedVideo',
|
||||||
localRef: {
|
localRef: {
|
||||||
@@ -213,7 +229,7 @@ function serializeVideo(
|
|||||||
path: localRefPath,
|
path: localRefPath,
|
||||||
},
|
},
|
||||||
alt: videoState.altText || undefined,
|
alt: videoState.altText || undefined,
|
||||||
// TODO: Add captions if needed
|
captions: captions.length > 0 ? captions : undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,12 +489,15 @@ export function draftToComposerPosts(
|
|||||||
videoUri,
|
videoUri,
|
||||||
altText: vid.alt,
|
altText: vid.alt,
|
||||||
mimeType,
|
mimeType,
|
||||||
|
captionCount: vid.captions?.length ?? 0,
|
||||||
})
|
})
|
||||||
restoredVideos.set(index, {
|
restoredVideos.set(index, {
|
||||||
uri: videoUri,
|
uri: videoUri,
|
||||||
altText: vid.alt || '',
|
altText: vid.alt || '',
|
||||||
mimeType,
|
mimeType,
|
||||||
localRefPath: vid.localRef.path,
|
localRefPath: vid.localRef.path,
|
||||||
|
captions:
|
||||||
|
vid.captions?.map(c => ({lang: c.lang, content: c.content})) ?? [],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export function useSaveDraftMutation() {
|
|||||||
originalLocalRefs: Set<string> | undefined
|
originalLocalRefs: Set<string> | undefined
|
||||||
}> => {
|
}> => {
|
||||||
// Convert composer state to server draft format
|
// Convert composer state to server draft format
|
||||||
const {draft, localRefPaths} = composerStateToDraft(composerState)
|
const {draft, localRefPaths} = await composerStateToDraft(composerState)
|
||||||
|
|
||||||
logger.debug('saving draft', {
|
logger.debug('saving draft', {
|
||||||
existingDraftId,
|
existingDraftId,
|
||||||
|
|||||||
Reference in New Issue
Block a user