@anandaramr/obscura 1.1.0 → 1.2.0
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/README.md +34 -17
- package/dist/cache.d.ts +8 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +61 -0
- package/dist/cache.js.map +1 -0
- package/dist/config.d.ts +4 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -1
- package/dist/config.js.map +1 -1
- package/dist/file.d.ts +1 -0
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +25 -10
- package/dist/file.js.map +1 -1
- package/dist/index.js +110 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/lib-ffmpeg.d.ts +2 -0
- package/dist/lib/lib-ffmpeg.d.ts.map +1 -0
- package/dist/lib/lib-ffmpeg.js +19 -0
- package/dist/lib/lib-ffmpeg.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +74 -29
- package/dist/server.js.map +1 -1
- package/dist/thumbnail.d.ts +1 -1
- package/dist/thumbnail.d.ts.map +1 -1
- package/dist/thumbnail.js +3 -3
- package/dist/thumbnail.js.map +1 -1
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +17 -0
- package/dist/utils.js.map +1 -1
- package/package.json +8 -4
- package/public/app.js +36 -12
- package/API.md +0 -93
package/README.md
CHANGED
|
@@ -17,44 +17,60 @@ You can install Obscura globally to invoke it directly from your terminal, or ex
|
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
npm install -g @anandaramr/obscura
|
|
20
|
-
|
|
21
20
|
```
|
|
22
21
|
|
|
22
|
+
> **Note:** Installation includes `ffmpeg-static` (~77MB), a bundled ffmpeg binary required for processing video thumbnails. If you already have ffmpeg installed on your system, you can skip it by running `npm install -g @anandaramr/obscura --no-optional` and set the `FFMPEG_PATH` environment variable to your ffmpeg binary path.
|
|
23
|
+
|
|
23
24
|
### Direct Execution (Without Installation)
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
27
|
npx @anandaramr/obscura [directory]
|
|
27
|
-
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
## Usage
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
### Starting the server
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
obscura
|
|
35
|
+
obscura [directory] [options]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Serves the specified directory (defaults to the current directory). Open the displayed URL on any device in your local network to browse the gallery.
|
|
36
39
|
|
|
40
|
+
```bash
|
|
41
|
+
obscura /path/to/media
|
|
42
|
+
obscura -p 8080 # custom port
|
|
43
|
+
obscura -a 192.168.1.10 /path/to/media # bind to specific interface
|
|
37
44
|
```
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
| Flag | Description | Default |
|
|
47
|
+
|------|-------------|---------|
|
|
48
|
+
| `-a, --address <ip>` | Address to bind to | `0.0.0.0` |
|
|
49
|
+
| `-p, --port <number>` | Port to listen on | `4963` |
|
|
50
|
+
| `--disk-concurrency <number>` | Max concurrent disk operations | `3` |
|
|
51
|
+
| `-v, --version` | Print version | |
|
|
52
|
+
| `-h, --help` | Show help | |
|
|
53
|
+
|
|
54
|
+
### Indexing a directory
|
|
40
55
|
|
|
41
|
-
|
|
56
|
+
Pre-build the cache for a directory. Useful for large libraries.
|
|
42
57
|
|
|
43
58
|
```bash
|
|
44
|
-
|
|
59
|
+
obscura index /path/to/media
|
|
60
|
+
obscura index /path/to/media --refresh # rebuild, overwriting existing cache
|
|
61
|
+
```
|
|
45
62
|
|
|
46
|
-
|
|
63
|
+
| Flag | Description |
|
|
64
|
+
|------|-------------|
|
|
65
|
+
| `-r, --refresh` | Overwrite existing cache entries |
|
|
47
66
|
|
|
48
|
-
|
|
49
|
-
directory Directory to serve (default: ".")
|
|
67
|
+
### Managing the cache
|
|
50
68
|
|
|
51
|
-
|
|
52
|
-
-v, --version output the version number
|
|
53
|
-
-a, --address <ip> Address to bind to (default: "0.0.0.0")
|
|
54
|
-
-p, --port <number> Port to listen to (default: "4963")
|
|
55
|
-
--disk-concurrency <number> Maximum number of concurrent disk operations (default: "3")
|
|
56
|
-
-h, --help display help for command
|
|
69
|
+
Inspect and manage the application cache.
|
|
57
70
|
|
|
71
|
+
```bash
|
|
72
|
+
obscura cache stats # show cache size and storage statistics
|
|
73
|
+
obscura cache clean # delete all cached data
|
|
58
74
|
```
|
|
59
75
|
|
|
60
76
|
## Environment Variables
|
|
@@ -66,7 +82,8 @@ Obscura natively honors standard system environment variables or values declared
|
|
|
66
82
|
| `DIRECTORY` | Absolute or relative path to target media directory |
|
|
67
83
|
| `ADDRESS` | Network IP address to bind server instance onto |
|
|
68
84
|
| `PORT` | Local network port to open for the server instance |
|
|
69
|
-
| `DISK_CONCURRENCY` | Maximum allowed parallel disk I/O operational ceiling
|
|
85
|
+
| `DISK_CONCURRENCY` | Maximum allowed parallel disk I/O operational ceiling |
|
|
86
|
+
| `FFMPEG_PATH` | Path to ffmpeg binary (if it exists; unnecessary if installed without `--no-optional` flag) |
|
|
70
87
|
|
|
71
88
|
## API
|
|
72
89
|
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FileMetaData } from "./types.js";
|
|
2
|
+
export declare const THUMBS_DIR: string;
|
|
3
|
+
export declare function getCacheSize(): Promise<number>;
|
|
4
|
+
export declare function emptyCache(): Promise<number>;
|
|
5
|
+
export declare function shouldAvoidCaching(file: FileMetaData, threshold: number): boolean;
|
|
6
|
+
export declare function getThumbPath(fileId: string): string;
|
|
7
|
+
export declare function isAlreadyCached(fileId: string): boolean;
|
|
8
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,eAAO,MAAM,UAAU,QAAkD,CAAA;AAEzE,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAkBpD;AAED,wBAAsB,UAAU,oBAoB/B;AAaD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,WAEvE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,UAE1C;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,WAE7C"}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import fsSync from "fs";
|
|
4
|
+
import { defaults, PROJECT_ROOT } from "./config.js";
|
|
5
|
+
export const THUMBS_DIR = path.resolve(PROJECT_ROOT, defaults.THUMBS_DIR);
|
|
6
|
+
export async function getCacheSize() {
|
|
7
|
+
let totalSize = 0;
|
|
8
|
+
let files;
|
|
9
|
+
try {
|
|
10
|
+
files = await fs.readdir(THUMBS_DIR, { withFileTypes: true });
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
if (compareErrorCode(e, "ENOENT"))
|
|
14
|
+
return 0;
|
|
15
|
+
throw e;
|
|
16
|
+
}
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
const fullPath = path.join(THUMBS_DIR, file.name);
|
|
19
|
+
const stats = await fs.stat(fullPath);
|
|
20
|
+
totalSize += stats.size;
|
|
21
|
+
}
|
|
22
|
+
return totalSize;
|
|
23
|
+
}
|
|
24
|
+
export async function emptyCache() {
|
|
25
|
+
let totalSize = 0;
|
|
26
|
+
let files;
|
|
27
|
+
try {
|
|
28
|
+
files = await fs.readdir(THUMBS_DIR);
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
if (compareErrorCode(e, "ENOENT"))
|
|
32
|
+
return 0;
|
|
33
|
+
throw e;
|
|
34
|
+
}
|
|
35
|
+
for (const file of files) {
|
|
36
|
+
const fullPath = path.join(THUMBS_DIR, file);
|
|
37
|
+
const stats = await fs.stat(fullPath);
|
|
38
|
+
totalSize += stats.size;
|
|
39
|
+
await fs.rm(fullPath, { force: true });
|
|
40
|
+
}
|
|
41
|
+
return totalSize;
|
|
42
|
+
}
|
|
43
|
+
function compareErrorCode(e, code) {
|
|
44
|
+
if (typeof e === "object" && e !== null && "code" in e) {
|
|
45
|
+
const err = e;
|
|
46
|
+
if (err.code === code) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
export function shouldAvoidCaching(file, threshold) {
|
|
53
|
+
return file.type == "image" && file.size < threshold && !file.isAnimated;
|
|
54
|
+
}
|
|
55
|
+
export function getThumbPath(fileId) {
|
|
56
|
+
return path.join(THUMBS_DIR, `${fileId}.jpg`);
|
|
57
|
+
}
|
|
58
|
+
export function isAlreadyCached(fileId) {
|
|
59
|
+
return fsSync.existsSync(getThumbPath(fileId));
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
ADDRESS: string;
|
|
5
|
-
PORT: number;
|
|
6
|
-
THUMB_SIZE: number;
|
|
7
|
-
IMG_CACHE_THRESHOLD: number;
|
|
8
|
-
DISK_CONCURRENCY: number;
|
|
9
|
-
};
|
|
1
|
+
import type { DefaultConfig } from "./types.js";
|
|
2
|
+
export declare const PROJECT_ROOT: string;
|
|
3
|
+
export declare const defaults: DefaultConfig;
|
|
10
4
|
export declare function parsePort(p: string): number;
|
|
11
5
|
export declare function parseDiskConcurrency(conc: string): number;
|
|
12
6
|
export declare function parseDirectory(dir: string): string;
|
|
13
7
|
export declare function parseAddress(ip: string): string;
|
|
8
|
+
export declare function getFfmpegPath(): Promise<string>;
|
|
14
9
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAK/C,eAAO,MAAM,YAAY,QAA+C,CAAA;AAExE,eAAO,MAAM,QAAQ,EAAE,aAStB,CAAA;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,UAMlC;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,UAMhD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,UAOzC;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,UAOtC;AAED,wBAAsB,aAAa,oBAElC"}
|
package/dist/config.js
CHANGED
|
@@ -2,6 +2,10 @@ import { InvalidArgumentError, InvalidOptionArgumentError } from "commander";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { validateDirectory } from "./file.js";
|
|
4
4
|
import net from "net";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { getFfmpegStaticPath } from "./lib/lib-ffmpeg.js";
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
export const PROJECT_ROOT = path.resolve(path.dirname(__filename), "..");
|
|
5
9
|
export const defaults = {
|
|
6
10
|
DIRECTORY: ".",
|
|
7
11
|
THUMBS_DIR: "thumbs",
|
|
@@ -34,9 +38,12 @@ export function parseDirectory(dir) {
|
|
|
34
38
|
return galleryDir;
|
|
35
39
|
}
|
|
36
40
|
export function parseAddress(ip) {
|
|
37
|
-
if (!ip || (!net.isIPv4(ip) && ip !==
|
|
41
|
+
if (!ip || (!net.isIPv4(ip) && ip !== "localhost")) {
|
|
38
42
|
throw new InvalidOptionArgumentError(`Invalid IP address \"${ip}\". Should be an IPv4 address`);
|
|
39
43
|
}
|
|
40
44
|
return ip;
|
|
41
45
|
}
|
|
46
|
+
export async function getFfmpegPath() {
|
|
47
|
+
return process.env.FFMPEG_PATH || getFfmpegStaticPath();
|
|
48
|
+
}
|
|
42
49
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAA;AAC5E,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,GAAG,MAAM,KAAK,CAAA;AAErB,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAA;AAC5E,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,GAAG,MAAM,KAAK,CAAA;AAErB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAEzD,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACjD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAA;AAExE,MAAM,CAAC,MAAM,QAAQ,GAAkB;IACnC,SAAS,EAAE,GAAG;IACd,UAAU,EAAE,QAAQ;IACpB,OAAO,EAAE,SAAS;IAClB,IAAI,EAAE,IAAI;IAEV,UAAU,EAAE,GAAG;IACf,mBAAmB,EAAE,IAAI,GAAG,IAAI;IAChC,gBAAgB,EAAE,CAAC;CACtB,CAAA;AAED,MAAM,UAAU,SAAS,CAAC,CAAS;IAC/B,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;IACtB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;QACtD,MAAM,IAAI,0BAA0B,CAAC,6CAA6C,CAAC,CAAA;IACvF,CAAC;IACD,OAAO,IAAI,CAAA;AACf,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC7C,MAAM,eAAe,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;IACpC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,eAAe,CAAC,IAAI,eAAe,GAAG,CAAC,EAAE,CAAC;QAC5D,MAAM,IAAI,0BAA0B,CAAC,yCAAyC,CAAC,CAAA;IACnF,CAAC;IACD,OAAO,eAAe,CAAA;AAC1B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAW;IACtC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,CAAA;IACnD,MAAM,EAAE,KAAK,EAAE,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAA;IAC/C,IAAI,KAAK,EAAE,CAAC;QACR,MAAM,IAAI,oBAAoB,CAAC,KAAK,CAAC,CAAA;IACzC,CAAC;IACD,OAAO,UAAU,CAAA;AACrB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,EAAU;IACnC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,WAAW,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,0BAA0B,CAChC,wBAAwB,EAAE,+BAA+B,CAC5D,CAAA;IACL,CAAC;IACD,OAAO,EAAE,CAAA;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IAC/B,OAAO,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,mBAAmB,EAAE,CAAA;AAC3D,CAAC"}
|
package/dist/file.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ import type { Result, FileMetaData } from "./types.js";
|
|
|
2
2
|
export declare function validateDirectory(absoluteDirPath: string): Result<void, string>;
|
|
3
3
|
export declare function parseFileMetadata(filePath: string): Promise<FileMetaData | null>;
|
|
4
4
|
export declare function generateHash(fullPath: string): string;
|
|
5
|
+
export declare function getFiles(directory: string, onScan?: (scanned: number) => void): Promise<FileMetaData[]>;
|
|
5
6
|
//# sourceMappingURL=file.d.ts.map
|
package/dist/file.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAyBtD,wBAAgB,iBAAiB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAc/E;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAqBtF;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,UAE5C;AAWD,wBAAsB,QAAQ,CAC1B,SAAS,EAAE,MAAM,EACjB,MAAM,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAe,GAC7C,OAAO,CAAC,YAAY,EAAE,CAAC,CAqBzB"}
|
package/dist/file.js
CHANGED
|
@@ -12,16 +12,9 @@ const IMAGE_EXTS = [
|
|
|
12
12
|
".heif",
|
|
13
13
|
".tiff",
|
|
14
14
|
".tif",
|
|
15
|
-
".avif"
|
|
16
|
-
];
|
|
17
|
-
const VIDEO_EXTS = [
|
|
18
|
-
".mp4",
|
|
19
|
-
".mov",
|
|
20
|
-
".mkv",
|
|
21
|
-
".webm",
|
|
22
|
-
".m4v",
|
|
23
|
-
".m2ts",
|
|
15
|
+
".avif"
|
|
24
16
|
];
|
|
17
|
+
const VIDEO_EXTS = [".mp4", ".mov", ".mkv", ".webm", ".m4v", ".m2ts"];
|
|
25
18
|
const ALWAYS_ANIMATED_EXTS = new Set([".gif"]);
|
|
26
19
|
const SOMETIMES_ANIMATED = new Set([".webp", ".avif", ".png"]);
|
|
27
20
|
export function validateDirectory(absoluteDirPath) {
|
|
@@ -56,7 +49,7 @@ export async function parseFileMetadata(filePath) {
|
|
|
56
49
|
date: stat.mtime,
|
|
57
50
|
size: stat.size,
|
|
58
51
|
ext: ext,
|
|
59
|
-
isAnimated: animated
|
|
52
|
+
isAnimated: animated
|
|
60
53
|
};
|
|
61
54
|
}
|
|
62
55
|
export function generateHash(fullPath) {
|
|
@@ -71,4 +64,26 @@ async function isAnimated(filePath) {
|
|
|
71
64
|
return false;
|
|
72
65
|
}
|
|
73
66
|
}
|
|
67
|
+
export async function getFiles(directory, onScan = () => { }) {
|
|
68
|
+
const files = await fs.promises.readdir(directory);
|
|
69
|
+
const result = [];
|
|
70
|
+
for (const entry of files) {
|
|
71
|
+
const fullPath = path.resolve(directory, entry);
|
|
72
|
+
const stat = await fs.promises.lstat(fullPath);
|
|
73
|
+
if (stat.isSymbolicLink())
|
|
74
|
+
continue;
|
|
75
|
+
if (stat.isDirectory()) {
|
|
76
|
+
const files = await getFiles(fullPath, scanned => onScan(result.length + scanned));
|
|
77
|
+
result.push(...files);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const file = await parseFileMetadata(fullPath);
|
|
81
|
+
if (!file)
|
|
82
|
+
continue;
|
|
83
|
+
result.push(file);
|
|
84
|
+
onScan(result.length);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
74
89
|
//# sourceMappingURL=file.js.map
|
package/dist/file.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.js","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":"AAEA,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,MAAM,MAAM,QAAQ,CAAA;AAC3B,OAAO,QAAQ,MAAM,oBAAoB,CAAA;AAEzC,MAAM,UAAU,GAAG;IACf,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,OAAO;CACV,CAAA;AAED,MAAM,UAAU,GAAG
|
|
1
|
+
{"version":3,"file":"file.js","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":"AAEA,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,MAAM,MAAM,QAAQ,CAAA;AAC3B,OAAO,QAAQ,MAAM,oBAAoB,CAAA;AAEzC,MAAM,UAAU,GAAG;IACf,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,OAAO;CACV,CAAA;AAED,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;AAErE,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;AAC9C,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;AAE9D,MAAM,UAAU,iBAAiB,CAAC,eAAuB;IACrD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QACpC,OAAO,EAAE,KAAK,EAAE,mDAAmD,EAAE,CAAA;IACzE,CAAC;IACD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QAClC,OAAO,EAAE,KAAK,EAAE,kDAAkD,EAAE,CAAA;IACxE,CAAC;IAED,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAA;IAC1C,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QACvB,OAAO,EAAE,KAAK,EAAE,8CAA8C,EAAE,CAAA;IACpE,CAAC;IAED,OAAO,EAAE,CAAA;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAAgB;IACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAA;IAChD,IAAI,CAAC,CAAC,GAAG,UAAU,EAAE,GAAG,UAAU,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAE9D,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAElC,IAAI,QAAQ,GAAG,KAAK,CAAA;IACpB,IAAI,oBAAoB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,QAAQ,GAAG,IAAI,CAAA;SAC7C,IAAI,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,QAAQ,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAA;IAE3E,OAAO;QACH,EAAE,EAAE,YAAY,CAAC,QAAQ,CAAC;QAC1B,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO;QAClD,IAAI,EAAE,IAAI,CAAC,KAAK;QAChB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,EAAE,GAAG;QACR,UAAU,EAAE,QAAQ;KACvB,CAAA;AACL,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,QAAgB;IACzC,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAClE,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,QAAgB;IACtC,IAAI,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAA;QACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;IACpC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,OAAO,KAAK,CAAA;IAChB,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC1B,SAAiB,EACjB,SAAoC,GAAG,EAAE,GAAE,CAAC;IAE5C,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAClD,MAAM,MAAM,GAAmB,EAAE,CAAA;IAEjC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;QAC/C,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;QAC9C,IAAI,IAAI,CAAC,cAAc,EAAE;YAAE,SAAQ;QAEnC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACrB,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC,CAAA;YAClF,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAA;QACzB,CAAC;aAAM,CAAC;YACJ,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,QAAQ,CAAC,CAAA;YAC9C,IAAI,CAAC,IAAI;gBAAE,SAAQ;YACnB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACjB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACzB,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAA;AACjB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import dotenv from "dotenv";
|
|
3
3
|
dotenv.config({ quiet: true });
|
|
4
|
+
import path from "path";
|
|
5
|
+
import pLimit from "p-limit";
|
|
4
6
|
import { Command } from "commander";
|
|
5
7
|
import manifest from "../package.json" with { type: "json" };
|
|
6
|
-
import { defaults, parseAddress, parseDirectory, parseDiskConcurrency, parsePort } from "./config.js";
|
|
8
|
+
import { defaults, getFfmpegPath, parseAddress, parseDirectory, parseDiskConcurrency, parsePort, PROJECT_ROOT } from "./config.js";
|
|
7
9
|
import startServer from "./server.js";
|
|
10
|
+
import { emptyCache, getCacheSize, THUMBS_DIR, shouldAvoidCaching, getThumbPath, isAlreadyCached } from "./cache.js";
|
|
11
|
+
import { bytesToString } from "./utils.js";
|
|
12
|
+
import { getFiles } from "./file.js";
|
|
13
|
+
import { generateImageThumbnail, generateVideoThumbnail } from "./thumbnail.js";
|
|
8
14
|
const program = new Command();
|
|
9
15
|
program
|
|
10
16
|
.name("obscura")
|
|
@@ -20,6 +26,7 @@ program
|
|
|
20
26
|
const port = parsePort(options.port);
|
|
21
27
|
const diskConcurrency = parseDiskConcurrency(options.diskConcurrency);
|
|
22
28
|
const address = parseAddress(options.address);
|
|
29
|
+
const ffmpegPath = await getFfmpegPath();
|
|
23
30
|
try {
|
|
24
31
|
const config = {
|
|
25
32
|
galleryDir: galleryDir,
|
|
@@ -27,7 +34,8 @@ program
|
|
|
27
34
|
port: port,
|
|
28
35
|
thumbSize: defaults.THUMB_SIZE,
|
|
29
36
|
imgCacheThreshold: defaults.IMG_CACHE_THRESHOLD,
|
|
30
|
-
diskConcurrency: diskConcurrency
|
|
37
|
+
diskConcurrency: diskConcurrency,
|
|
38
|
+
ffmpegPath: ffmpegPath
|
|
31
39
|
};
|
|
32
40
|
const close = await startServer(config);
|
|
33
41
|
process.on("SIGINT", async () => {
|
|
@@ -41,5 +49,105 @@ program
|
|
|
41
49
|
process.exit(1);
|
|
42
50
|
}
|
|
43
51
|
});
|
|
52
|
+
const cache = program.command("cache").description("Manage and inspect the application cache");
|
|
53
|
+
cache
|
|
54
|
+
.command("stats")
|
|
55
|
+
.description("Show cache usage and storage statistics")
|
|
56
|
+
.action(async () => {
|
|
57
|
+
console.log(`Cache directory: \x1b[36m${THUMBS_DIR}\x1b[0m`);
|
|
58
|
+
const cacheSize = await getCacheSize();
|
|
59
|
+
console.log(`Cache size: ${bytesToString(cacheSize)}`);
|
|
60
|
+
});
|
|
61
|
+
cache
|
|
62
|
+
.command("clean")
|
|
63
|
+
.description("Clear the cache directory")
|
|
64
|
+
.action(async () => {
|
|
65
|
+
try {
|
|
66
|
+
const clearedBytes = await emptyCache();
|
|
67
|
+
console.log(`Cache cleaned (Freed ${bytesToString(clearedBytes)})`);
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
console.log(`Error encountered: ${e}`);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
program
|
|
74
|
+
.command("index")
|
|
75
|
+
.description("Index directory")
|
|
76
|
+
.argument("[directory]", "Directory to be indexed", ".")
|
|
77
|
+
.option("-r, --refresh", "Overwrite existing cache")
|
|
78
|
+
.action(async (dir, options) => {
|
|
79
|
+
const directory = path.resolve(PROJECT_ROOT, dir);
|
|
80
|
+
const diskConcurrency = parseDiskConcurrency(process.env.DISK_CONCURRENCY || String(defaults.DISK_CONCURRENCY));
|
|
81
|
+
const limit = pLimit(diskConcurrency);
|
|
82
|
+
const start = Date.now();
|
|
83
|
+
const throttled = createThrottler(500, 500);
|
|
84
|
+
let files = await getFiles(directory, scanned => {
|
|
85
|
+
throttled(() => process.stdout.write(`\x1b[2KScanned ${scanned} files\r`));
|
|
86
|
+
});
|
|
87
|
+
const totalFiles = files.length;
|
|
88
|
+
files = files.filter(f => !shouldAvoidCaching(f, defaults.IMG_CACHE_THRESHOLD));
|
|
89
|
+
const filesToBeCached = files.length;
|
|
90
|
+
process.stdout.write(`\x1b[2KFound ${totalFiles} files (${filesToBeCached} to be cached)\n\n`);
|
|
91
|
+
let completed = 0;
|
|
92
|
+
const ffmpegPath = await getFfmpegPath();
|
|
93
|
+
const writer = progressWriter(100);
|
|
94
|
+
const tasks = files.map(file => limit(async () => {
|
|
95
|
+
await cacheFile(file, ffmpegPath, { refresh: options.refresh });
|
|
96
|
+
completed++;
|
|
97
|
+
writer(completed, filesToBeCached);
|
|
98
|
+
}));
|
|
99
|
+
await Promise.all(tasks);
|
|
100
|
+
process.stdout.write(`\x1b[2K\x1b[2A\x1b[2K\nIndexed ${totalFiles} files from \x1b[36m${directory}\x1b[0m (${filesToBeCached} cached)\n`);
|
|
101
|
+
const duration = (Date.now() - start) / 1000;
|
|
102
|
+
console.log(`Completed in ${duration.toFixed(2)}s`);
|
|
103
|
+
});
|
|
104
|
+
function progressWriter(interval) {
|
|
105
|
+
let lastWriteTime = 0;
|
|
106
|
+
let lastValue = 0;
|
|
107
|
+
const INFINITY_SYMBOL = `\u221e`;
|
|
108
|
+
return (current, total) => {
|
|
109
|
+
const now = Date.now();
|
|
110
|
+
if (now - lastWriteTime < interval)
|
|
111
|
+
return;
|
|
112
|
+
const perc = Math.round((current / total) * 100);
|
|
113
|
+
const rate = (current - lastValue) / (now - lastWriteTime);
|
|
114
|
+
const eta = rate !== 0 && lastValue > 3
|
|
115
|
+
? ((total - current) / (rate * 1000)).toFixed(2) + "s"
|
|
116
|
+
: INFINITY_SYMBOL;
|
|
117
|
+
const width = 30;
|
|
118
|
+
const bars = Math.floor((width * current) / total);
|
|
119
|
+
const progressBar = `[${"#".repeat(bars).padEnd(width)}]`;
|
|
120
|
+
process.stdout.write(`\x1b[2K${progressBar} ${current}/${total} (${perc}%) | ETA ${eta}\r`);
|
|
121
|
+
lastWriteTime = now;
|
|
122
|
+
lastValue = current;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function createThrottler(interval, delay) {
|
|
126
|
+
let lastUpdate = 0;
|
|
127
|
+
let start = undefined;
|
|
128
|
+
return (cb) => {
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
if (!start)
|
|
131
|
+
start = now;
|
|
132
|
+
if (now - lastUpdate < interval)
|
|
133
|
+
return;
|
|
134
|
+
if (now - start < delay)
|
|
135
|
+
return;
|
|
136
|
+
cb();
|
|
137
|
+
lastUpdate = now;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
async function cacheFile(file, ffmpegPath, opts = { refresh: false }) {
|
|
141
|
+
const thumbPath = getThumbPath(file.id);
|
|
142
|
+
// Skip already cached files unless in refresh mode
|
|
143
|
+
if (!opts.refresh && isAlreadyCached(file.id))
|
|
144
|
+
return;
|
|
145
|
+
if (file.type === "image") {
|
|
146
|
+
await generateImageThumbnail(file, thumbPath, defaults.THUMB_SIZE);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
await generateVideoThumbnail(ffmpegPath, file, thumbPath, defaults.THUMB_SIZE);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
44
152
|
program.parse(process.argv);
|
|
45
153
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAC3B,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AAE9B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib-ffmpeg.d.ts","sourceRoot":"","sources":["../../src/lib/lib-ffmpeg.ts"],"names":[],"mappings":"AAEA,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAc3D"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
let ffmpegPath = null;
|
|
2
|
+
export async function getFfmpegStaticPath() {
|
|
3
|
+
if (ffmpegPath)
|
|
4
|
+
return ffmpegPath;
|
|
5
|
+
try {
|
|
6
|
+
const ffmpeg = await import("ffmpeg-static");
|
|
7
|
+
ffmpegPath = typeof ffmpeg === "string" ? ffmpeg : ffmpeg.default;
|
|
8
|
+
return ffmpegPath;
|
|
9
|
+
}
|
|
10
|
+
catch (e) {
|
|
11
|
+
if (e.code === "ERR_MODULE_NOT_FOUND") {
|
|
12
|
+
throw new Error("ffmpeg was not installed as a dependency");
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
throw e;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=lib-ffmpeg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib-ffmpeg.js","sourceRoot":"","sources":["../../src/lib/lib-ffmpeg.ts"],"names":[],"mappings":"AAAA,IAAI,UAAU,GAAkB,IAAI,CAAA;AAEpC,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACrC,IAAI,UAAU;QAAE,OAAO,UAAU,CAAA;IAEjC,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAA;QAC5C,UAAU,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,MAAc,CAAC,OAAO,CAAA;QAC1E,OAAO,UAAoB,CAAA;IAC/B,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QACd,IAAI,CAAC,CAAC,IAAI,KAAK,sBAAsB,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;QAC/D,CAAC;aAAM,CAAC;YACJ,MAAM,CAAC,CAAA;QACX,CAAC;IACL,CAAC;AACL,CAAC"}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAoC,YAAY,EAAa,MAAM,YAAY,CAAA;AAc3F,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAyMtF"}
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import express from "express";
|
|
2
|
+
import cors from "cors";
|
|
2
3
|
import os from "os";
|
|
3
4
|
import chokidar from "chokidar";
|
|
4
5
|
import pLimit from "p-limit";
|
|
@@ -8,20 +9,19 @@ import { parseFileMetadata, generateHash } from "./file.js";
|
|
|
8
9
|
import logger from "./logger.js";
|
|
9
10
|
import { generateImageThumbnail, generateVideoThumbnail } from "./thumbnail.js";
|
|
10
11
|
import { insertSorted } from "./utils.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
-
const PROJECT_ROOT = path.resolve(path.dirname(__filename), "..");
|
|
12
|
+
import { getThumbPath, THUMBS_DIR, shouldAvoidCaching } from "./cache.js";
|
|
13
|
+
import { PROJECT_ROOT } from "./config.js";
|
|
15
14
|
function createApp() {
|
|
16
15
|
const app = express();
|
|
17
16
|
app.use(express.json());
|
|
18
17
|
app.use(logger());
|
|
18
|
+
app.use(cors());
|
|
19
19
|
app.use(express.static(path.join(PROJECT_ROOT, "public")));
|
|
20
20
|
return app;
|
|
21
21
|
}
|
|
22
22
|
export default function startServer(config) {
|
|
23
|
+
const ffmpegPath = config.ffmpegPath;
|
|
23
24
|
const limit = pLimit(config.diskConcurrency);
|
|
24
|
-
const THUMBS_DIR = path.resolve(PROJECT_ROOT, defaults.THUMBS_DIR);
|
|
25
25
|
if (!fs.existsSync(THUMBS_DIR))
|
|
26
26
|
fs.mkdirSync(THUMBS_DIR, { recursive: true });
|
|
27
27
|
let filesMap = new Map();
|
|
@@ -37,6 +37,14 @@ export default function startServer(config) {
|
|
|
37
37
|
client.res.write(`data: ${JSON.stringify({ action, file: fileData })}\n\n`);
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
+
function toClientFile(file) {
|
|
41
|
+
const { id, name, type, date, size, isAnimated } = file;
|
|
42
|
+
return { id, name, type, date, size, isAnimated };
|
|
43
|
+
}
|
|
44
|
+
function insertToSortedFiles(file) {
|
|
45
|
+
const clientFile = toClientFile(file);
|
|
46
|
+
insertSorted(sortedFiles, clientFile, file => new Date(file.date).getTime(), (a, b) => b - a);
|
|
47
|
+
}
|
|
40
48
|
let isBooting = true;
|
|
41
49
|
watcher.on("add", async (filePath) => {
|
|
42
50
|
const fileData = await parseFileMetadata(filePath);
|
|
@@ -46,12 +54,24 @@ export default function startServer(config) {
|
|
|
46
54
|
filesMap.set(fileData.id, fileData);
|
|
47
55
|
if (isExisting)
|
|
48
56
|
return;
|
|
49
|
-
|
|
50
|
-
const clientFileData = { id, name, type, date, size, isAnimated };
|
|
51
|
-
insertSorted(sortedFiles, clientFileData, file => new Date(file.date).getTime(), (a, b) => b - a);
|
|
57
|
+
insertToSortedFiles(fileData);
|
|
52
58
|
if (isBooting)
|
|
53
59
|
return;
|
|
54
|
-
broadcastToUsers("add",
|
|
60
|
+
broadcastToUsers("add", fileData);
|
|
61
|
+
});
|
|
62
|
+
watcher.on("change", async (filePath) => {
|
|
63
|
+
const file = await parseFileMetadata(filePath);
|
|
64
|
+
if (!file)
|
|
65
|
+
return;
|
|
66
|
+
sortedFiles = sortedFiles.filter(f => f.id !== file.id);
|
|
67
|
+
insertToSortedFiles(file);
|
|
68
|
+
try {
|
|
69
|
+
await refreshThumbnail(file);
|
|
70
|
+
broadcastToUsers("update", file);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
console.error(`Error while updating file metadat change: ${err}`);
|
|
74
|
+
}
|
|
55
75
|
});
|
|
56
76
|
watcher.on("unlink", filePath => {
|
|
57
77
|
const fileId = generateHash(filePath);
|
|
@@ -59,7 +79,7 @@ export default function startServer(config) {
|
|
|
59
79
|
filesMap.delete(fileId);
|
|
60
80
|
sortedFiles = sortedFiles.filter(file => file.id !== fileId);
|
|
61
81
|
broadcastToUsers("remove", { id: fileId });
|
|
62
|
-
const thumbPath = getThumbPath(
|
|
82
|
+
const thumbPath = getThumbPath(fileId);
|
|
63
83
|
fs.unlink(thumbPath, () => { });
|
|
64
84
|
}
|
|
65
85
|
});
|
|
@@ -91,28 +111,59 @@ export default function startServer(config) {
|
|
|
91
111
|
const file = filesMap.get(req.params.id);
|
|
92
112
|
if (!file)
|
|
93
113
|
return res.sendStatus(404);
|
|
114
|
+
try {
|
|
115
|
+
const thumbPath = await getOrCreateThumbnail(file);
|
|
116
|
+
res.sendFile(thumbPath);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
console.error(err);
|
|
120
|
+
res.sendStatus(500);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
async function getOrCreateThumbnail(file) {
|
|
94
124
|
if (shouldAvoidCaching(file, config.imgCacheThreshold)) {
|
|
95
|
-
return
|
|
125
|
+
return file.path;
|
|
96
126
|
}
|
|
97
|
-
const thumbPath = getThumbPath(
|
|
127
|
+
const thumbPath = getThumbPath(file.id);
|
|
98
128
|
try {
|
|
99
129
|
await limit(async () => {
|
|
100
|
-
if (fs.existsSync(thumbPath))
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
await generateVideoThumbnail(file, thumbPath, config.thumbSize);
|
|
130
|
+
if (fs.existsSync(thumbPath)) {
|
|
131
|
+
const stat = fs.statSync(thumbPath);
|
|
132
|
+
if (stat.mtime >= file.date)
|
|
133
|
+
return;
|
|
107
134
|
}
|
|
135
|
+
await createThumbnail(file, thumbPath);
|
|
108
136
|
});
|
|
109
|
-
|
|
137
|
+
return path.resolve(thumbPath);
|
|
110
138
|
}
|
|
111
139
|
catch (err) {
|
|
112
|
-
|
|
113
|
-
res.sendStatus(500);
|
|
140
|
+
throw new Error(`Error while creating thumbnail: ${err}`);
|
|
114
141
|
}
|
|
115
|
-
}
|
|
142
|
+
}
|
|
143
|
+
async function refreshThumbnail(file) {
|
|
144
|
+
const thumbPath = getThumbPath(file.id);
|
|
145
|
+
if (shouldAvoidCaching(file, config.imgCacheThreshold)) {
|
|
146
|
+
await fs.promises.rm(thumbPath, { force: true });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
await limit(async () => {
|
|
151
|
+
await createThumbnail(file, thumbPath);
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
throw new Error(`Error while creating thumbnail: ${err}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function createThumbnail(file, thumbPath) {
|
|
160
|
+
if (file.type === "image") {
|
|
161
|
+
await generateImageThumbnail(file, thumbPath, config.thumbSize);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
await generateVideoThumbnail(ffmpegPath, file, thumbPath, config.thumbSize);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
116
167
|
return new Promise((resolve, reject) => {
|
|
117
168
|
const server = app.listen(config.port, config.address, error => {
|
|
118
169
|
if (error) {
|
|
@@ -147,10 +198,4 @@ function logAddress(addr, port, name) {
|
|
|
147
198
|
const url = `http://${addr}:${port}`;
|
|
148
199
|
console.log(`- \x1b[36m${url.padEnd(30)}\x1b[0m ${name ? "[" + name + "]" : ""}`);
|
|
149
200
|
}
|
|
150
|
-
function getThumbPath(thumbsDir, fileId) {
|
|
151
|
-
return path.join(thumbsDir, `${fileId}.jpg`);
|
|
152
|
-
}
|
|
153
|
-
function shouldAvoidCaching(file, threshold) {
|
|
154
|
-
return file.type == "image" && file.size < threshold && !file.isAnimated;
|
|
155
|
-
}
|
|
156
201
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,QAAQ,MAAM,UAAU,CAAA;AAC/B,OAAO,MAAM,MAAM,SAAS,CAAA;AAE5B,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,MAAM,IAAI,CAAA;AAEnB,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC3D,OAAO,MAAM,MAAM,aAAa,CAAA;AAEhC,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAC/E,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAGzC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/thumbnail.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { FileMetaData } from "./types.js";
|
|
2
|
-
export declare function generateVideoThumbnail(file: FileMetaData, thumbPath: string, thumbSize: number): Promise<void>;
|
|
2
|
+
export declare function generateVideoThumbnail(ffmpegPath: string, file: FileMetaData, thumbPath: string, thumbSize: number): Promise<void>;
|
|
3
3
|
export declare function generateImageThumbnail(file: FileMetaData, thumbPath: string, thumbSize: number): Promise<void>;
|
|
4
4
|
//# sourceMappingURL=thumbnail.d.ts.map
|
package/dist/thumbnail.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thumbnail.d.ts","sourceRoot":"","sources":["../src/thumbnail.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"thumbnail.d.ts","sourceRoot":"","sources":["../src/thumbnail.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAK9C,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBAyBlH;AAED,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBAKpG"}
|
package/dist/thumbnail.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { execFile } from "child_process";
|
|
2
2
|
import libSharp from "./lib/lib-sharp.js";
|
|
3
|
-
|
|
4
|
-
const ffmpegPath = typeof ffmpeg === "string" ? ffmpeg : ffmpeg.default;
|
|
5
|
-
export function generateVideoThumbnail(file, thumbPath, thumbSize) {
|
|
3
|
+
export function generateVideoThumbnail(ffmpegPath, file, thumbPath, thumbSize) {
|
|
6
4
|
return new Promise((resolve, reject) => {
|
|
7
5
|
execFile(ffmpegPath, [
|
|
6
|
+
"-y", // allow overwrite
|
|
7
|
+
"-nostdin",
|
|
8
8
|
"-i",
|
|
9
9
|
file.path,
|
|
10
10
|
"-frames:v",
|
package/dist/thumbnail.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thumbnail.js","sourceRoot":"","sources":["../src/thumbnail.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,QAAQ,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"thumbnail.js","sourceRoot":"","sources":["../src/thumbnail.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,QAAQ,MAAM,oBAAoB,CAAA;AAEzC,MAAM,UAAU,sBAAsB,CAAC,UAAkB,EAAE,IAAkB,EAAE,SAAiB,EAAE,SAAiB;IAC/G,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACzC,QAAQ,CACJ,UAAU,EACV;YACI,IAAI,EAAE,kBAAkB;YACxB,UAAU;YACV,IAAI;YACJ,IAAI,CAAC,IAAI;YACT,WAAW;YACX,GAAG;YACH,KAAK;YACL,SAAS,SAAS,IAAI,SAAS,8CAA8C,SAAS,IAAI,SAAS,QAAQ,SAAS,WAAW,SAAS,KAAK;YAC7I,MAAM;YACN,GAAG;YACH,SAAS;SACZ,EACD,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;YACtB,IAAI,GAAG,EAAE,CAAC;gBACN,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,iBAAiB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;YAC5D,CAAC;YACD,OAAO,EAAE,CAAA;QACb,CAAC,CACJ,CAAA;IACL,CAAC,CAAC,CAAA;AACN,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,IAAkB,EAAE,SAAiB,EAAE,SAAiB;IACjG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;SAClD,MAAM,CAAC,SAAS,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;SAC9C,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;SACrB,MAAM,CAAC,SAAS,CAAC,CAAA;AAC1B,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -25,5 +25,18 @@ export interface ServerConfig {
|
|
|
25
25
|
thumbSize: number;
|
|
26
26
|
imgCacheThreshold: number;
|
|
27
27
|
diskConcurrency: number;
|
|
28
|
+
ffmpegPath: string;
|
|
29
|
+
}
|
|
30
|
+
export interface DefaultConfig {
|
|
31
|
+
DIRECTORY: string;
|
|
32
|
+
THUMBS_DIR: string;
|
|
33
|
+
ADDRESS: string;
|
|
34
|
+
PORT: number;
|
|
35
|
+
THUMB_SIZE: number;
|
|
36
|
+
IMG_CACHE_THRESHOLD: number;
|
|
37
|
+
DISK_CONCURRENCY: number;
|
|
38
|
+
}
|
|
39
|
+
export interface IndexOpts {
|
|
40
|
+
refresh: boolean;
|
|
28
41
|
}
|
|
29
42
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,IAAI,EAAE,IAAI,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,OAAO,CAAA;CACtB;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,IAAI,EAAE,IAAI,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,OAAO,CAAA;CACtB;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACjC,YAAY,EACZ,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,YAAY,CAC1D,CAAA;AAED,MAAM,WAAW,SAAS;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,QAAQ,CAAA;CAChB;AAED,MAAM,WAAW,MAAM,CAAC,CAAC,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,CAAC,CAAA;IACV,KAAK,CAAC,EAAE,CAAC,CAAA;CACZ;AAED,MAAM,WAAW,YAAY;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;IACzB,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,aAAa;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IAEZ,UAAU,EAAE,MAAM,CAAA;IAClB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,gBAAgB,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,SAAS;IACtB,OAAO,EAAE,OAAO,CAAA;CACnB"}
|
package/dist/utils.d.ts
CHANGED
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,CAAC,EAC1B,GAAG,EAAE,CAAC,EAAE,EACR,OAAO,EAAE,CAAC,EACV,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,MAAM,EAC3B,UAAU,IAAI,GAAG,MAAM,EAAE,GAAG,MAAM,WAAU,QAkB/C"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,CAAC,EAC1B,GAAG,EAAE,CAAC,EAAE,EACR,OAAO,EAAE,CAAC,EACV,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,MAAM,EAC3B,UAAU,IAAI,GAAG,MAAM,EAAE,GAAG,MAAM,WAAU,QAkB/C;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWnD"}
|
package/dist/utils.js
CHANGED
|
@@ -14,4 +14,21 @@ export function insertSorted(arr, element, key, comparator = (a, b) => a - b) {
|
|
|
14
14
|
}
|
|
15
15
|
arr.splice(low, 0, element);
|
|
16
16
|
}
|
|
17
|
+
export function bytesToString(bytes) {
|
|
18
|
+
const KB = 1000;
|
|
19
|
+
const MB = 1000 * KB;
|
|
20
|
+
const GB = 1000 * MB;
|
|
21
|
+
if (bytes < MB) {
|
|
22
|
+
return `${formatDecimal(bytes / KB)} KB`;
|
|
23
|
+
}
|
|
24
|
+
else if (bytes < GB) {
|
|
25
|
+
return `${formatDecimal(bytes / MB)} MB`;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
return `${formatDecimal(bytes / GB)} GB`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function formatDecimal(num, fractionDigits = 2) {
|
|
32
|
+
return parseFloat(num.toFixed(fractionDigits)).toString();
|
|
33
|
+
}
|
|
17
34
|
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,YAAY,CACxB,GAAQ,EACR,OAAU,EACV,GAA2B,EAC3B,aAAa,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC;IAE5C,IAAI,GAAG,GAAG,CAAC,CAAA;IACX,IAAI,IAAI,GAAG,GAAG,CAAC,MAAM,CAAA;IACrB,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,CAAA;IAEhC,OAAO,GAAG,GAAG,IAAI,EAAE,CAAC;QAChB,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAA;QAC9B,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAM,CAAC,CAAA;QAEnC,IAAI,UAAU,CAAC,WAAW,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACxC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAA;QACjB,CAAC;aAAM,CAAC;YACJ,IAAI,GAAG,GAAG,CAAA;QACd,CAAC;IACL,CAAC;IAED,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,OAAO,CAAC,CAAA;AAC/B,CAAC"}
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,YAAY,CACxB,GAAQ,EACR,OAAU,EACV,GAA2B,EAC3B,aAAa,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC;IAE5C,IAAI,GAAG,GAAG,CAAC,CAAA;IACX,IAAI,IAAI,GAAG,GAAG,CAAC,MAAM,CAAA;IACrB,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,CAAA;IAEhC,OAAO,GAAG,GAAG,IAAI,EAAE,CAAC;QAChB,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAA;QAC9B,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAM,CAAC,CAAA;QAEnC,IAAI,UAAU,CAAC,WAAW,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACxC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAA;QACjB,CAAC;aAAM,CAAC;YACJ,IAAI,GAAG,GAAG,CAAA;QACd,CAAC;IACL,CAAC;IAED,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,OAAO,CAAC,CAAA;AAC/B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACvC,MAAM,EAAE,GAAG,IAAI,CAAA;IACf,MAAM,EAAE,GAAG,IAAI,GAAG,EAAE,CAAA;IACpB,MAAM,EAAE,GAAG,IAAI,GAAG,EAAE,CAAA;IACpB,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;QACb,OAAO,GAAG,aAAa,CAAC,KAAK,GAAG,EAAE,CAAC,KAAK,CAAA;IAC5C,CAAC;SAAM,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;QACpB,OAAO,GAAG,aAAa,CAAC,KAAK,GAAG,EAAE,CAAC,KAAK,CAAA;IAC5C,CAAC;SAAM,CAAC;QACJ,OAAO,GAAG,aAAa,CAAC,KAAK,GAAG,EAAE,CAAC,KAAK,CAAA;IAC5C,CAAC;AACL,CAAC;AAED,SAAS,aAAa,CAAC,GAAW,EAAE,iBAAyB,CAAC;IAC1D,OAAO,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;AAC7D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anandaramr/obscura",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A lightweight local media streaming server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -14,26 +14,30 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"dist/**/*",
|
|
16
16
|
"public/**/*",
|
|
17
|
-
"API.md",
|
|
18
17
|
"README.md"
|
|
19
18
|
],
|
|
20
19
|
"scripts": {
|
|
21
20
|
"dev": "tsx watch src/index.ts",
|
|
22
21
|
"build": "tsc",
|
|
23
22
|
"start": "node dist/index.js",
|
|
24
|
-
"prepublishOnly": "npm run build"
|
|
23
|
+
"prepublishOnly": "npm run build",
|
|
24
|
+
"watch": "tsc --watch"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"chokidar": "^5.0.0",
|
|
28
28
|
"commander": "^14.0.3",
|
|
29
|
+
"cors": "^2.8.6",
|
|
29
30
|
"dotenv": "^17.4.2",
|
|
30
31
|
"express": "^5.2.1",
|
|
31
|
-
"ffmpeg-static": "^5.3.0",
|
|
32
32
|
"morgan": "^1.10.1",
|
|
33
33
|
"p-limit": "^7.3.0",
|
|
34
34
|
"sharp": "^0.34.5"
|
|
35
35
|
},
|
|
36
|
+
"optionalDependencies": {
|
|
37
|
+
"ffmpeg-static": "^5.3.0"
|
|
38
|
+
},
|
|
36
39
|
"devDependencies": {
|
|
40
|
+
"@types/cors": "^2.8.19",
|
|
37
41
|
"@types/express": "^5.0.6",
|
|
38
42
|
"@types/morgan": "^1.9.10",
|
|
39
43
|
"@types/node": "^25.9.1",
|
package/public/app.js
CHANGED
|
@@ -42,15 +42,32 @@ const eventSource = new EventSource('/api/events')
|
|
|
42
42
|
|
|
43
43
|
eventSource.onmessage = evt => {
|
|
44
44
|
const data = JSON.parse(evt.data)
|
|
45
|
-
const grid = document.getElementById('grid')
|
|
46
45
|
|
|
47
46
|
if (data.action === 'add') {
|
|
48
|
-
|
|
47
|
+
onAdd(data.file)
|
|
49
48
|
} else if (data.action === 'remove') {
|
|
50
|
-
|
|
49
|
+
onRemove(data.file.id)
|
|
50
|
+
} else if (data.action === 'update') {
|
|
51
|
+
onUpdate(data.file)
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
function onUpdate(file) {
|
|
56
|
+
onRemove(file.id)
|
|
57
|
+
onAdd(file)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function onRemove(fileId) {
|
|
61
|
+
files = files.filter(f => f.id !== fileId)
|
|
62
|
+
removeGridItem(fileId)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function onAdd(file) {
|
|
66
|
+
files.unshift(file)
|
|
67
|
+
const grid = document.getElementById('grid')
|
|
68
|
+
insertGridItem(file, grid, true)
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
eventSource.onopen = () => {
|
|
55
72
|
window.dispatchEvent(new Event('connected'))
|
|
56
73
|
}
|
|
@@ -75,11 +92,11 @@ function insertGridItem(file, grid, prepend = false) {
|
|
|
75
92
|
const preview = document.createElement('a')
|
|
76
93
|
|
|
77
94
|
preview.className = 'grid-item'
|
|
78
|
-
preview.href =
|
|
95
|
+
preview.href = getFileSource(file)
|
|
79
96
|
preview.id = file.id
|
|
80
97
|
|
|
81
98
|
const media = document.createElement('img')
|
|
82
|
-
media.src =
|
|
99
|
+
media.src = getThumbSource(file)
|
|
83
100
|
media.loading = 'lazy'
|
|
84
101
|
media.className = file.type === 'image' ? 'img' : 'video'
|
|
85
102
|
media.onerror = () => {
|
|
@@ -111,7 +128,7 @@ function insertGridItem(file, grid, prepend = false) {
|
|
|
111
128
|
|
|
112
129
|
preview.onmouseenter = () => {
|
|
113
130
|
if (!isMobileDevice()) {
|
|
114
|
-
startVideoPreview(vid, file
|
|
131
|
+
startVideoPreview(vid, file, media, icon)
|
|
115
132
|
}
|
|
116
133
|
}
|
|
117
134
|
|
|
@@ -128,13 +145,13 @@ function insertGridItem(file, grid, prepend = false) {
|
|
|
128
145
|
|
|
129
146
|
preview.onmouseenter = () => {
|
|
130
147
|
if (!isMobileDevice()) {
|
|
131
|
-
media.src =
|
|
148
|
+
media.src = getFileSource(file)
|
|
132
149
|
}
|
|
133
150
|
}
|
|
134
151
|
|
|
135
152
|
preview.onmouseleave = () => {
|
|
136
153
|
if (!isMobileDevice()) {
|
|
137
|
-
media.src =
|
|
154
|
+
media.src = getThumbSource(file)
|
|
138
155
|
}
|
|
139
156
|
}
|
|
140
157
|
}
|
|
@@ -149,6 +166,14 @@ function insertGridItem(file, grid, prepend = false) {
|
|
|
149
166
|
elementMap.set(file.id, preview)
|
|
150
167
|
}
|
|
151
168
|
|
|
169
|
+
function getFileSource(file) {
|
|
170
|
+
return `/api/files/${file.id}?v=${file.date}`
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function getThumbSource(file) {
|
|
174
|
+
return `/api/thumb/${file.id}?v=${file.date}`
|
|
175
|
+
}
|
|
176
|
+
|
|
152
177
|
function isServerReachable() {
|
|
153
178
|
return eventSource.readyState === EventSource.OPEN
|
|
154
179
|
}
|
|
@@ -245,7 +270,7 @@ function stopVideoPreview(vid, id, media, icon) {
|
|
|
245
270
|
icon.classList.remove('fade')
|
|
246
271
|
}
|
|
247
272
|
|
|
248
|
-
function startVideoPreview(vid,
|
|
273
|
+
function startVideoPreview(vid, file, media, icon) {
|
|
249
274
|
icon.classList.add('blink')
|
|
250
275
|
vid.addEventListener(
|
|
251
276
|
'playing',
|
|
@@ -257,14 +282,14 @@ function startVideoPreview(vid, id, media, icon) {
|
|
|
257
282
|
)
|
|
258
283
|
|
|
259
284
|
if (needsVideoLoading(vid)) {
|
|
260
|
-
vid.src =
|
|
285
|
+
vid.src = getFileSource(file)
|
|
261
286
|
vid.load()
|
|
262
287
|
}
|
|
263
288
|
|
|
264
289
|
vid.classList.remove('fade')
|
|
265
290
|
media.classList.add('fade')
|
|
266
291
|
|
|
267
|
-
onVideoPlay(id)
|
|
292
|
+
onVideoPlay(file.id)
|
|
268
293
|
vid.currentTime = 0
|
|
269
294
|
vid.play().catch(err => console.log('Play interrupted:', err))
|
|
270
295
|
}
|
|
@@ -283,7 +308,6 @@ function removeGridItem(fileId) {
|
|
|
283
308
|
const grid = document.getElementById('grid')
|
|
284
309
|
grid.removeChild(child)
|
|
285
310
|
elementMap.delete(fileId)
|
|
286
|
-
files = files.filter(f => f.id !== fileId)
|
|
287
311
|
}
|
|
288
312
|
}
|
|
289
313
|
|
package/API.md
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# Obscura API
|
|
2
|
-
|
|
3
|
-
Base URL: `/api`
|
|
4
|
-
|
|
5
|
-
All endpoints return JSON unless otherwise noted.
|
|
6
|
-
|
|
7
|
-
## Files
|
|
8
|
-
|
|
9
|
-
### List files
|
|
10
|
-
|
|
11
|
-
`GET /api/files`
|
|
12
|
-
|
|
13
|
-
Returns all files sorted by date descending.
|
|
14
|
-
|
|
15
|
-
**Response** `200 OK`
|
|
16
|
-
|
|
17
|
-
```json
|
|
18
|
-
[
|
|
19
|
-
{
|
|
20
|
-
"id": "4186824008a4299b7587c6902d00e6df",
|
|
21
|
-
"name": "beach_volleyball.mp4",
|
|
22
|
-
"type": "video",
|
|
23
|
-
"date": "2026-05-25T22:15:30.000Z",
|
|
24
|
-
"size": 45102080,
|
|
25
|
-
"isAnimated": false
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"id": "4316e0adec899c7fdd40bb55d47900e4",
|
|
29
|
-
"name": "sunset.jpg",
|
|
30
|
-
"type": "image",
|
|
31
|
-
"date": "2026-05-25T23:11:00.000Z",
|
|
32
|
-
"size": 2048576,
|
|
33
|
-
"isAnimated": false
|
|
34
|
-
}
|
|
35
|
-
]
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### Get file
|
|
39
|
-
|
|
40
|
-
`GET /api/files/:id`
|
|
41
|
-
|
|
42
|
-
Returns the raw file for rendering in the browser.
|
|
43
|
-
|
|
44
|
-
**Responses**
|
|
45
|
-
|
|
46
|
-
- `200` — raw file bytes
|
|
47
|
-
- `404` — file not found
|
|
48
|
-
|
|
49
|
-
## Thumbnails
|
|
50
|
-
|
|
51
|
-
### Get thumbnail
|
|
52
|
-
|
|
53
|
-
`GET /api/thumb/:id`
|
|
54
|
-
|
|
55
|
-
Returns a JPEG thumbnail for the file. Generated on first request and cached to disk. Images below the cache threshold are returned as-is without processing. Thumbnail generation is subject to the configured disk concurrency limit.
|
|
56
|
-
|
|
57
|
-
**Responses**
|
|
58
|
-
|
|
59
|
-
- `200` — JPEG thumbnail (or original file if below cache threshold)
|
|
60
|
-
- `404` — file not found
|
|
61
|
-
- `500` — thumbnail generation failed
|
|
62
|
-
|
|
63
|
-
## Events
|
|
64
|
-
|
|
65
|
-
### Subscribe to file changes
|
|
66
|
-
|
|
67
|
-
`GET /api/events`
|
|
68
|
-
|
|
69
|
-
Opens a server-sent events stream. Emits an event whenever a file is added or removed from the gallery directory. Reconnect and re-fetch `/api/files` if the connection drops.
|
|
70
|
-
|
|
71
|
-
**Event shape**
|
|
72
|
-
|
|
73
|
-
`add` — emitted when a file is added to the gallery directory.
|
|
74
|
-
|
|
75
|
-
```json
|
|
76
|
-
{
|
|
77
|
-
"action": "add",
|
|
78
|
-
"file": {
|
|
79
|
-
"id": "4186824008a4299b7587c6902d00e6df",
|
|
80
|
-
"name": "beach_volleyball.mp4",
|
|
81
|
-
"type": "video",
|
|
82
|
-
"date": "2026-05-25T22:15:30.000Z",
|
|
83
|
-
"size": 45102080,
|
|
84
|
-
"isAnimated": false
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
`remove` — emitted when a file is removed from the gallery directory.
|
|
90
|
-
|
|
91
|
-
```json
|
|
92
|
-
{ "action": "remove", "file": { "id": "4186824008a4299b7587c6902d00e6df" } }
|
|
93
|
-
```
|