includio-cms 0.13.3 → 0.14.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/ROADMAP.md +19 -2
  3. package/dist/admin/api/handler.js +6 -0
  4. package/dist/admin/api/media-gc.js +19 -3
  5. package/dist/admin/api/regenerate-posters.d.ts +2 -0
  6. package/dist/admin/api/regenerate-posters.js +32 -0
  7. package/dist/admin/api/system-info.d.ts +2 -0
  8. package/dist/admin/api/system-info.js +9 -0
  9. package/dist/admin/api/transcode-videos.d.ts +2 -0
  10. package/dist/admin/api/transcode-videos.js +32 -0
  11. package/dist/admin/client/maintenance/maintenance-page.svelte +554 -3
  12. package/dist/core/fields/structuredToHtml.js +20 -1
  13. package/dist/core/server/fields/resolveImageFields.js +37 -2
  14. package/dist/core/server/media/operations/batchRegenerateVideoPosters.d.ts +15 -0
  15. package/dist/core/server/media/operations/batchRegenerateVideoPosters.js +112 -0
  16. package/dist/core/server/media/operations/batchTranscodeVideos.d.ts +23 -0
  17. package/dist/core/server/media/operations/batchTranscodeVideos.js +104 -0
  18. package/dist/core/server/media/operations/deleteMediaFile.js +16 -1
  19. package/dist/core/server/media/operations/getDiskUsage.d.ts +24 -0
  20. package/dist/core/server/media/operations/getDiskUsage.js +103 -0
  21. package/dist/core/server/media/operations/getSystemInfo.d.ts +27 -0
  22. package/dist/core/server/media/operations/getSystemInfo.js +90 -0
  23. package/dist/core/server/media/operations/replaceFile.js +10 -0
  24. package/dist/core/server/media/operations/uploadFile.js +2 -0
  25. package/dist/core/server/media/styles/ffmpeg/generateVideoStyle.d.ts +7 -0
  26. package/dist/core/server/media/styles/ffmpeg/generateVideoStyle.js +59 -0
  27. package/dist/core/server/media/styles/operations/generateDefaultVideoStyles.d.ts +9 -0
  28. package/dist/core/server/media/styles/operations/generateDefaultVideoStyles.js +88 -0
  29. package/dist/db-postgres/index.js +113 -0
  30. package/dist/db-postgres/schema/index.d.ts +1 -0
  31. package/dist/db-postgres/schema/index.js +1 -0
  32. package/dist/db-postgres/schema/videoStyle.d.ts +228 -0
  33. package/dist/db-postgres/schema/videoStyle.js +18 -0
  34. package/dist/files-local/index.d.ts +6 -1
  35. package/dist/files-local/index.js +7 -141
  36. package/dist/files-local/sanitizeFilename.js +2 -1
  37. package/dist/files-local/transcode.d.ts +27 -0
  38. package/dist/files-local/transcode.js +130 -0
  39. package/dist/files-local/video.d.ts +10 -0
  40. package/dist/files-local/video.js +167 -0
  41. package/dist/paraglide/messages/_index.d.ts +36 -3
  42. package/dist/paraglide/messages/_index.js +71 -3
  43. package/dist/paraglide/messages/en.d.ts +5 -0
  44. package/dist/paraglide/messages/en.js +14 -0
  45. package/dist/paraglide/messages/pl.d.ts +5 -0
  46. package/dist/paraglide/messages/pl.js +14 -0
  47. package/dist/sveltekit/components/video.svelte +15 -1
  48. package/dist/sveltekit/utils/media.js +2 -2
  49. package/dist/types/adapters/db.d.ts +37 -1
  50. package/dist/types/cms.d.ts +16 -0
  51. package/dist/types/fields.d.ts +12 -1
  52. package/dist/types/index.d.ts +1 -1
  53. package/dist/types/media.d.ts +15 -0
  54. package/dist/updates/0.13.4/index.d.ts +2 -0
  55. package/dist/updates/0.13.4/index.js +14 -0
  56. package/dist/updates/0.14.0/index.d.ts +2 -0
  57. package/dist/updates/0.14.0/index.js +36 -0
  58. package/dist/updates/index.js +3 -1
  59. package/package.json +1 -1
  60. package/dist/paraglide/messages/hello_world.d.ts +0 -5
  61. package/dist/paraglide/messages/hello_world.js +0 -33
  62. package/dist/paraglide/messages/login_hello.d.ts +0 -16
  63. package/dist/paraglide/messages/login_hello.js +0 -34
  64. package/dist/paraglide/messages/login_please_login.d.ts +0 -16
  65. package/dist/paraglide/messages/login_please_login.js +0 -34
package/CHANGELOG.md CHANGED
@@ -3,6 +3,59 @@
3
3
  All notable changes to includio-cms are documented here.
4
4
  Generated from `src/lib/updates/` — do not edit manually.
5
5
 
6
+ ## 0.14.0 — 2026-03-24
7
+
8
+ Video transcoding & optimization
9
+
10
+ ### Added
11
+ - Auto-transcode uploaded videos to mp4 (h264) and webm (vp9) in background
12
+ - Video styles system with status tracking (pending/processing/done/failed)
13
+ - Admin maintenance: video transcoding card with batch transcode, purge & retranscode, SSE progress
14
+ - Configurable video transcoding: formats, max resolution, CRF quality, concurrency
15
+ - Skip logic: skip already-optimized mp4 h264 at 1080p or below, skip files over 500MB
16
+ - Frontend video serves multiple source elements (webm, mp4, original fallback)
17
+ - System info endpoint: CMS version, Node, PostgreSQL, ffmpeg (codec checks), sharp, OS diagnostics
18
+ - Disk usage endpoint: breakdown per category (originals by type, image styles, video styles, posters)
19
+
20
+ ### Migration
21
+
22
+ ```sql
23
+ CREATE TABLE IF NOT EXISTS video_styles (
24
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
25
+ media_file_id UUID NOT NULL REFERENCES media_file(id) ON DELETE CASCADE,
26
+ name TEXT NOT NULL,
27
+ url TEXT NOT NULL,
28
+ width INTEGER,
29
+ height INTEGER,
30
+ format TEXT NOT NULL,
31
+ codec TEXT,
32
+ file_size INTEGER,
33
+ mime_type TEXT NOT NULL,
34
+ status TEXT NOT NULL DEFAULT 'pending',
35
+ error TEXT,
36
+ created_at TIMESTAMP DEFAULT NOW()
37
+ );
38
+
39
+ CREATE UNIQUE INDEX IF NOT EXISTS video_styles_unique_key
40
+ ON video_styles (media_file_id, name);
41
+ ```
42
+
43
+ ### Notes
44
+
45
+ Video transcoding requires ffmpeg with libx264 and libvpx-vp9 codecs. Graceful degradation if ffmpeg is unavailable.
46
+
47
+ ## 0.13.4 — 2026-03-24
48
+
49
+ Video poster regeneration, filename sanitization
50
+
51
+ ### Added
52
+ - Batch regenerate video posters & thumbnails from admin maintenance page with SSE progress
53
+ - Video poster status reporting in media GC endpoint
54
+ - Extracted video processing to reusable module
55
+
56
+ ### Fixed
57
+ - Filename sanitization normalizes Unicode to NFC (prevents NFD/NFC mismatch)
58
+
6
59
  ## 0.13.3 — 2026-03-23
7
60
 
8
61
  Security hardening, per-language content fixes
package/ROADMAP.md CHANGED
@@ -239,14 +239,31 @@
239
239
  - [x] `[fix]` `[P1]` CMS constructor validates non-empty languages array <!-- files: src/lib/core/cms.ts -->
240
240
  - [x] `[fix]` `[P2]` Unhandled promise rejection handlers in admin components
241
241
 
242
- ## 0.14.0SEO module
242
+ ## 0.13.4Video poster regeneration
243
+
244
+ - [x] `[feature]` `[P1]` Batch video poster regeneration — admin maintenance page with SSE progress <!-- files: src/lib/admin/api/regenerate-posters.ts, src/lib/core/server/media/operations/batchRegenerateVideoPosters.ts, src/lib/admin/client/maintenance/maintenance-page.svelte -->
245
+ - [x] `[feature]` `[P1]` Video poster status reporting in media GC endpoint <!-- files: src/lib/admin/api/media-gc.ts -->
246
+ - [x] `[chore]` `[P1]` Extracted video processing to reusable module <!-- files: src/lib/files-local/video.ts, src/lib/files-local/index.ts -->
247
+ - [x] `[fix]` `[P1]` Filename sanitization normalizes Unicode to NFC <!-- files: src/lib/files-local/sanitizeFilename.ts -->
248
+
249
+ ## 0.14.0 — Video transcoding
250
+
251
+ - [x] `[feature]` `[P1]` Video styles system — auto-transcode to mp4 h264 + webm vp9 <!-- files: src/lib/db-postgres/schema/videoStyle.ts, src/lib/files-local/transcode.ts -->
252
+ - [x] `[feature]` `[P1]` Background transcoding pipeline on upload with skip logic <!-- files: src/lib/core/server/media/styles/operations/generateDefaultVideoStyles.ts -->
253
+ - [x] `[feature]` `[P1]` Admin maintenance: video transcoding card (batch, purge, SSE progress) <!-- files: src/lib/admin/client/maintenance/maintenance-page.svelte -->
254
+ - [x] `[feature]` `[P2]` Frontend multi-source video delivery (webm, mp4, original) <!-- files: src/lib/sveltekit/components/video.svelte -->
255
+ - [x] `[feature]` `[P2]` Configurable video transcoding in MediaConfig <!-- files: src/lib/types/cms.ts -->
256
+ - [x] `[feature]` `[P2]` System info endpoint — CMS version, Node, PostgreSQL, ffmpeg, sharp, OS <!-- files: src/lib/admin/api/system-info.ts, src/lib/core/server/media/operations/getSystemInfo.ts -->
257
+ - [x] `[feature]` `[P2]` Disk usage endpoint — breakdown per category (originals, image styles, video styles, posters) <!-- files: src/lib/admin/api/system-info.ts, src/lib/core/server/media/operations/getDiskUsage.ts -->
258
+
259
+ ## 0.15.0 — SEO module
243
260
 
244
261
  - [ ] `[feature]` `[P1]` SERP preview + character limits for title/description <!-- files: src/lib/admin/components/fields/seo-field.svelte -->
245
262
  - [ ] `[feature]` `[P1]` Global SEO settings
246
263
  - [ ] `[feature]` `[P1]` Dedicated frontend SEO components <!-- files: src/lib/sveltekit/components/seo.svelte -->
247
264
  - [ ] `[feature]` `[P2]` Sitemap generation
248
265
 
249
- ## 0.15.0 — WCAG/ATAG compliance
266
+ ## 0.16.0 — WCAG/ATAG compliance
250
267
 
251
268
  - [ ] `[chore]` `[P0]` Full WCAG/ATAG audit
252
269
  - [ ] `[feature]` `[P0]` Accessibility rework based on audit findings
@@ -6,7 +6,10 @@ import * as inviteHandlers from './invite.js';
6
6
  import * as acceptInviteHandlers from './accept-invite.js';
7
7
  import * as mediaGcHandlers from './media-gc.js';
8
8
  import * as generateStylesHandlers from './generate-styles.js';
9
+ import * as regeneratePostersHandlers from './regenerate-posters.js';
10
+ import * as transcodeVideosHandlers from './transcode-videos.js';
9
11
  import * as uploadLimitHandlers from './upload-limit.js';
12
+ import * as systemInfoHandlers from './system-info.js';
10
13
  import { requireAuth } from '../remote/middleware/auth.js';
11
14
  import { getCMS } from '../../core/cms.js';
12
15
  import { lookup } from 'mrmime';
@@ -19,7 +22,10 @@ export function createAdminApiHandler(options) {
19
22
  'accept-invite': acceptInviteHandlers,
20
23
  'media-gc': mediaGcHandlers,
21
24
  'generate-styles': generateStylesHandlers,
25
+ 'regenerate-posters': regeneratePostersHandlers,
26
+ 'transcode-videos': transcodeVideosHandlers,
22
27
  'upload-limit': uploadLimitHandlers,
28
+ 'system-info': systemInfoHandlers,
23
29
  ...options?.extraRoutes
24
30
  };
25
31
  const privateMediaGet = async (event) => {
@@ -2,12 +2,16 @@ import { requireRole } from '../remote/middleware/auth.js';
2
2
  import { purgeAllImageStyles } from '../../core/server/media/operations/purgeImageStyles.js';
3
3
  import { getReconciliationReport, deleteOrphanedDiskFiles } from '../../core/server/media/operations/reconcileMedia.js';
4
4
  import { getStylesStatus } from '../../core/server/media/styles/operations/batchGenerateStyles.js';
5
+ import { getVideoPosterStatus } from '../../core/server/media/operations/batchRegenerateVideoPosters.js';
6
+ import { getVideoTranscodeStatus, purgeVideoStyles } from '../../core/server/media/operations/batchTranscodeVideos.js';
5
7
  import { json } from '@sveltejs/kit';
6
8
  export const GET = async ({ url }) => {
7
9
  requireRole('admin');
8
- const [stylesStatus, report] = await Promise.all([
10
+ const [stylesStatus, report, videoPosterStatus, videoTranscodeStatus] = await Promise.all([
9
11
  getStylesStatus(),
10
- getReconciliationReport()
12
+ getReconciliationReport(),
13
+ getVideoPosterStatus(),
14
+ getVideoTranscodeStatus()
11
15
  ]);
12
16
  return json({
13
17
  imageStylesCount: stylesStatus.existingStyles,
@@ -15,7 +19,15 @@ export const GET = async ({ url }) => {
15
19
  expectedStylesCount: stylesStatus.expectedStyles,
16
20
  missingStylesCount: stylesStatus.missingStyles,
17
21
  orphanedDiskFiles: report.orphanedDisk,
18
- missingDiskRecords: report.missingDisk
22
+ missingDiskRecords: report.missingDisk,
23
+ videosCount: videoPosterStatus.videosCount,
24
+ videosWithPosters: videoPosterStatus.videosWithPosters,
25
+ videosMissingPosters: videoPosterStatus.videosMissingPosters,
26
+ videoStylesCount: videoTranscodeStatus.videoStylesCount,
27
+ videoStylesExpected: videoTranscodeStatus.videoStylesExpected,
28
+ videoStylesDone: videoTranscodeStatus.videoStylesDone,
29
+ videoStylesPending: videoTranscodeStatus.videoStylesPending,
30
+ videoStylesFailed: videoTranscodeStatus.videoStylesFailed
19
31
  });
20
32
  };
21
33
  export const DELETE = async ({ url }) => {
@@ -29,5 +41,9 @@ export const DELETE = async ({ url }) => {
29
41
  const result = await deleteOrphanedDiskFiles();
30
42
  return json(result);
31
43
  }
44
+ if (action === 'purge-video-styles') {
45
+ const result = await purgeVideoStyles();
46
+ return json(result);
47
+ }
32
48
  return json({ error: 'Unknown action' }, { status: 400 });
33
49
  };
@@ -0,0 +1,2 @@
1
+ import type { RequestHandler } from '@sveltejs/kit';
2
+ export declare const POST: RequestHandler;
@@ -0,0 +1,32 @@
1
+ import { requireRole } from '../remote/middleware/auth.js';
2
+ import { batchRegenerateVideoPosters } from '../../core/server/media/operations/batchRegenerateVideoPosters.js';
3
+ export const POST = async () => {
4
+ requireRole('admin');
5
+ const abort = new AbortController();
6
+ const encoder = new TextEncoder();
7
+ const stream = new ReadableStream({
8
+ async start(controller) {
9
+ try {
10
+ for await (const event of batchRegenerateVideoPosters(abort.signal)) {
11
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
12
+ }
13
+ }
14
+ catch (e) {
15
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'error', error: e instanceof Error ? e.message : String(e) })}\n\n`));
16
+ }
17
+ finally {
18
+ controller.close();
19
+ }
20
+ },
21
+ cancel() {
22
+ abort.abort();
23
+ }
24
+ });
25
+ return new Response(stream, {
26
+ headers: {
27
+ 'Content-Type': 'text/event-stream',
28
+ 'Cache-Control': 'no-cache',
29
+ Connection: 'keep-alive'
30
+ }
31
+ });
32
+ };
@@ -0,0 +1,2 @@
1
+ import { type RequestHandler } from '@sveltejs/kit';
2
+ export declare const GET: RequestHandler;
@@ -0,0 +1,9 @@
1
+ import { requireRole } from '../remote/middleware/auth.js';
2
+ import { getDiskUsage } from '../../core/server/media/operations/getDiskUsage.js';
3
+ import { getSystemInfo } from '../../core/server/media/operations/getSystemInfo.js';
4
+ import { json } from '@sveltejs/kit';
5
+ export const GET = async () => {
6
+ requireRole('admin');
7
+ const [diskUsage, systemInfo] = await Promise.all([getDiskUsage(), getSystemInfo()]);
8
+ return json({ diskUsage, systemInfo });
9
+ };
@@ -0,0 +1,2 @@
1
+ import type { RequestHandler } from '@sveltejs/kit';
2
+ export declare const POST: RequestHandler;
@@ -0,0 +1,32 @@
1
+ import { requireRole } from '../remote/middleware/auth.js';
2
+ import { batchTranscodeVideos } from '../../core/server/media/operations/batchTranscodeVideos.js';
3
+ export const POST = async () => {
4
+ requireRole('admin');
5
+ const abort = new AbortController();
6
+ const encoder = new TextEncoder();
7
+ const stream = new ReadableStream({
8
+ async start(controller) {
9
+ try {
10
+ for await (const event of batchTranscodeVideos(abort.signal)) {
11
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
12
+ }
13
+ }
14
+ catch (e) {
15
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'error', error: e instanceof Error ? e.message : String(e) })}\n\n`));
16
+ }
17
+ finally {
18
+ controller.close();
19
+ }
20
+ },
21
+ cancel() {
22
+ abort.abort();
23
+ }
24
+ });
25
+ return new Response(stream, {
26
+ headers: {
27
+ 'Content-Type': 'text/event-stream',
28
+ 'Cache-Control': 'no-cache',
29
+ Connection: 'keep-alive'
30
+ }
31
+ });
32
+ };