SamuelScheit / puppeteer-stream

A Library for puppeteer to retrieve audio and/or video streams
MIT License
357 stars 114 forks source link

A solution for non-seekable video files. #136

Open Jb-Za opened 1 year ago

Jb-Za commented 1 year ago

My use-case required me to have, mp4 files, at 60fps, and for the video to be seek-able. To my understanding the resulting videos are non-seekable due to the fact that they are originally from a stream, and thus do not have any metadata. The solution that I found is to force metadata into the file.

This is rather slow, and the better option would be to have the encoder (somehow) add the metadata during the recording process.

A disclaimer: I don't know much about the inner workings of chromium recording/streaming, this is simply just a solution that I found that may help someone out.

const { launch, getStream } = require("puppeteer-stream");
const fs = require("fs");
const { executablePath } = require("puppeteer");
const ffmpeg = require("fluent-ffmpeg");

const webmFile = __dirname + "/test.webm";

async function test() {
  const browser = await launch({
    defaultViewport: {
      width: 1920,
      height: 1080,
    },
    headless: false,
    executablePath: executablePath(),
  });

  const page = await browser.newPage();
  await page.goto("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
  const stream = await getStream(page, { audio: true, video: true, videoConstraints: { mandatory: {  minFrameRate: 58, maxFrameRate: 60 }} });
  console.log("recording");

  const file = fs.createWriteStream(webmFile);
  stream.pipe(file);

  setTimeout(async () => {
    await stream.destroy();
    file.close();
    browser.close();

    const outputMP4File = __dirname + "/test.mp4";
    const videoMetadata = await getVideoMetadata(webmFile);

    ffmpeg()
      .input(webmFile)
      .outputOptions([
        `-metadata title="${videoMetadata.title}"`,
        `-metadata artist="${videoMetadata.artist}"`,
        "-r 60",
        "-b:v 8000k",
      ])
      .output(outputMP4File)
      .on("end", () => {
        console.log("Metadata added and WebM converted to MP4 successfully.");
      })
      .on("error", (err) => {
        console.error(
          "Error while adding metadata and converting WebM to MP4:",
          err
        );
      })
      .run();
  }, 1000 * 10);
}

async function getVideoMetadata(inputFile) {
  return new Promise((resolve, reject) => {
    ffmpeg.ffprobe(inputFile, (err, metadata) => {
      if (err) {
        reject(err);
      } else {
        const { title, artist } = metadata.format.tags;
        resolve({ title, artist });
      }
    });
  });
}

test();
ddaydd commented 11 months ago

Hi, No one has any comments for this? Is this really the only solution to make the video seekable?

Edit : https://github.com/SamuelScheit/puppeteer-stream/issues/57

fix-webm-duration seems to work with the blob in memory, for large files it doesn't seem to be the solution. I tried with mkvmerge which is very fast but it is not very stable with large files const command = mkvmerge -o "${outputPath}" --clusters-in-meta-seek "${inputPath}"; and with ffmpeg which works well is not as fast as mkvmerge const command = ffmpeg -i "${inputPath}" -vcodec copy -acodec copy "${outputPath}";

If you have other ideas, it would be cool to centralize them here

ddaydd commented 3 months ago

I just discovered the frameSize option which makes my file seekable? you confirm? or something else but recently my video became seekable