Changeset 7d01696 in Klonkt for test/c2s-compose.test.js


Ignore:
Timestamp:
07/30/2026 07:56:33 AM (6 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
9ed940e
Parents:
97bcf7e
Message:

Posterframes voor video, ffmpeg-optioneel

shaer-zowq, de serverhelft. Bij een video-upload trekt ffmpeg een frame op
1 seconde naar <naam>.poster.jpg, best-effort en buiten de responspad: op een
machine zonder ffmpeg gebeurt er niets en breekt er niets (de clients halen
dan zelf een frame, native). Vanaf daar reist de poster naar drie plekken:

  • poster= op de gevouwen video-tag, dus het web toont een stilstaand beeld
  • opgeslagen op de c2s_attachments-entry
  • als AS2 icon op het gefedereerde Video-attachment, dus apps en andere servers krijgen de thumbnail-URL cadeau

Inkomend geldt het spiegelbeeld: een remote video-attachment met een icon
houdt hem als poster in media_json, en de C2S-inboxlees serveert hem terug.

Changed files:
src/routes/activitypub.js

  • uploadMedia: ffmpeg-posterframe voor video, best-effort

src/services/ActivityPubService.js

  • c2sCreatePost: poster op entry en tag; buildNote: icon op het attachment; mediaFromNote/timelineAttachments: inkomende posters door

test/c2s-compose.test.js

  • de poster bereikt tag, opslag en attachment; zonder posterbestand wordt er niets gegokt

remarks: 338 tests groen. ffmpeg staat NIET op de VPS; installeren is een
sudo-actie die ik niet mag doen: sudo apt-get install -y ffmpeg. Zonder dat
werkt alles, alleen maken de clients hun thumbnails zelf.

-robo
Co-Authored-By: Claude Opus 5 <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • test/c2s-compose.test.js

    r97bcf7e r7d01696  
    5555});
    5656
     57test("a video's poster frame rides the tag, the store and the federated attachment", async () => {
     58  // The upload leg writes <name>.poster.jpg next to a video when ffmpeg is
     59  // around (shaer-zowq). From there it must reach three places: the poster=
     60  // on the folded tag (web), the stored entry, and the AS2 icon on the
     61  // federated attachment (apps and other servers).
     62  const os = await import('os');
     63  const fsm = await import('fs');
     64  const pathm = await import('path');
     65  const root = fsm.mkdtempSync(pathm.join(os.tmpdir(), 'klonkt-media-'));
     66  fsm.mkdirSync(pathm.join(root, 'reply-media'), { recursive: true });
     67  fsm.writeFileSync(pathm.join(root, 'reply-media', 'film.mp4.poster.jpg'), 'x');
     68  const prev = process.env.MEDIA_PATH;
     69  process.env.MEDIA_PATH = root;
     70  try {
     71    const r = await AP.ingestOutboxActivity(site, user, {
     72      type: 'Create',
     73      object: {
     74        type: 'Note', content: '<p>filmpje</p>',
     75        to: ['https://test.example/ap/users/kid/followers'],
     76        attachment: [{ type: 'Video', url: '/media/reply-media/film.mp4', mediaType: 'video/mp4' }],
     77      },
     78    });
     79    assert.equal(r.status, 201);
     80    const post = db.prepare('SELECT * FROM posts WHERE id = ?').get(r.id);
     81    assert.match(post.content, /poster="\/media\/reply-media\/film\.mp4\.poster\.jpg"/, 'the web tag shows the still');
     82    const note = AP.buildNote('https://test.example', site, post);
     83    const vid = (note.attachment || []).find((a) => a.url.endsWith('film.mp4'));
     84    assert.ok(vid && vid.type === 'Video');
     85    assert.equal(vid.icon && vid.icon.url, 'https://test.example/media/reply-media/film.mp4.poster.jpg',
     86      'the poster federates as the attachment icon');
     87  } finally {
     88    if (prev === undefined) delete process.env.MEDIA_PATH; else process.env.MEDIA_PATH = prev;
     89  }
     90});
     91
     92test('a video without a poster simply has none: no guessed icon', async () => {
     93  const r = await AP.ingestOutboxActivity(site, user, {
     94    type: 'Create',
     95    object: {
     96      type: 'Note', content: '<p>kaal</p>',
     97      to: ['https://test.example/ap/users/kid/followers'],
     98      attachment: [{ type: 'Video', url: '/media/reply-media/zonder.mp4', mediaType: 'video/mp4' }],
     99    },
     100  });
     101  const post = db.prepare('SELECT * FROM posts WHERE id = ?').get(r.id);
     102  assert.ok(!post.content.includes('poster='), 'no poster attr without a poster file');
     103  const vid = (AP.buildNote('https://test.example', site, post).attachment || []).find((a) => a.url.endsWith('zonder.mp4'));
     104  assert.equal(vid.icon, undefined);
     105});
     106
    57107test('a media-only post is a post, not an empty-note error', async () => {
    58108  const r = await AP.ingestOutboxActivity(site, user, {
Note: See TracChangeset for help on using the changeset viewer.