-
Notifications
You must be signed in to change notification settings - Fork 1
Enforce the upload contract before a video leaves the device #142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jlocala1
wants to merge
4
commits into
mieweb:main
Choose a base branch
from
jlocala1:fix/upload-contract
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
cc2795c
fix(upload): enforce the upload contract before a video leaves the de…
jlocala1 b0bfdc8
fix(upload): apply the contract to segment uploads too
jlocala1 7ec02af
fix(upload): normalise the contract input to a file:// URI
jlocala1 566a63f
fix(upload): actually enforce faststart, not just claim it
jlocala1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| import { File } from 'expo-file-system'; | ||
| import { compress, probeVideo } from 'react-native-video-trim'; | ||
|
|
||
| import { toFileUri, uploadDest } from './file-store'; | ||
| import { decideUploadContract } from './upload-contract'; | ||
|
|
||
| /** | ||
| * What conditioning did to a file on its way to being uploaded. | ||
| * | ||
| * `path` is always usable — on any failure it falls back to the input, so a broken probe | ||
| * or a failed encode degrades to "upload the original", never to "upload nothing". | ||
| */ | ||
| export type ContractResult = { | ||
| /** The file to upload: the conditioned copy, or the input when nothing was needed. */ | ||
| path: string; | ||
| /** True when `path` differs from the input. */ | ||
| changed: boolean; | ||
| /** Human-readable contract breaches that triggered the re-encode, for logging/UI. */ | ||
| reasons: string[]; | ||
| /** | ||
| * Set when the file could NOT be brought into the contract and the original is being | ||
| * uploaded instead. Never silently empty — the whole point of this gate is that a | ||
| * failure is visible. (`importClip` swallows exactly this case today, which is how a | ||
| * 4K master can still enter a draft.) | ||
| */ | ||
| failure?: string; | ||
| }; | ||
|
|
||
| /** | ||
| * Bring a file into the upload contract before it is uploaded: probe it, and re-encode | ||
| * only if it breaches (see {@link decideUploadContract}). | ||
| * | ||
| * A compliant file is returned untouched — no copy, no re-encode, no quality generation | ||
| * spent. That is the intended steady state once the recorder emits 1080p/5 Mbps: this | ||
| * gate costs one probe and nothing else. It exists for the case where the recorder's | ||
| * format negotiation loses on some device we have not tested, or an import slips a 4K | ||
| * master through — outcomes we cannot prevent, only catch. | ||
| * | ||
| * Failure policy is deliberately "fail open, loudly": an upload that happens at reduced | ||
| * quality is better than an upload that does not happen, but it must be reported rather | ||
| * than absorbed. | ||
| */ | ||
| export async function ensureUploadContract(path: string): Promise<ContractResult> { | ||
| const probe = await probeVideo(path).catch((e: unknown) => { | ||
| console.warn('[contract] probe failed; uploading the original', e); | ||
| return null; | ||
| }); | ||
| if (!probe) { | ||
| return { path, changed: false, reasons: [], failure: 'could not probe the file' }; | ||
| } | ||
|
|
||
| const decision = decideUploadContract(probe); | ||
| if (decision.action === 'passthrough') { | ||
| return { path, changed: false, reasons: [] }; | ||
| } | ||
|
|
||
| const result = await compress(path, { ...decision.options, outputExt: 'mp4' }).catch( | ||
| (e: unknown) => { | ||
| console.warn('[contract] re-encode failed; uploading the original', decision.reasons, e); | ||
| return null; | ||
| }, | ||
| ); | ||
| if (!result) { | ||
| return { | ||
| path, | ||
| changed: false, | ||
| reasons: decision.reasons, | ||
| failure: `could not re-encode (${decision.reasons.join(', ')})`, | ||
| }; | ||
| } | ||
|
|
||
| return { path: result.outputPath, changed: true, reasons: decision.reasons }; | ||
| } | ||
|
|
||
| /** | ||
| * {@link ensureUploadContract} with a stable, reusable output location — the form the SEGMENT | ||
| * upload path needs. | ||
| * | ||
| * Segment uploads resume byte-wise (TUS `HEAD` for the offset, then `PATCH` from there), so a run | ||
| * that resumes must send exactly the bytes it began with. Re-running a re-encode would produce a | ||
| * second, subtly different encode and splice it into a half-finished transfer. Conditioning into a | ||
| * fixed per-clip path means a resumed run finds the file it already made and reuses it — correct | ||
| * first, and a saved re-encode second. | ||
| * | ||
| * The merged path solves the same problem differently: it persists the conditioned path in the | ||
| * draft row, which segments have no column for. | ||
| */ | ||
| export async function ensureUploadContractCached( | ||
| sourcePath: string, | ||
| draftId: string, | ||
| segmentId: string, | ||
| ): Promise<ContractResult> { | ||
| const dest = uploadDest(draftId, segmentId); | ||
| if (dest.exists && (dest.size ?? 0) > 0) { | ||
| // Already conditioned on an earlier attempt — reuse verbatim. | ||
| return { path: dest.uri, changed: true, reasons: [] }; | ||
| } | ||
|
|
||
| const result = await ensureUploadContract(sourcePath); | ||
| if (!result.changed) return result; | ||
|
|
||
| try { | ||
| // compress() writes into the OS-purgeable cache dir; move it somewhere a resume can find it. | ||
| await new File(toFileUri(result.path)).move(dest); | ||
| return { ...result, path: dest.uri }; | ||
| } catch (e) { | ||
| // The conditioned bytes exist but could not be parked. Upload them from where they are | ||
| // rather than falling back to the oversized original; a resume may re-encode, which is | ||
| // worse than this but still better than uploading 4K. | ||
| console.warn('[contract] could not park the conditioned clip; using the cache copy', e); | ||
| return result; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.