Download server.js from clove1002/zhs_tool: direct link, hf CLI and curl.
- Browser
- Download file 4.63 kB
-
https://huggingface.co/spaces/clove1002/zhs_tool/resolve/main/server.js
- Command line
-
hf download hf://spaces/clove1002/zhs_tool/server.js
-
curl -L -o server.js https://huggingface.co/spaces/clove1002/zhs_tool/resolve/main/server.js
4.63 kB
| const express = require('express'); | |
| const fileUpload = require('express-fileupload'); | |
| const ffmpeg = require('fluent-ffmpeg'); | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const { execSync } = require('child_process'); | |
| const app = express(); | |
| const PORT = process.env.PORT || 7860; | |
| // Get ffmpeg path dynamically | |
| try { | |
| const ffmpegPath = execSync('which ffmpeg').toString().trim(); | |
| ffmpeg.setFfmpegPath(ffmpegPath); | |
| console.log('[INFO] FFmpeg path:', ffmpegPath); | |
| } catch (err) { | |
| console.error('[ERROR] FFmpeg not found'); | |
| } | |
| app.use(fileUpload()); | |
| app.use(express.json()); | |
| // Health check | |
| app.get('/', (req, res) => { | |
| res.send('🟢 FFmpeg API is online'); | |
| }); | |
| // Clip video | |
| app.post('/api/clip', async (req, res) => { | |
| const { start, duration, format } = req.body; | |
| if (!req.files || !req.files.video) return res.status(400).send('No video uploaded'); | |
| const inputPath = path.join('/tmp', 'input.mp4'); | |
| const outputExt = format || 'mp4'; | |
| const outputPath = path.join('/tmp', `output.${outputExt}`); | |
| try { | |
| await req.files.video.mv(inputPath); | |
| ffmpeg(inputPath) | |
| .setStartTime(start || 0) | |
| .setDuration(duration || 5) | |
| .toFormat(outputExt) | |
| .output(outputPath) | |
| .on('start', cmd => console.log('[FFmpeg]', cmd)) | |
| .on('stderr', line => console.log('[FFmpeg]', line)) | |
| .on('end', () => { | |
| res.setHeader('Content-Type', 'video/mp4'); | |
| const readStream = fs.createReadStream(outputPath); | |
| readStream.pipe(res); | |
| readStream.on('close', () => { | |
| fs.unlinkSync(inputPath); | |
| fs.unlinkSync(outputPath); | |
| }); | |
| }) | |
| .on('error', err => { | |
| console.error('[FFmpeg Error]', err); | |
| res.status(500).send('Video processing failed'); | |
| }) | |
| .run(); | |
| } catch (err) { | |
| console.error('[Server Error]', err); | |
| res.status(500).send('Internal error'); | |
| } | |
| }); | |
| // convert mpd to mp4 | |
| app.post('/api/mpd-to-mp4', async (req, res) => { | |
| const { url } = req.body; | |
| const outputPath = path.join('/tmp', 'output.mp4'); | |
| if (!url) return res.status(400).send('Missing URL'); | |
| try { | |
| ffmpeg(url) | |
| .outputOptions('-c copy') | |
| .on('start', cmd => console.log('[FFmpeg]', cmd)) | |
| .on('stderr', line => console.log('[FFmpeg]', line)) | |
| .on('end', () => { | |
| const readStream = fs.createReadStream(outputPath); | |
| res.setHeader('Content-Type', 'video/mp4'); | |
| readStream.pipe(res); | |
| readStream.on('close', () => { | |
| fs.unlinkSync(outputPath); | |
| }); | |
| }) | |
| .on('error', err => { | |
| console.error('[FFmpeg Error]', err); | |
| res.status(500).send('Failed to fetch video'); | |
| }) | |
| .save(outputPath); | |
| } catch (err) { | |
| console.error('[Server Error]', err); | |
| res.status(500).send('Internal server error'); | |
| } | |
| }); | |
| // reframe video and add text on top | |
| // Add text with white background at the top of the video | |
| app.post('/api/add-text-on-top', async (req, res) => { | |
| if ( | |
| !req.files || | |
| !req.files.video || | |
| !req.body.textcolor || | |
| !req.body.height || | |
| !req.body.drawtextFilters | |
| ) { | |
| return res.status(400).send('Missing Params'); | |
| } | |
| const uploadedVideo = req.files.video; | |
| const drawtextFilters = req.body.drawtextFilters; | |
| const textColor = req.body.textcolor; | |
| const height = req.body.height; | |
| const inputPath = path.join('/tmp', 'input.mp4'); | |
| const outputPath = path.join('/tmp', 'output.mp4'); | |
| await uploadedVideo.mv(inputPath); | |
| let ffmpegLogs = ''; | |
| ffmpeg(inputPath) | |
| .videoFilters([ | |
| `drawbox=x=0:y=0:w=iw:h=${height}:color=${textColor}:t=fill`, | |
| `${drawtextFilters}` | |
| ]) | |
| .on('stderr', line => { | |
| console.log('[FFmpeg]', line); | |
| ffmpegLogs += line + '\n'; | |
| }) | |
| .on('end', () => { | |
| res.download(outputPath, 'video_with_text.mp4', err => { | |
| if (err) console.error('Download error:', err); | |
| fs.unlinkSync(inputPath); | |
| fs.unlinkSync(outputPath); | |
| }); | |
| }) | |
| .on('error', err => { | |
| console.error('FFmpeg error:', err); | |
| if (fs.existsSync(inputPath)) fs.unlinkSync(inputPath); | |
| if (fs.existsSync(outputPath)) fs.unlinkSync(outputPath); | |
| res.status(500).send(`Failed to add text to video:\n${ffmpegLogs.slice(0, 500)}`); | |
| }) | |
| .save(outputPath); | |
| }); | |
| // Clean files every 15 min | |
| setInterval(() => { | |
| for (const file of ['input.mp4', 'output.mp4']) { | |
| const filePath = path.join('/tmp', file); | |
| if (fs.existsSync(filePath)) fs.unlinkSync(filePath); | |
| } | |
| }, 15 * 60 * 1000); | |
| app.listen(PORT, () => console.log(`✅ Server running at http://localhost:${PORT}`)); |