@valbuild/server 0.140.2 → 0.141.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +83 -0
- package/dist/declarations/src/ValOps.d.ts +20 -0
- package/dist/declarations/src/ValOpsFS.d.ts +5 -1
- package/dist/declarations/src/createFixPatch.d.ts +3 -1
- package/dist/declarations/src/extractMetadata.d.ts +47 -1
- package/dist/declarations/src/fixHandlers.d.ts +38 -33
- package/dist/declarations/src/galleryEntryKey.d.ts +35 -0
- package/dist/declarations/src/galleryFiles.d.ts +59 -0
- package/dist/declarations/src/index.d.ts +5 -2
- package/dist/declarations/src/isoBmff.d.ts +41 -0
- package/dist/declarations/src/valServerConfig.d.ts +8 -0
- package/dist/declarations/src/videoFiles.d.ts +22 -0
- package/dist/declarations/src/videosetFixes.d.ts +141 -0
- package/dist/valbuild-server.cjs.dev.js +3376 -432
- package/dist/valbuild-server.cjs.prod.js +3376 -432
- package/dist/valbuild-server.esm.js +3368 -434
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,88 @@
|
|
|
1
1
|
# @valbuild/server
|
|
2
2
|
|
|
3
|
+
## 0.141.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#808](https://github.com/valbuild/val/pull/808) [`14b18d7`](https://github.com/valbuild/val/commit/14b18d7cb145cec1bc1cd832b9ca857ff4b45adc) Thanks [@freekh](https://github.com/freekh)! - A project with `files: { remote: true }` can save text edits without remote-file credentials: in local development without `val login` or a project id, and on a server without `VAL_API_KEY`.
|
|
8
|
+
|
|
9
|
+
Before, a save asked for remote credentials whenever any schema in the project was remote. With `files: { remote: true }` that is every media schema, so every save was refused, even a typo fix. Now it asks only when the change being saved contains a remote file.
|
|
10
|
+
|
|
11
|
+
When uploads are unavailable, the Studio now shows a card above the editor. It says what is wrong and gives the fix: a link to admin.val.build or the `val login` command to copy. When the cause is missing setup (no project id, not logged in, no API key) it also says that text edits still save and publish. Dismissing it leaves an "Uploads off" item in the status bar, or in the account sheet on a phone, that brings it back. This replaces the red bar that covered the top of the Studio, the Publish button included, and the login dialog that opened on every load.
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [[`6faacda`](https://github.com/valbuild/val/commit/6faacda398fc92bec187b7afe07a53228a6f2a3b), [`14b18d7`](https://github.com/valbuild/val/commit/14b18d7cb145cec1bc1cd832b9ca857ff4b45adc)]:
|
|
14
|
+
- @valbuild/ui@0.141.1
|
|
15
|
+
|
|
16
|
+
## 0.141.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- [#802](https://github.com/valbuild/val/pull/802) [`199e214`](https://github.com/valbuild/val/commit/199e21471d98806fb34c579cb876560078c928b9) Thanks [@freekh](https://github.com/freekh)! - New: `files: { remote: true }` in `val.config.ts` stores every image, file and video on Val's remote content host.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
const { s, c, val, config } = initVal({
|
|
24
|
+
project: "myorg/myproject",
|
|
25
|
+
files: { remote: true },
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
With it, `s.image()`, `s.file()`, `s.video()`, `s.imageset()`, `s.fileset()`, `s.videoset()` and `s.richtext({ img: true })` behave as though `.remote()` had been written on them. Images already in the project are reported as `image:upload-remote` until they are uploaded; `npx val validate --fix` does that.
|
|
30
|
+
|
|
31
|
+
**Required in the Val app.** A project running there (`VAL_ENV=app`) refuses to start without it, with an error that says what to add, and `val publish` refuses before it builds.
|
|
32
|
+
|
|
33
|
+
**Breaking:** `files.directory` has been removed from `val.config.ts`. Local files that have no `dir` of their own are stored in `/public/val`, which was the default.
|
|
34
|
+
|
|
35
|
+
- [#779](https://github.com/valbuild/val/pull/779) [`ba3f330`](https://github.com/valbuild/val/commit/ba3f3300a3dd8649e154097cdfca3b64db209d89) Thanks [@freekh](https://github.com/freekh)! - New: `s.video()`, for mp4/WebM files and HLS streams.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const schema = s.object({
|
|
39
|
+
intro: s.video({ stream: { type: "hls" } }),
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- **Upload and play in the Studio.** A video field shows a player, the file's size and length, and progress while it uploads.
|
|
44
|
+
- **HLS streaming without a video service.** With `stream: { type: "hls" }`, the Studio converts an upload to an HLS stream in the editor's browser before it uploads it. It makes one H.264 rendition per height (1080p, 720p and 480p by default, and never larger than the upload) and stores them as a folder of files next to your other uploads. Nothing is installed in your app for this: the converter ships inside the Studio and is only downloaded when someone uploads to a streaming field. A browser that can't convert (no WebCodecs, or a non-https address) uploads the original file and says so.
|
|
45
|
+
- **Poster:** pause on a frame and choose "Use current frame". The frame is saved as an image, and `posterTime` records where it was taken.
|
|
46
|
+
- **Start and end:** play part of a video without cutting the file.
|
|
47
|
+
- **Focal point:** marks what must stay in frame when the page crops the video.
|
|
48
|
+
- **Description:** text for people who can't see the video.
|
|
49
|
+
- **Captions:** one WebVTT file per language. `.srt` files are converted on upload.
|
|
50
|
+
- **`<ValVideo>`** in `@valbuild/next` and `@valbuild/tanstack` renders all of the above and is click-to-edit. For HLS in browsers that can't play it natively, pass `hls={() => import("hls.js")}`. It is only loaded when it's needed.
|
|
51
|
+
- **Rename** a video in the Studio. An HLS stream is renamed as a whole: every playlist and segment moves to the new folder.
|
|
52
|
+
- `npx val validate --fix` fills in `mimeType`, `width`, `height` and `duration` for a hand-written `.mp4`, `.mov`, `.webm`, `.mkv` or HLS video, on disk or on Val Remote. A remote video is not downloaded: only its headers are read, usually in one to three small requests.
|
|
53
|
+
- `.remote()` works for videos. `npx val validate --fix` moves a video to Val Remote, or back into the project, together with its poster, captions and every file of an HLS stream.
|
|
54
|
+
|
|
55
|
+
A video value always has a `mimeType`: it is how a page knows whether it has an `.mp4` or an `.m3u8`.
|
|
56
|
+
|
|
57
|
+
New: `s.videoset()`, a collection of videos, like `s.imageset()` for images.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const videosVal = c.define(
|
|
61
|
+
"/content/videos.val.ts",
|
|
62
|
+
s.videoset({ dir: "/public/val/videos", stream: { type: "hls" } }),
|
|
63
|
+
{},
|
|
64
|
+
);
|
|
65
|
+
const schema = s.object({ intro: s.video(videosVal) });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- **Upload once, set it up once, use anywhere.** A set's entry holds what is true of the file (its type, size and length) and, as defaults, a description, poster, start and end, focal point and captions. A field that picks from the set (`s.video(videosVal)`) gets all of those, and can override any of them on its own. For example, the same clip can open one page at 0:02 and every other page where the set says.
|
|
69
|
+
- **Media in the Studio.** A set is listed under Media and opens as a gallery of videos, with each video's poster as its thumbnail. Hovering a video previews it, and the selected video plays in a real player (HLS streams too) beside the poster, times, focal point and captions you edit for it. You can upload into the set (with `stream`, uploads become HLS streams, and each upload gets a poster), rename a video, and delete a video that nothing uses. Renaming a video updates every field that uses it. An HLS stream is one entry, and renaming or deleting it moves or removes all of its files.
|
|
70
|
+
- **Picking.** A set-backed field picks from the set, or uploads into it. Each value the set provides is marked "From gallery · Override", and once overridden, "Overridden · Use gallery's".
|
|
71
|
+
- `useVal` and `fetchVal` fill in what the set holds (`mimeType`, `width`, `height`, `duration`, and every default the field does not override), so `<ValVideo>` works the same either way.
|
|
72
|
+
- `.remote()` works for sets. `npx val validate --fix` fills in missing metadata for an entry, adds untracked videos found in the set's directory, and moves a set's videos to Val Remote.
|
|
73
|
+
|
|
74
|
+
Image and file galleries use the same new gallery layout: a grid or list of entries, with the selected one open in a panel beside it.
|
|
75
|
+
|
|
76
|
+
- An `s.imageset()` entry can hold a default focal point, as well as its description. An `s.image(galleryVal)` field shows both, and can override either.
|
|
77
|
+
- Fixed: outside draft mode, `useVal` and `fetchVal` now fill a gallery-backed image or file from its gallery (dimensions, mime type, description). Before, a published page got only the `path`.
|
|
78
|
+
|
|
79
|
+
### Patch Changes
|
|
80
|
+
|
|
81
|
+
- Updated dependencies [[`199e214`](https://github.com/valbuild/val/commit/199e21471d98806fb34c579cb876560078c928b9), [`9cbe428`](https://github.com/valbuild/val/commit/9cbe42895d006d55c3d993dc27f0779808b85306), [`fb5f097`](https://github.com/valbuild/val/commit/fb5f09721ac07f4d90b8502fdbee3fb3884d9688), [`6473a2e`](https://github.com/valbuild/val/commit/6473a2efe874f1749b27ed6f8ac4e0d19805edb1), [`ba3f330`](https://github.com/valbuild/val/commit/ba3f3300a3dd8649e154097cdfca3b64db209d89)]:
|
|
82
|
+
- @valbuild/core@0.141.0
|
|
83
|
+
- @valbuild/shared@0.141.0
|
|
84
|
+
- @valbuild/ui@0.141.0
|
|
85
|
+
|
|
3
86
|
## 0.140.2
|
|
4
87
|
|
|
5
88
|
### Patch Changes
|
|
@@ -594,6 +594,19 @@ export declare abstract class ValOps {
|
|
|
594
594
|
abstract getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, remote: boolean): Promise<Buffer | null>;
|
|
595
595
|
protected abstract getBase64EncodedBinaryFileMetadataFromPatch<T extends "file" | "image">(filePath: string, type: T, patchId: PatchId, remote: boolean): Promise<OpsMetadata<T>>;
|
|
596
596
|
abstract getBinaryFile(filePathOrRef: string): Promise<Buffer | null>;
|
|
597
|
+
/**
|
|
598
|
+
* A file's bytes as something a `Range` request can be answered from
|
|
599
|
+
* without reading the rest: a video is seeked in by many small ranges, and
|
|
600
|
+
* each used to load the whole file.
|
|
601
|
+
*
|
|
602
|
+
* This default holds the whole file, which is all a mode that only gets
|
|
603
|
+
* whole files can do (`ValOpsHttp`: the content service answers with the
|
|
604
|
+
* file in a JSON body). `ValOpsFS` reads only the asked-for range off disk.
|
|
605
|
+
*/
|
|
606
|
+
openBinaryFile(filePath: string, fromPatch: {
|
|
607
|
+
patchId: PatchId;
|
|
608
|
+
remote: boolean;
|
|
609
|
+
} | null): Promise<BinaryFileReader | null>;
|
|
597
610
|
protected abstract getBinaryFileMetadata<T extends "file" | "image">(filePath: string, type: T): Promise<OpsMetadata<T>>;
|
|
598
611
|
abstract deletePatches(patchIds: PatchId[]): Promise<{
|
|
599
612
|
deleted: PatchId[];
|
|
@@ -977,3 +990,10 @@ export declare function getFieldsForType<T extends BinaryFileType>(type: T): (ke
|
|
|
977
990
|
export declare function createMetadataFromBuffer<T extends BinaryFileType>(type: BinaryFileType, mimeType: string, buffer: Buffer): OpsMetadata<T>;
|
|
978
991
|
export declare function guessMimeTypeFromPath(filePath: string): string | null;
|
|
979
992
|
export declare function bufferFromDataUrl(dataUrl: string): Buffer | undefined;
|
|
993
|
+
/** A file of known size whose bytes are read a range at a time. */
|
|
994
|
+
export type BinaryFileReader = {
|
|
995
|
+
readonly size: number;
|
|
996
|
+
/** Bytes `start` to `end`, both inclusive, as an HTTP range is. */
|
|
997
|
+
read(start: number, end: number): Promise<Buffer>;
|
|
998
|
+
};
|
|
999
|
+
export declare function bufferReader(buffer: Buffer): BinaryFileReader;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PatchId, ModuleFilePath, ValModules } from "@valbuild/core";
|
|
2
|
-
import { AuthorId, BaseSha, BinaryFileType, GenericErrorMessage, MetadataOfType, OpsMetadata, PreparedCommit, ValOps, ValOpsOptions, WithGenericError, SaveSourceFilePatchResult, type PatchGroupMembership, SchemaSha, CommitSha, OrderedPatches, OrderedPatchesMetadata, SourcesSha } from "./ValOps.js";
|
|
2
|
+
import { AuthorId, BaseSha, BinaryFileType, BinaryFileReader, GenericErrorMessage, MetadataOfType, OpsMetadata, PreparedCommit, ValOps, ValOpsOptions, WithGenericError, SaveSourceFilePatchResult, type PatchGroupMembership, SchemaSha, CommitSha, OrderedPatches, OrderedPatchesMetadata, SourcesSha } from "./ValOps.js";
|
|
3
3
|
import { Patch, ParentRef, ValCommit } from "@valbuild/shared/internal";
|
|
4
4
|
import type { HistoryError } from "./history/HistoryError.js";
|
|
5
5
|
import type { AffectedFile, StoredModuleVersion, CommitPage, CommitPatch, HistoricalCommit } from "./history/types.js";
|
|
@@ -162,6 +162,10 @@ export declare class ValOpsFS extends ValOps {
|
|
|
162
162
|
filePath?: string;
|
|
163
163
|
}>;
|
|
164
164
|
}>;
|
|
165
|
+
openBinaryFile(filePath: string, fromPatch: {
|
|
166
|
+
patchId: PatchId;
|
|
167
|
+
remote: boolean;
|
|
168
|
+
} | null): Promise<BinaryFileReader | null>;
|
|
165
169
|
getBinaryFile(filePath: string): Promise<Buffer | null>;
|
|
166
170
|
protected getBinaryFileMetadata<T extends BinaryFileType>(filePath: string, type: T): Promise<OpsMetadata<T>>;
|
|
167
171
|
private getPatchesDir;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FileMetadata, ImageMetadata, SerializedSchema, Source, SourcePath, ValidationError } from "@valbuild/core";
|
|
1
|
+
import { FileMetadata, ImageMetadata, VideoMetadata, SerializedSchema, Source, SourcePath, ValidationError } from "@valbuild/core";
|
|
2
2
|
import { JSONValue, Patch } from "@valbuild/core/patch";
|
|
3
3
|
export type FixPatchRemainingError = ValidationError & {
|
|
4
4
|
sourcePath?: SourcePath;
|
|
@@ -21,10 +21,12 @@ export declare function createFixPatch(config: {
|
|
|
21
21
|
[sourcePath: SourcePath]: {
|
|
22
22
|
ref: string;
|
|
23
23
|
metadata?: Record<string, unknown>;
|
|
24
|
+
refs?: Record<string, string>;
|
|
24
25
|
};
|
|
25
26
|
}, moduleSource?: Source, moduleSchema?: SerializedSchema): Promise<{
|
|
26
27
|
patch: Patch;
|
|
27
28
|
remainingErrors: FixPatchRemainingError[];
|
|
28
29
|
} | undefined>;
|
|
30
|
+
export declare function getVideoMetadata(projectRoot: string, fileRef: string): Promise<VideoMetadata>;
|
|
29
31
|
export declare function getImageMetadata(projectRoot: string, validationError: ValidationError): Promise<ImageMetadata>;
|
|
30
32
|
export declare function getFileMetadata(projectRoot: string, validationError: ValidationError): Promise<FileMetadata>;
|
|
@@ -1,4 +1,50 @@
|
|
|
1
|
-
import { FileMetadata, ImageMetadata } from "@valbuild/core";
|
|
1
|
+
import { FileMetadata, ImageMetadata, VideoMetadata } from "@valbuild/core";
|
|
2
2
|
import { Buffer } from "buffer";
|
|
3
|
+
import { type ByteSource } from "./isoBmff.js";
|
|
3
4
|
export declare function extractImageMetadata(filename: string, input: Buffer): Promise<ImageMetadata>;
|
|
4
5
|
export declare function extractFileMetadata(filename: string, _input: Buffer): Promise<FileMetadata>;
|
|
6
|
+
/**
|
|
7
|
+
* What `val validate --fix` writes into a video: `mimeType`, `width`, `height`
|
|
8
|
+
* and `duration`, read from the file. A field that cannot be read is left out
|
|
9
|
+
* rather than guessed, and the caller reports it — see
|
|
10
|
+
* {@link unreadableVideoMetadataMessage}.
|
|
11
|
+
*
|
|
12
|
+
* `mimeType` always comes from the file EXTENSION: validation compares the two,
|
|
13
|
+
* so any other answer is one validation rejects. The rest depends on the kind
|
|
14
|
+
* of file:
|
|
15
|
+
*
|
|
16
|
+
* - **ISO BMFF** (`.mp4`, `.m4v`, `.mov`): read from the `moov` box by a small
|
|
17
|
+
* parser of our own (`isoBmff.ts`). No media library: this package is loaded
|
|
18
|
+
* by every app that runs Val.
|
|
19
|
+
* - **WebM / Matroska** (`.webm`, `.mkv`): read from the Segment's Info and Tracks by another
|
|
20
|
+
* (`ebml.ts`). A WebM recorded in a browser does not declare its length, and
|
|
21
|
+
* then `duration` is left out — the frames are not counted to find it.
|
|
22
|
+
* - **An HLS master playlist** (`.m3u8`): the dimensions are the largest
|
|
23
|
+
* rendition's `RESOLUTION`, and the duration is the sum of the `#EXTINF`s of
|
|
24
|
+
* the first media playlist it names, read from disk beside it — which is why
|
|
25
|
+
* `filename` must be the playlist's path on disk.
|
|
26
|
+
* - **Anything else**: the mime type and nothing more. The Studio reads those
|
|
27
|
+
* in the browser when they are uploaded.
|
|
28
|
+
*/
|
|
29
|
+
export declare function extractVideoMetadata(filename: string, input: Buffer, readFile?: (absolutePath: string) => Buffer | undefined): Promise<VideoMetadata>;
|
|
30
|
+
/**
|
|
31
|
+
* {@link extractVideoMetadata} for a file on disk, reading only the headers
|
|
32
|
+
* it needs: a video's frames (an mp4's `mdat`, a WebM's Clusters) are most of
|
|
33
|
+
* the file, and are skipped, not loaded.
|
|
34
|
+
*/
|
|
35
|
+
export declare function extractVideoMetadataFromFile(absolutePath: string): Promise<VideoMetadata>;
|
|
36
|
+
/**
|
|
37
|
+
* What to tell someone whose video's `fields` could not be read. Names the
|
|
38
|
+
* way out, because "could not read" alone leaves them nowhere to go.
|
|
39
|
+
*/
|
|
40
|
+
export declare function unreadableVideoMetadataMessage(fileRef: string, fields: readonly string[]): string;
|
|
41
|
+
/** Whether Val reads this video's size and length itself, by its extension. */
|
|
42
|
+
export declare function canReadVideoMetadata(fileRef: string): boolean;
|
|
43
|
+
export declare function extractFromByteSource(filename: string, source: ByteSource): VideoMetadata;
|
|
44
|
+
/**
|
|
45
|
+
* An HLS stream's metadata from its master playlist's text, and — for the
|
|
46
|
+
* duration — the first media playlist it names, which `readMediaPlaylist`
|
|
47
|
+
* fetches by the URI the master gives (relative to it, or a remote ref). The
|
|
48
|
+
* one implementation for a stream on disk and one on Val Remote.
|
|
49
|
+
*/
|
|
50
|
+
export declare function readHlsMetadata(text: string, readMediaPlaylist: (uri: string) => Promise<string | undefined>): Promise<VideoMetadata>;
|
|
@@ -27,9 +27,12 @@
|
|
|
27
27
|
* walks every module, decides what to report, and renders it to a terminal.
|
|
28
28
|
*/
|
|
29
29
|
import { ModuleFilePath, SourcePath, ValidationFix } from "@valbuild/core";
|
|
30
|
+
import { checkGalleryFiles } from "./galleryFiles.js";
|
|
30
31
|
import type { Service } from "./Service.js";
|
|
31
32
|
import type { IValFSHost } from "./ValFSHost.js";
|
|
33
|
+
import type { Patch } from "@valbuild/core/patch";
|
|
32
34
|
export type { IValFSHost };
|
|
35
|
+
export { checkGalleryFiles };
|
|
33
36
|
export type IValRemote = {
|
|
34
37
|
remoteHost: string;
|
|
35
38
|
getSettings(projectName: string, options: {
|
|
@@ -73,27 +76,49 @@ export type FixHandlerContext = {
|
|
|
73
76
|
moduleFilePath: ModuleFilePath;
|
|
74
77
|
file: string;
|
|
75
78
|
fs: IValFSHost;
|
|
76
|
-
remoteFiles: Record<SourcePath,
|
|
77
|
-
ref: string;
|
|
78
|
-
metadata?: Record<string, unknown>;
|
|
79
|
-
}>;
|
|
79
|
+
remoteFiles: Record<SourcePath, RemoteFileMove>;
|
|
80
80
|
publicProjectId?: string;
|
|
81
81
|
remoteFileBuckets?: string[];
|
|
82
82
|
remoteFilesCounter: number;
|
|
83
83
|
remote: IValRemote;
|
|
84
84
|
project: string | undefined;
|
|
85
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* Where a fix moved a media value's file. `refs` is for a value that names
|
|
88
|
+
* SEVERAL files — a video's poster, captions and stream — keyed by the path
|
|
89
|
+
* the value held, so the patch can rewrite each one where it is.
|
|
90
|
+
*/
|
|
91
|
+
export type RemoteFileMove = {
|
|
92
|
+
ref: string;
|
|
93
|
+
metadata?: Record<string, unknown>;
|
|
94
|
+
refs?: Record<string, string>;
|
|
95
|
+
};
|
|
86
96
|
export type FixHandlerResult = {
|
|
87
97
|
success: boolean;
|
|
88
98
|
errorMessage?: string;
|
|
89
99
|
shouldApplyPatch?: boolean;
|
|
90
100
|
appliedFix?: boolean;
|
|
91
101
|
fixableErrorMessage?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Patches to OTHER modules than the one the fix is about, for the caller to
|
|
104
|
+
* apply alongside the fix's own patch (and only when it applies that one).
|
|
105
|
+
*
|
|
106
|
+
* A fix normally rewrites one value. Renaming a key that other modules name
|
|
107
|
+
* cannot be one: `videos:upload-remote` renames a set entry's key to its
|
|
108
|
+
* remote ref, and every `s.video(set)` field holding the old key would name
|
|
109
|
+
* a video the set no longer has.
|
|
110
|
+
*/
|
|
111
|
+
otherModulePatches?: ModulePatch[];
|
|
92
112
|
publicProjectId?: string;
|
|
93
113
|
remoteFileBuckets?: string[];
|
|
94
114
|
remoteFilesCounter?: number;
|
|
95
115
|
events?: ValidationEvent[];
|
|
96
116
|
};
|
|
117
|
+
/** A patch, and the module it is for. */
|
|
118
|
+
export type ModulePatch = {
|
|
119
|
+
moduleFilePath: ModuleFilePath;
|
|
120
|
+
patch: Patch;
|
|
121
|
+
};
|
|
97
122
|
export type FixHandler = (ctx: FixHandlerContext) => Promise<FixHandlerResult>;
|
|
98
123
|
export type ValidationEvent = {
|
|
99
124
|
type: "file-valid";
|
|
@@ -157,40 +182,20 @@ export type ValidationEvent = {
|
|
|
157
182
|
type: "summary-success";
|
|
158
183
|
};
|
|
159
184
|
export declare function handleFileMetadata(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
185
|
+
/**
|
|
186
|
+
* `video:add-metadata`: the bytes must be on disk, or on Val Remote (read
|
|
187
|
+
* over HTTP, headers only — see `remoteVideoMetadata.ts`), and they must be a
|
|
188
|
+
* kind of file Val can read the size and length of here: mp4/mov boxes,
|
|
189
|
+
* WebM/Matroska headers and HLS playlists. Anything else is refused with what
|
|
190
|
+
* to do instead, unless all that is missing is the mime type, which the
|
|
191
|
+
* extension answers.
|
|
192
|
+
*/
|
|
193
|
+
export declare function handleVideoMetadata(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
160
194
|
export declare function handleRemoteFileUpload(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
161
195
|
export declare function handleRemoteGalleryFileUpload(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
162
196
|
export declare function handleRemoteFileDownload(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
163
197
|
export declare function handleRemoteFileCheck(): Promise<FixHandlerResult>;
|
|
164
198
|
export declare function handleUniqueFolderCheck(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
165
|
-
/**
|
|
166
|
-
* What is out of step between a gallery's entries and its directory.
|
|
167
|
-
*
|
|
168
|
-
* Two questions, and they treat a remote entry differently — which is the whole
|
|
169
|
-
* reason this is separate from the handler around it:
|
|
170
|
-
*
|
|
171
|
-
* - **Missing**: an entry with no bytes at its local path. Asked of LOCAL
|
|
172
|
-
* entries only. A remote entry's bytes live on the content host, and nothing
|
|
173
|
-
* puts a copy in the working tree: `saveOrUploadFiles` uploads the remote
|
|
174
|
-
* descriptors and copies only the local ones into the tree, so a remote entry
|
|
175
|
-
* added through the Studio (or over MCP) has no local file by design, and
|
|
176
|
-
* demanding one would mean committing remote bytes to git — which is what
|
|
177
|
-
* remote storage exists to avoid. Whether those bytes really are on the host
|
|
178
|
-
* is a different check, `image:check-remote`, which already runs for exactly
|
|
179
|
-
* these entries.
|
|
180
|
-
* - **Untracked**: a file in the directory that no entry claims. Asked of every
|
|
181
|
-
* entry, remote included, and that is why they are normalised to their local
|
|
182
|
-
* path: `val validate --fix` promotes a local file to a remote ref and leaves
|
|
183
|
-
* the file where it was, so a remote entry can perfectly well have one.
|
|
184
|
-
*/
|
|
185
|
-
export declare function checkGalleryFiles(input: {
|
|
186
|
-
entryKeys: string[];
|
|
187
|
-
dir: string;
|
|
188
|
-
projectRoot: string;
|
|
189
|
-
fs: Pick<IValFSHost, "fileExists" | "readDirectory">;
|
|
190
|
-
}): {
|
|
191
|
-
missingTrackedFiles: string[];
|
|
192
|
-
untrackedFiles: string[];
|
|
193
|
-
};
|
|
194
199
|
export declare function handleCheckAllFiles(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
195
200
|
export declare function handleJsonValuesExtractEntry(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
196
201
|
/**
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A gallery entry's key, read as what it actually is.
|
|
3
|
+
*
|
|
4
|
+
* A gallery is a record keyed by its entries' file paths. A REMOTE gallery keys
|
|
5
|
+
* an uploaded entry by the remote URL instead — which encodes that same path —
|
|
6
|
+
* so every entry has a local path, and the interesting question is whether the
|
|
7
|
+
* bytes are expected to be at it.
|
|
8
|
+
*
|
|
9
|
+
* **They are not, for a remote entry added the ordinary way.** `saveOrUploadFiles`
|
|
10
|
+
* uploads the remote descriptors to the content host and copies only the local
|
|
11
|
+
* ones into the working tree, so an image added through the Studio (or over MCP)
|
|
12
|
+
* and published has no file in the repo, by design — putting one there is what
|
|
13
|
+
* remote storage exists to avoid. A remote entry CAN have one, because
|
|
14
|
+
* `val validate --fix` promotes a local file to a remote ref and leaves the file
|
|
15
|
+
* where it was; so "remote" means "do not require it", never "there is not one".
|
|
16
|
+
*
|
|
17
|
+
* One copy, in its own file, because this used to be two: a `remoteKeyToLocalPath`
|
|
18
|
+
* in `fixHandlers` and a `galleryKeyToLocalPath` in `createFixPatch`, each
|
|
19
|
+
* normalising the key correctly and each then treating the result as a file that
|
|
20
|
+
* must exist. That agreement is what made a published remote image report as
|
|
21
|
+
* missing from both sides at once.
|
|
22
|
+
*/
|
|
23
|
+
export type GalleryEntryKey = {
|
|
24
|
+
/**
|
|
25
|
+
* Where the bytes are, or would be, in the working tree.
|
|
26
|
+
*
|
|
27
|
+
* The key itself for a remote key that does not parse — there is no such
|
|
28
|
+
* place for one, and nothing reads this for a remote entry except the
|
|
29
|
+
* untracked-files comparison, where a URL matches no file.
|
|
30
|
+
*/
|
|
31
|
+
localPath: string;
|
|
32
|
+
/** True when the key is a URL, so a local file is not required. */
|
|
33
|
+
remote: boolean;
|
|
34
|
+
};
|
|
35
|
+
export declare function galleryEntryOf(key: string): GalleryEntryKey;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type GalleryEntryKey } from "./galleryEntryKey.js";
|
|
2
|
+
import type { IValFSHost } from "./ValFSHost.js";
|
|
3
|
+
/**
|
|
4
|
+
* What is out of step between a gallery's entries and its directory.
|
|
5
|
+
*
|
|
6
|
+
* Two questions, and they treat a remote entry differently — which is the whole
|
|
7
|
+
* reason this is separate from the handler around it:
|
|
8
|
+
*
|
|
9
|
+
* - **Missing**: an entry with no bytes at its local path. Asked of LOCAL
|
|
10
|
+
* entries only. A remote entry's bytes live on the content host, and nothing
|
|
11
|
+
* puts a copy in the working tree: `saveOrUploadFiles` uploads the remote
|
|
12
|
+
* descriptors and copies only the local ones into the tree, so a remote entry
|
|
13
|
+
* added through the Studio (or over MCP) has no local file by design, and
|
|
14
|
+
* demanding one would mean committing remote bytes to git — which is what
|
|
15
|
+
* remote storage exists to avoid. Whether those bytes really are on the host
|
|
16
|
+
* is a different check, `image:check-remote`, which already runs for exactly
|
|
17
|
+
* these entries.
|
|
18
|
+
* - **Untracked**: a file in the directory that no entry claims. Asked of every
|
|
19
|
+
* entry, remote included, and that is why they are normalised to their local
|
|
20
|
+
* path: `val validate --fix` promotes a local file to a remote ref and leaves
|
|
21
|
+
* the file where it was, so a remote entry can perfectly well have one.
|
|
22
|
+
*
|
|
23
|
+
* An entry can claim more than its key. A video set's HLS stream is keyed by
|
|
24
|
+
* its master playlist, and the media playlists and segments it names are the
|
|
25
|
+
* same entry — so `filesOfEntry` says what else an entry holds, and those are
|
|
26
|
+
* not untracked either.
|
|
27
|
+
*
|
|
28
|
+
* Its own module rather than a part of `fixHandlers.ts`, so the video set's
|
|
29
|
+
* fixes can use it without importing the handler registry they are part of.
|
|
30
|
+
*/
|
|
31
|
+
export declare function checkGalleryFiles(input: {
|
|
32
|
+
entryKeys: string[];
|
|
33
|
+
dir: string;
|
|
34
|
+
projectRoot: string;
|
|
35
|
+
fs: Pick<IValFSHost, "fileExists" | "readDirectory">;
|
|
36
|
+
/**
|
|
37
|
+
* Every file an entry holds besides its key, as `/public/…` refs. Asked of
|
|
38
|
+
* every entry, for the same reason untracked is.
|
|
39
|
+
*/
|
|
40
|
+
filesOfEntry?: (entry: GalleryEntryKey, key: string) => string[];
|
|
41
|
+
}): {
|
|
42
|
+
missingTrackedFiles: string[];
|
|
43
|
+
untrackedFiles: string[];
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* The LOCAL entries whose key is there but some of what they hold is not: a
|
|
47
|
+
* stream missing a segment plays up to the gap and then stops. Separate from
|
|
48
|
+
* `checkGalleryFiles`' "missing" because a fix does not remove these — the
|
|
49
|
+
* entry still names a video someone uploaded and may have the rest of.
|
|
50
|
+
*/
|
|
51
|
+
export declare function incompleteGalleryEntries(input: {
|
|
52
|
+
entryKeys: string[];
|
|
53
|
+
projectRoot: string;
|
|
54
|
+
fs: Pick<IValFSHost, "fileExists">;
|
|
55
|
+
filesOfEntry: (entry: GalleryEntryKey, key: string) => string[];
|
|
56
|
+
}): {
|
|
57
|
+
key: string;
|
|
58
|
+
missing: string[];
|
|
59
|
+
}[];
|
|
@@ -4,6 +4,7 @@ export type { AdapterFor, BoundExternalRecord, ExternalAuthor, ExternalBuilder,
|
|
|
4
4
|
export { createValApiRouter, createValServer, safeReadGit } from "./ValRouter.js";
|
|
5
5
|
export { initHandlerOptions, createValOps } from "./valServerConfig.js";
|
|
6
6
|
export { resolveRemoteFileAuth } from "./valServerConfig.js";
|
|
7
|
+
export { APP_MODE_REQUIRES_REMOTE_FILES } from "./valServerConfig.js";
|
|
7
8
|
export type { RemoteFileAuth, ResolveRemoteFileAuthResult, } from "./valServerConfig.js";
|
|
8
9
|
export { ValModuleLoader } from "./ValModuleLoader.js";
|
|
9
10
|
export { getCompilerOptions } from "./getCompilerOptions.js";
|
|
@@ -17,13 +18,15 @@ export { analyzeValModule } from "./patch/ts/valModule.js";
|
|
|
17
18
|
export type { ValModuleAnalysis } from "./patch/ts/valModule.js";
|
|
18
19
|
export { createFixPatch } from "./createFixPatch.js";
|
|
19
20
|
export { fixHandlers, currentFixHandlers, createDefaultValFSHost, handleFileMetadata, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleRemoteFileDownload, handleRemoteFileCheck, handleUniqueFolderCheck, handleCheckAllFiles, handleJsonValuesExtractEntry, handleExternalUpload, } from "./fixHandlers.js";
|
|
20
|
-
export
|
|
21
|
+
export { handleVideosetMetadata, handleVideosetUploadRemote, handleVideosetCheckRemote, handleVideosetCheckAllFiles, videosetEntryVideoSchema, filesOfVideosetEntry, } from "./videosetFixes.js";
|
|
22
|
+
export type { FixHandler, ModulePatch, FixHandlerContext, FixHandlerResult, IValRemote, ValidationEvent, ValidationError, ValModule, } from "./fixHandlers.js";
|
|
21
23
|
export * from "./jwt.js";
|
|
22
24
|
export type { ValServer } from "./ValServer.js";
|
|
23
25
|
export { getSettings } from "./getSettings.js";
|
|
24
26
|
export { getPersonalAccessTokenPath, parsePersonalAccessTokenFile, } from "./personalAccessTokens.js";
|
|
25
27
|
export { uploadRemoteFile } from "./uploadRemoteFile.js";
|
|
26
|
-
export { extractImageMetadata, extractFileMetadata } from "./extractMetadata.js";
|
|
28
|
+
export { extractImageMetadata, extractFileMetadata, extractVideoMetadata, extractVideoMetadataFromFile, } from "./extractMetadata.js";
|
|
29
|
+
export { filesOfVideo } from "./videoFiles.js";
|
|
27
30
|
export { validateMetadata } from "./validateMetadata.js";
|
|
28
31
|
export { getValidationErrorFileRef } from "./getValidationErrorFileRef.js";
|
|
29
32
|
export { checkRemoteRef, downloadFileFromRemote, getCachedRemoteFileDir, getCachedRemoteFilePath, } from "./checkRemoteRef.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The size and length of an ISO base media file (`.mp4`, `.m4v`, `.mov`),
|
|
3
|
+
* read from its headers. Nothing is decoded.
|
|
4
|
+
*
|
|
5
|
+
* Hand-written rather than a media library, deliberately: `@valbuild/server`
|
|
6
|
+
* is loaded by every app that runs Val, and the four numbers wanted here are
|
|
7
|
+
* three small boxes away from the start of the `moov` box:
|
|
8
|
+
*
|
|
9
|
+
* moov > mvhd timescale and duration (version 0 and 1)
|
|
10
|
+
* moov > mvex > mehd duration of a FRAGMENTED file, whose mvhd says 0
|
|
11
|
+
* moov > trak > tkhd width and height (16.16 fixed point) and the
|
|
12
|
+
* display matrix, for a rotated phone video
|
|
13
|
+
* moov > trak > mdia > hdlr `vide`: which trak is the picture
|
|
14
|
+
*
|
|
15
|
+
* Only the box headers are read on the way to `moov`, and then `moov` itself,
|
|
16
|
+
* so a multi-GB file costs a few small reads: `mdat` is seeked past, never
|
|
17
|
+
* loaded. That is what {@link ByteSource} is for.
|
|
18
|
+
*/
|
|
19
|
+
/** Random access to a file's bytes, so a large `mdat` can be skipped. */
|
|
20
|
+
export type ByteSource = {
|
|
21
|
+
readonly size: number;
|
|
22
|
+
/** Up to `length` bytes from `offset`; fewer only at the end of the file. */
|
|
23
|
+
read(offset: number, length: number): Buffer;
|
|
24
|
+
};
|
|
25
|
+
export declare function bufferByteSource(buffer: Buffer): ByteSource;
|
|
26
|
+
/**
|
|
27
|
+
* Run `read` against a file on disk, reading only what it asks for. The file
|
|
28
|
+
* is closed afterwards whatever happens.
|
|
29
|
+
*/
|
|
30
|
+
export declare function withFileByteSource<T>(absolutePath: string, read: (source: ByteSource) => T): T;
|
|
31
|
+
export type IsoBmffMetadata = {
|
|
32
|
+
width?: number;
|
|
33
|
+
height?: number;
|
|
34
|
+
/** Seconds, unrounded. */
|
|
35
|
+
duration?: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Whatever of {@link IsoBmffMetadata} the file declares. A field that cannot be
|
|
39
|
+
* read is absent; nothing here throws on a file that is not ISO BMFF.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readIsoBmffMetadata(source: ByteSource): IsoBmffMetadata;
|
|
@@ -24,6 +24,14 @@ import { ValOpsMemory } from "./ValOpsMemory.js";
|
|
|
24
24
|
* exists to avoid.
|
|
25
25
|
*/
|
|
26
26
|
export declare const DEFAULT_VAL_BUILD_URL = "https://admin.val.build";
|
|
27
|
+
/**
|
|
28
|
+
* What the Val app says when a project does not store its media remotely.
|
|
29
|
+
*
|
|
30
|
+
* Exported so that `val publish` -- which publishes to the app, and so is
|
|
31
|
+
* the last place to catch this before a build goes out -- refuses with the
|
|
32
|
+
* same sentence the deployed server would.
|
|
33
|
+
*/
|
|
34
|
+
export declare const APP_MODE_REQUIRES_REMOTE_FILES: string;
|
|
27
35
|
/**
|
|
28
36
|
* Resolve options plus environment into a concrete {@link ValServerConfig}.
|
|
29
37
|
*
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every file a video value names, as refs (`/public/…`, or a remote URL as
|
|
3
|
+
* written): the video, its poster, each caption track — and, for a LOCAL HLS
|
|
4
|
+
* stream, every file its playlists name, followed from the master down through
|
|
5
|
+
* each media playlist to the segment files, init sections and audio tracks.
|
|
6
|
+
*
|
|
7
|
+
* Followed rather than "everything in the master's directory": the Studio
|
|
8
|
+
* gives each stream a directory of its own, but nothing stops a hand-placed
|
|
9
|
+
* `/public/val/clip.m3u8`, and the directory of THAT one is every file in
|
|
10
|
+
* `/public/val`. A file in a stream's directory that no playlist names is not
|
|
11
|
+
* used by the stream either, so calling it unused is right.
|
|
12
|
+
*
|
|
13
|
+
* A remote stream's playlists are on the content host, so its master is all
|
|
14
|
+
* that is named here.
|
|
15
|
+
*
|
|
16
|
+
* The one place that answers this, so `list-unused-files` and anything else
|
|
17
|
+
* asking "is this file used" agree on what a video holds.
|
|
18
|
+
*/
|
|
19
|
+
export declare function filesOfVideo(video: unknown, options: {
|
|
20
|
+
projectRoot: string;
|
|
21
|
+
readFile?: (absolutePath: string) => Buffer | undefined;
|
|
22
|
+
}): string[];
|