Recherche avancée

Médias (91)

Autres articles (104)

  • Gestion des droits de création et d’édition des objets

    8 février 2011, par

    Par défaut, beaucoup de fonctionnalités sont limitées aux administrateurs mais restent configurables indépendamment pour modifier leur statut minimal d’utilisation notamment : la rédaction de contenus sur le site modifiables dans la gestion des templates de formulaires ; l’ajout de notes aux articles ; l’ajout de légendes et d’annotations sur les images ;

  • Supporting all media types

    13 avril 2011, par

    Unlike most software and media-sharing platforms, MediaSPIP aims to manage as many different media types as possible. The following are just a few examples from an ever-expanding list of supported formats : images : png, gif, jpg, bmp and more audio : MP3, Ogg, Wav and more video : AVI, MP4, OGV, mpg, mov, wmv and more text, code and other data : OpenOffice, Microsoft Office (Word, PowerPoint, Excel), web (html, CSS), LaTeX, Google Earth and (...)

  • Keeping control of your media in your hands

    13 avril 2011, par

    The vocabulary used on this site and around MediaSPIP in general, aims to avoid reference to Web 2.0 and the companies that profit from media-sharing.
    While using MediaSPIP, you are invited to avoid using words like "Brand", "Cloud" and "Market".
    MediaSPIP is designed to facilitate the sharing of creative media online, while allowing authors to retain complete control of their work.
    MediaSPIP aims to be accessible to as many people as possible and development is based on expanding the (...)

Sur d’autres sites (11910)

  • Write EPIPE after upgrade Node.js

    30 juillet, par Rougher

    I am using this code for detecting audio replay gain. It was working well with Node.js 16, but after upgrading to Node.js 22, it started crashing a few times in an hour with this error :

    


    write EPIPE
    at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:87:19) {
  errno: -32,
  code: 'EPIPE',
  syscall: 'write'
}


    


    My original code was

    


    static getReplayGainVolume(audioData: Buffer) {
        // Calculate the mean volume of the audio file at the given filePath
        var ffmpeg = spawn('ffmpeg', [
            '-i', '-',
            '-af', 'replaygain',
            '-f', 'null', '/dev/null',
            '-hide_banner', '-nostats'
        ]);

        var output = '';

        ffmpeg.stdin.write(audioData);
        ffmpeg.stdin.end();

        return new Promise((resolve,reject)=>{
            ffmpeg.on('error', function (err: any) {
                reject(err);
            });
            
            ffmpeg.on('close', function (_code: any) {
                // [Parsed_replaygain_0 @ 0000000002a2b5c0] track_gain = +6.53 dB
                if (!output.includes("track_gain")) {
                    reject(output);

                    return;
                }

                const gainWithDb = output.split("track_gain = ")[1];
                if (!gainWithDb) {
                    reject(output);

                    return;
                }

                const gain = gainWithDb.split(" dB")[0];
                if (!gain) {
                    reject(output);

                    return;
                }

                resolve(parseFloat(gain));
            });
            
            ffmpeg.stderr.on('data', function (data: any) {
                // ffmpeg sends all output to stderr. It is not a bug, it is a feature :)
                var tData = data.toString('utf8');
                output += tData;
            });
        });
    }


    


    Then after search in forums and Google, I improved (I hope I improved it with cleanups)

    


    static getReplayGainVolume(audioData: Buffer): Promise<number> {&#xA;        return new Promise((resolve, reject) => {&#xA;            const FFMPEG_PATH = &#x27;ffmpeg&#x27;; // Adjust this if ffmpeg is not in system PATH&#xA;            const FFMPEG_TIMEOUT_MS = 30 * 1000; // 30 seconds timeout for FFmpeg execution&#xA;&#xA;            let ffmpeg: ChildProcessWithoutNullStreams;&#xA;            let output = &#x27;&#x27;; // Accumulate all stderr output&#xA;&#xA;            // Timeout for the FFmpeg process itself&#xA;            const ffmpegTimeout = setTimeout(() => {&#xA;                log.error(`[FFmpeg] FFmpeg process timed out after ${FFMPEG_TIMEOUT_MS / 1000} seconds. Killing process.`);&#xA;                if (ffmpeg &amp;&amp; !ffmpeg.killed) {&#xA;                    ffmpeg.kill(&#x27;SIGKILL&#x27;); // Force kill&#xA;                    reject(new Error(`FFmpeg process timed out and was killed.`));&#xA;                }&#xA;            }, FFMPEG_TIMEOUT_MS);&#xA;&#xA;            // --- Define cleanup function to be called on process exit/error ---&#xA;            const cleanup = (shouldReject = false, error?: Error | string) => {&#xA;                clearTimeout(ffmpegTimeout); // Ensure timeout is cleared&#xA;&#xA;                // Remove all listeners to prevent leaks&#xA;                // This is CRITICAL for long-running bots that spawn many child processes&#xA;                ffmpeg.stdin.removeAllListeners();&#xA;                ffmpeg.stdout.removeAllListeners();&#xA;                ffmpeg.stderr.removeAllListeners();&#xA;                ffmpeg.removeAllListeners(); // Remove process listeners&#xA;&#xA;                if (ffmpeg &amp;&amp; !ffmpeg.killed) { // Ensure ffmpeg process is killed if still alive&#xA;                    ffmpeg.kill(); // Graceful kill (SIGTERM), then wait for exit. If not, then SIGKILL.&#xA;                }&#xA;&#xA;                if (shouldReject) {&#xA;                    reject(error instanceof Error ? error : new Error(String(error)));&#xA;                }&#xA;            };&#xA;&#xA;            try {&#xA;                ffmpeg = spawn(FFMPEG_PATH, [&#xA;                    &#x27;-i&#x27;, &#x27;pipe:0&#x27;, // Read input from stdin (pipe:0)&#xA;                    &#x27;-af&#x27;, &#x27;replaygain&#x27;,&#xA;                    &#x27;-f&#x27;, &#x27;null&#x27;, &#x27;/dev/null&#x27;, // Write output to null device (discard audio output)&#xA;                    &#x27;-hide_banner&#x27;, &#x27;-nostats&#x27; // Suppress ffmpeg&#x27;s initial info and progress stats&#xA;                ], { stdio: [&#x27;pipe&#x27;, &#x27;pipe&#x27;, &#x27;pipe&#x27;] }); // Explicitly pipe stdin, stdout, stderr&#xA;&#xA;                // --- CRITICAL: Event Handlers for ffmpeg process ---&#xA;&#xA;                // 1. Handle errors during spawning or execution (e.g., ffmpeg not found)&#xA;                ffmpeg.on(&#x27;error&#x27;, (err: any) => {&#xA;                    log.error(`[FFmpeg] Failed to spawn or execute FFmpeg process:`, err);&#xA;                    cleanup(true, new Error(`FFmpeg process error: ${err.message}`));&#xA;                });&#xA;&#xA;                // 2. Accumulate stderr output (where replaygain results and ffmpeg errors are printed)&#xA;                ffmpeg.stderr.on(&#x27;data&#x27;, (data: Buffer) => {&#xA;                    output &#x2B;= data.toString(&#x27;utf8&#x27;);&#xA;                });&#xA;&#xA;                // 3. Handle process exit (success or failure)&#xA;                ffmpeg.on(&#x27;close&#x27;, (code: number) => { // &#x27;close&#x27; indicates process has exited&#xA;                    log.debug(`[FFmpeg] FFmpeg process exited with code: ${code}.`);&#xA;                    if (code !== 0) { // Non-zero exit code means failure&#xA;                        log.error(`[FFmpeg] FFmpeg process exited with non-zero code ${code}. Output:\n${output}`);&#xA;                        cleanup(true, new Error(`FFmpeg process failed with exit code ${code}. Output: ${output}`));&#xA;                        return;&#xA;                    }&#xA;&#xA;                    // If successful exit (code 0), parse the output&#xA;                    if (!output.includes("track_gain")) {&#xA;                        log.error(`[FFmpeg] &#x27;track_gain&#x27; not found in FFmpeg output (exit code 0). Output:\n${output}`);&#xA;                        cleanup(true, new Error(`&#x27;track_gain&#x27; not found in FFmpeg output. Output: ${output}`));&#xA;                        return;&#xA;                    }&#xA;&#xA;                    try {&#xA;                        // Regex to parse track_gain (e.g., "&#x2B;6.53 dB" or "-12.00 dB")&#xA;                        const gainMatch = output.match(/track_gain\s*=\s*([&#x2B;-]?\d&#x2B;\.?\d*)\s*dB/);&#xA;                        if (gainMatch &amp;&amp; gainMatch[1]) {&#xA;                            const gain = parseFloat(gainMatch[1]);&#xA;                            log.debug(`[FFmpeg] Replay gain volume: ${gain} dB.`);&#xA;                            cleanup(); // Clean up on success&#xA;                            resolve(gain);&#xA;                        } else {&#xA;                            log.error(`[FFmpeg] Failed to parse gain from FFmpeg output. Output:\n${output}`);&#xA;                            cleanup(true, new Error(`Failed to parse gain from FFmpeg output. Output: ${output}`));&#xA;                        }&#xA;                    } catch (parseError: any) {&#xA;                        log.error(`[FFmpeg] Error parsing FFmpeg replay gain output:`, parseError);&#xA;                        cleanup(true, new Error(`Error parsing FFmpeg output: ${parseError.message}. Output: ${output}`));&#xA;                    }&#xA;                });&#xA;&#xA;                // 4. Write audio data to ffmpeg&#x27;s stdin&#xA;                // This is the only write operation that could throw EPIPE in this function.&#xA;                try {&#xA;                    ffmpeg.stdin.write(audioData);&#xA;                    ffmpeg.stdin.end(); // Close stdin to signal end of input&#xA;                } catch (stdinError: any) {&#xA;                    log.error(`[FFmpeg] Error writing audioData to FFmpeg stdin:`, stdinError);&#xA;                    // This error means ffmpeg&#x27;s stdin pipe closed unexpectedly.&#xA;                    // This is the direct equivalent of an EPIPE (Broken Pipe) at the child process level.&#xA;                    cleanup(true, new Error(`Failed to pipe audio data to FFmpeg stdin: ${stdinError.message}`));&#xA;                }&#xA;&#xA;            } catch (spawnError: any) { // Catch errors from the spawn call itself (e.g., FFMPEG_PATH is invalid)&#xA;                log.error(`[FFmpeg] Error spawning FFmpeg:`, spawnError);&#xA;                cleanup(true, new Error(`Failed to spawn FFmpeg process: ${spawnError.message}`));&#xA;            }&#xA;        });&#xA;    }&#xA;</number>

    &#xA;

    But unfortunately I still get the same error. Has anyone encountered this problem ? How can I solve it ?

    &#xA;

    I use ffmpeg version 4.2.7-0ubuntu0.1

    &#xA;

  • Write EPIPE after upgrade NodeJS

    28 juillet, par Rougher

    I am using this code for detecting audio replay gain. It was working well with NodeJs 16, but after upgrading to NodeJs 22, it started crashing a few times in an hour with this error :

    &#xA;

    write EPIPE&#xA;    at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:87:19) {&#xA;  errno: -32,&#xA;  code: &#x27;EPIPE&#x27;,&#xA;  syscall: &#x27;write&#x27;&#xA;}&#xA;

    &#xA;

    My original code was

    &#xA;

    static getReplayGainVolume(audioData: Buffer) {&#xA;        // Calculate the mean volume of the audio file at the given filePath&#xA;        var ffmpeg = spawn(&#x27;ffmpeg&#x27;, [&#xA;            &#x27;-i&#x27;, &#x27;-&#x27;,&#xA;            &#x27;-af&#x27;, &#x27;replaygain&#x27;,&#xA;            &#x27;-f&#x27;, &#x27;null&#x27;, &#x27;/dev/null&#x27;,&#xA;            &#x27;-hide_banner&#x27;, &#x27;-nostats&#x27;&#xA;        ]);&#xA;&#xA;        var output = &#x27;&#x27;;&#xA;&#xA;        ffmpeg.stdin.write(audioData);&#xA;        ffmpeg.stdin.end();&#xA;&#xA;        return new Promise((resolve,reject)=>{&#xA;            ffmpeg.on(&#x27;error&#x27;, function (err: any) {&#xA;                reject(err);&#xA;            });&#xA;            &#xA;            ffmpeg.on(&#x27;close&#x27;, function (_code: any) {&#xA;                // [Parsed_replaygain_0 @ 0000000002a2b5c0] track_gain = &#x2B;6.53 dB&#xA;                if (!output.includes("track_gain")) {&#xA;                    reject(output);&#xA;&#xA;                    return;&#xA;                }&#xA;&#xA;                const gainWithDb = output.split("track_gain = ")[1];&#xA;                if (!gainWithDb) {&#xA;                    reject(output);&#xA;&#xA;                    return;&#xA;                }&#xA;&#xA;                const gain = gainWithDb.split(" dB")[0];&#xA;                if (!gain) {&#xA;                    reject(output);&#xA;&#xA;                    return;&#xA;                }&#xA;&#xA;                resolve(parseFloat(gain));&#xA;            });&#xA;            &#xA;            ffmpeg.stderr.on(&#x27;data&#x27;, function (data: any) {&#xA;                // ffmpeg sends all output to stderr. It is not a bug, it is a feature :)&#xA;                var tData = data.toString(&#x27;utf8&#x27;);&#xA;                output &#x2B;= tData;&#xA;            });&#xA;        });&#xA;    }&#xA;

    &#xA;

    Then after search in forums and Google, I improved (I hope I improved it with cleanups)

    &#xA;

    static getReplayGainVolume(audioData: Buffer): Promise<number> {&#xA;        return new Promise((resolve, reject) => {&#xA;            const FFMPEG_PATH = &#x27;ffmpeg&#x27;; // Adjust this if ffmpeg is not in system PATH&#xA;            const FFMPEG_TIMEOUT_MS = 30 * 1000; // 30 seconds timeout for FFmpeg execution&#xA;&#xA;            let ffmpeg: ChildProcessWithoutNullStreams;&#xA;            let output = &#x27;&#x27;; // Accumulate all stderr output&#xA;&#xA;            // Timeout for the FFmpeg process itself&#xA;            const ffmpegTimeout = setTimeout(() => {&#xA;                log.error(`[FFmpeg] FFmpeg process timed out after ${FFMPEG_TIMEOUT_MS / 1000} seconds. Killing process.`);&#xA;                if (ffmpeg &amp;&amp; !ffmpeg.killed) {&#xA;                    ffmpeg.kill(&#x27;SIGKILL&#x27;); // Force kill&#xA;                    reject(new Error(`FFmpeg process timed out and was killed.`));&#xA;                }&#xA;            }, FFMPEG_TIMEOUT_MS);&#xA;&#xA;            // --- Define cleanup function to be called on process exit/error ---&#xA;            const cleanup = (shouldReject = false, error?: Error | string) => {&#xA;                clearTimeout(ffmpegTimeout); // Ensure timeout is cleared&#xA;&#xA;                // Remove all listeners to prevent leaks&#xA;                // This is CRITICAL for long-running bots that spawn many child processes&#xA;                ffmpeg.stdin.removeAllListeners();&#xA;                ffmpeg.stdout.removeAllListeners();&#xA;                ffmpeg.stderr.removeAllListeners();&#xA;                ffmpeg.removeAllListeners(); // Remove process listeners&#xA;&#xA;                if (ffmpeg &amp;&amp; !ffmpeg.killed) { // Ensure ffmpeg process is killed if still alive&#xA;                    ffmpeg.kill(); // Graceful kill (SIGTERM), then wait for exit. If not, then SIGKILL.&#xA;                }&#xA;&#xA;                if (shouldReject) {&#xA;                    reject(error instanceof Error ? error : new Error(String(error)));&#xA;                }&#xA;            };&#xA;&#xA;            try {&#xA;                ffmpeg = spawn(FFMPEG_PATH, [&#xA;                    &#x27;-i&#x27;, &#x27;pipe:0&#x27;, // Read input from stdin (pipe:0)&#xA;                    &#x27;-af&#x27;, &#x27;replaygain&#x27;,&#xA;                    &#x27;-f&#x27;, &#x27;null&#x27;, &#x27;/dev/null&#x27;, // Write output to null device (discard audio output)&#xA;                    &#x27;-hide_banner&#x27;, &#x27;-nostats&#x27; // Suppress ffmpeg&#x27;s initial info and progress stats&#xA;                ], { stdio: [&#x27;pipe&#x27;, &#x27;pipe&#x27;, &#x27;pipe&#x27;] }); // Explicitly pipe stdin, stdout, stderr&#xA;&#xA;                // --- CRITICAL: Event Handlers for ffmpeg process ---&#xA;&#xA;                // 1. Handle errors during spawning or execution (e.g., ffmpeg not found)&#xA;                ffmpeg.on(&#x27;error&#x27;, (err: any) => {&#xA;                    log.error(`[FFmpeg] Failed to spawn or execute FFmpeg process:`, err);&#xA;                    cleanup(true, new Error(`FFmpeg process error: ${err.message}`));&#xA;                });&#xA;&#xA;                // 2. Accumulate stderr output (where replaygain results and ffmpeg errors are printed)&#xA;                ffmpeg.stderr.on(&#x27;data&#x27;, (data: Buffer) => {&#xA;                    output &#x2B;= data.toString(&#x27;utf8&#x27;);&#xA;                });&#xA;&#xA;                // 3. Handle process exit (success or failure)&#xA;                ffmpeg.on(&#x27;close&#x27;, (code: number) => { // &#x27;close&#x27; indicates process has exited&#xA;                    log.debug(`[FFmpeg] FFmpeg process exited with code: ${code}.`);&#xA;                    if (code !== 0) { // Non-zero exit code means failure&#xA;                        log.error(`[FFmpeg] FFmpeg process exited with non-zero code ${code}. Output:\n${output}`);&#xA;                        cleanup(true, new Error(`FFmpeg process failed with exit code ${code}. Output: ${output}`));&#xA;                        return;&#xA;                    }&#xA;&#xA;                    // If successful exit (code 0), parse the output&#xA;                    if (!output.includes("track_gain")) {&#xA;                        log.error(`[FFmpeg] &#x27;track_gain&#x27; not found in FFmpeg output (exit code 0). Output:\n${output}`);&#xA;                        cleanup(true, new Error(`&#x27;track_gain&#x27; not found in FFmpeg output. Output: ${output}`));&#xA;                        return;&#xA;                    }&#xA;&#xA;                    try {&#xA;                        // Regex to parse track_gain (e.g., "&#x2B;6.53 dB" or "-12.00 dB")&#xA;                        const gainMatch = output.match(/track_gain\s*=\s*([&#x2B;-]?\d&#x2B;\.?\d*)\s*dB/);&#xA;                        if (gainMatch &amp;&amp; gainMatch[1]) {&#xA;                            const gain = parseFloat(gainMatch[1]);&#xA;                            log.debug(`[FFmpeg] Replay gain volume: ${gain} dB.`);&#xA;                            cleanup(); // Clean up on success&#xA;                            resolve(gain);&#xA;                        } else {&#xA;                            log.error(`[FFmpeg] Failed to parse gain from FFmpeg output. Output:\n${output}`);&#xA;                            cleanup(true, new Error(`Failed to parse gain from FFmpeg output. Output: ${output}`));&#xA;                        }&#xA;                    } catch (parseError: any) {&#xA;                        log.error(`[FFmpeg] Error parsing FFmpeg replay gain output:`, parseError);&#xA;                        cleanup(true, new Error(`Error parsing FFmpeg output: ${parseError.message}. Output: ${output}`));&#xA;                    }&#xA;                });&#xA;&#xA;                // 4. Write audio data to ffmpeg&#x27;s stdin&#xA;                // This is the only write operation that could throw EPIPE in this function.&#xA;                try {&#xA;                    ffmpeg.stdin.write(audioData);&#xA;                    ffmpeg.stdin.end(); // Close stdin to signal end of input&#xA;                } catch (stdinError: any) {&#xA;                    log.error(`[FFmpeg] Error writing audioData to FFmpeg stdin:`, stdinError);&#xA;                    // This error means ffmpeg&#x27;s stdin pipe closed unexpectedly.&#xA;                    // This is the direct equivalent of an EPIPE (Broken Pipe) at the child process level.&#xA;                    cleanup(true, new Error(`Failed to pipe audio data to FFmpeg stdin: ${stdinError.message}`));&#xA;                }&#xA;&#xA;            } catch (spawnError: any) { // Catch errors from the spawn call itself (e.g., FFMPEG_PATH is invalid)&#xA;                log.error(`[FFmpeg] Error spawning FFmpeg:`, spawnError);&#xA;                cleanup(true, new Error(`Failed to spawn FFmpeg process: ${spawnError.message}`));&#xA;            }&#xA;        });&#xA;    }&#xA;</number>

    &#xA;

    But unfortunately I still get the same error. Has anyone encountered this problem ? How can I solve it ?

    &#xA;

    I use ffmpeg version 4.2.7-0ubuntu0.1

    &#xA;

    Thanks.

    &#xA;

  • FFmpeg-wasm unpredictable errors with next js

    21 juillet, par hjtomi

    I use ffmpeg-wasm in next-js.

    &#xA;

    Whenever I execute an ffmpeg command it has a chance that it throws a runtime error "memory access out of bounds". In this case I created a page that has an file selection html element and a button that converts each image to .jpeg format with the following command :

    &#xA;

    await ffmpeg.exec([&#x27;-i&#x27;, originalFileName, &#x27;-q:v&#x27;, &#x27;5&#x27;, convertedFileName]);&#xA;

    &#xA;

    I have tried the same process with different images, extensions, sizes, restarting the application, restarting the computer, different browsers, mobile/desktop and sometimes it works, sometimes it doesn't. I feel like there is something that is out of my control.

    &#xA;

    Maybe something that has to do with webassembly itself.

    &#xA;

    In each runtime it has a 50-50 chance that ffmpeg will work or not.

    &#xA;

    I have tried changing the target extension to different types.

    &#xA;

    I have tried changing the ffmpeg command entirely to make it do something else.

    &#xA;

    Writing the files to the virtual file system works as expected.

    &#xA;

    I am on windows 10 using next-js version 15.3.5 and ffmpeg version 0.12.10

    &#xA;

    &#x27;use client&#x27;;&#xA;&#xA;import * as React from &#x27;react&#x27;;&#xA;import { FFmpeg } from &#x27;@ffmpeg/ffmpeg&#x27;; // Import FFmpeg&#xA;import { fetchFile, toBlobURL } from &#x27;@ffmpeg/util&#x27;; // Import fetchFile&#xA;&#xA;export default function Page() {&#xA;  const [files, setFiles] = React.useState([]);&#xA;  const [error, setError] = React.useState<string null="null">(null);&#xA;&#xA;  // FFmpeg related states and ref&#xA;  const ffmpegRef = React.useRef<ffmpeg null="null">(null);&#xA;  const [ffmpegLoaded, setFFmpegLoaded] = React.useState(false);&#xA;  const [isConvertingImages, setIsConvertingImages] = React.useState(false);&#xA;  const [videoGenerationProgress, setVideoGenerationProgress] = React.useState<string>(&#x27;&#x27;);&#xA;&#xA;  // --- Load FFmpeg on component mount ---&#xA;  React.useEffect(() => {&#xA;    const loadFFmpeg = async () => {&#xA;      const baseURL = "https://unpkg.com/@ffmpeg/core@0.12.10/dist/umd";&#xA;      try {&#xA;        const ffmpeg = new FFmpeg();&#xA;        // Set up logging for FFmpeg progress&#xA;        ffmpeg.on(&#x27;log&#x27;, ({ message }) => {&#xA;          if (message.includes(&#x27;frame=&#x27;)) {&#xA;            setVideoGenerationProgress(message);&#xA;          }&#xA;        });&#xA;        await ffmpeg.load({&#xA;              coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),&#xA;              wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"),&#xA;            });&#xA;        ffmpegRef.current = ffmpeg;&#xA;        setFFmpegLoaded(true);&#xA;        console.log(&#x27;FFmpeg loaded successfully!&#x27;);&#xA;      } catch (err) {&#xA;        console.error(&#x27;Failed to load FFmpeg:&#x27;, err);&#xA;        setError(&#x27;Failed to load video processing tools.&#x27;);&#xA;      }&#xA;    };&#xA;&#xA;    loadFFmpeg();&#xA;  }, []);&#xA;&#xA;  // --- Internal file selection logic ---&#xA;  const handleFileChange = (e: React.ChangeEvent<htmlinputelement>) => {&#xA;    if (e.target.files &amp;&amp; e.target.files.length > 0) {&#xA;      const newFiles = Array.from(e.target.files).filter(file => file.type.startsWith(&#x27;image/&#x27;)); // Only accept images&#xA;      setFiles(newFiles);&#xA;      setError(null);&#xA;    }&#xA;  };&#xA;&#xA;  // --- Handle Image conversion ---&#xA;  const handleConvertImages = async () => {&#xA;    if (!ffmpegLoaded || !ffmpegRef.current) {&#xA;      setError(&#x27;FFmpeg is not loaded yet. Please wait.&#x27;);&#xA;      return;&#xA;    }&#xA;    if (files.length === 0) {&#xA;      setError(&#x27;Please select images first to generate a video.&#x27;);&#xA;      return;&#xA;    }&#xA;&#xA;    setIsConvertingImages(true);&#xA;    setError(null);&#xA;    setVideoGenerationProgress(&#x27;&#x27;);&#xA;&#xA;    try {&#xA;      const ffmpeg = ffmpegRef.current;&#xA;      const targetExtension = &#x27;jpeg&#x27;; // &lt;--- Define your target extension here (e.g., &#x27;png&#x27;, &#x27;webp&#x27;)&#xA;      const convertedImageNames: string[] = [];&#xA;&#xA;      // Convert all uploaded images to the target format&#xA;      for (let i = 0; i &lt; files.length; i&#x2B;&#x2B;) {&#xA;          const file = files[i];&#xA;          // Give the original file a unique name in FFmpeg&#x27;s VFS&#xA;          const originalFileName = `original_image_${String(i).padStart(3, &#x27;0&#x27;)}.${file.name.split(&#x27;.&#x27;).pop()}`;&#xA;          // Define the output filename with the target extension&#xA;          const convertedFileName = `converted_image_${String(i).padStart(3, &#x27;0&#x27;)}.${targetExtension}`;&#xA;          convertedImageNames.push(convertedFileName);&#xA;&#xA;          // Write the original file data to FFmpeg&#x27;s virtual file system&#xA;          await ffmpeg.writeFile(`${originalFileName}`, await fetchFile(file));&#xA;          console.log(`Wrote original ${originalFileName} to FFmpeg FS`);&#xA;&#xA;          setVideoGenerationProgress(`Converting ${file.name} to ${targetExtension.toUpperCase()}...`);&#xA;&#xA;          await ffmpeg.exec([&#x27;-i&#x27;, originalFileName, &#x27;-q:v&#x27;, &#x27;5&#x27;, convertedFileName]);     //    &lt;------&#xA;&#xA;          console.log(`Converted ${originalFileName} to ${convertedFileName}`);&#xA;&#xA;          // Delete the original file from VFS to free up memory&#xA;          await ffmpeg.deleteFile(originalFileName);&#xA;          console.log(`Deleted original ${originalFileName} from FFmpeg FS`);&#xA;      }&#xA;&#xA;      setFiles([]);&#xA;&#xA;      setVideoGenerationProgress(`All images converted to ${targetExtension.toUpperCase()}.`);&#xA;    } catch (err) {&#xA;      console.error(&#x27;Error converting images:&#x27;, err);&#xA;      setError(`Failed to convert images: ${err instanceof Error ? err.message : String(err)}`);&#xA;    } finally {&#xA;      setIsConvertingImages(false);&#xA;    }&#xA;  };&#xA;&#xA;  return (&#xA;    <div classname="min-h-screen bg-gray-100 flex items-center justify-center p-4 relative overflow-hidden">&#xA;      <div classname="bg-white p-8 rounded-lg shadow-xl w-full max-w-md z-10 relative"> {/* Ensure content is above overlay */}&#xA;        <h2 classname="text-2xl font-semibold text-gray-800 mb-6 text-center">Select your images</h2>&#xA;&#xA;        {/* Regular File Input Area (still needed for click-to-select) */}&#xA;        > document.getElementById(&#x27;file-input&#x27;)?.click()}&#xA;        >&#xA;        &#xA;          &#xA;          <svg classname="mx-auto h-12 w-12 text-gray-400" fill="none" viewbox="0 0 24 24" stroke="currentColor">&#xA;            <path strokelinecap="round" strokelinejoin="round" strokewidth="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>&#xA;          </svg>&#xA;          <p classname="mt-2 text-sm text-gray-600">&#xA;            <span classname="font-medium text-blue-600">Click to select</span>&#xA;          </p>&#xA;          <p classname="text-xs text-gray-500">Supports multiple formats</p>&#xA;        </div>&#xA;&#xA;        {/* Selected Files Display */}&#xA;        {files.length > 0 &amp;&amp; (&#xA;          <div data-testid="selected-files-display" classname="selected-files-display mt-4 p-3 bg-blue-50 border border-blue-200 rounded-md text-sm text-blue-800">&#xA;            <p classname="font-semibold mb-2">Selected Files ({files.length}):</p>&#xA;            <ul classname="max-h-40 overflow-y-auto">&#xA;              {files.map((file) => (&#xA;                <li key="{file.name}" classname="flex items-center justify-between py-1">&#xA;                  <span>{file.name}</span>&#xA;                </li>&#xA;              ))}&#xA;            </ul>&#xA;          </div>&#xA;        )}&#xA;&#xA;        {/* Error Message */}&#xA;        {error &amp;&amp; (&#xA;          <div classname="mt-4 p-3 bg-red-50 border border-red-200 rounded-md text-sm text-red-800">&#xA;            {error}&#xA;          </div>&#xA;        )}&#xA;&#xA;        {/* Image conversion Progress/Status */}&#xA;        {isConvertingImages &amp;&amp; (&#xA;          <div classname="mt-4 p-3 bg-purple-50 border border-purple-200 rounded-md text-sm text-purple-800 text-center">&#xA;            <p classname="font-semibold">Converting images</p>&#xA;            <p classname="text-xs mt-1">{videoGenerationProgress}</p>&#xA;          </div>&#xA;        )}&#xA;        {!ffmpegLoaded &amp;&amp; (&#xA;          <div classname="mt-4 p-3 bg-yellow-50 border border-yellow-200 rounded-md text-sm text-yellow-800 text-center">&#xA;            Loading video tools (FFmpeg)... Please wait.&#xA;          </div>&#xA;        )}&#xA;&#xA;        {/* Convert images button */}&#xA;        &#xA;          Convert images&#xA;        &#xA;      </div>&#xA;    &#xA;  );&#xA;}&#xA;</htmlinputelement></string></ffmpeg></string>

    &#xA;

    The next js page above

    &#xA;