Spaces:
Paused
Paused
| /* eslint-disable jsdoc/require-jsdoc */ | |
| const axios = require("axios"); | |
| const FormData = require("form-data"); | |
| const fs = require("fs"); | |
| const { exec } = require("child_process"); | |
| const path = require("path"); | |
| async function ttt(link) { | |
| try { | |
| const form = new FormData(); | |
| form.append("url", link); | |
| form.append("count", "12"); | |
| form.append("cursor", "0"); | |
| form.append("web", "1"); | |
| form.append("hd", "1"); | |
| const { data } = await axios.post("https://www.tikwm.com/api/", form); | |
| if (data.code === 0 && data.msg === "success") { | |
| const baseUrl = "https://www.tikwm.com"; | |
| data.data.cover = baseUrl + data.data.cover; | |
| data.data.play = baseUrl + data.data.play; | |
| data.data.wmplay = baseUrl + data.data.wmplay; | |
| data.data.hdplay = baseUrl + data.data.hdplay; | |
| data.data.music = baseUrl + data.data.music; | |
| data.data.author.avatar = baseUrl + data.data.author.avatar; | |
| } | |
| const res = { | |
| status: 200, | |
| type: "tiktok", | |
| title: data.data?.title || null, | |
| desc: null, | |
| thumbnail: data.data.cover, | |
| video: [{ url: data.data.hdplay }, { url: data.data.play }], | |
| music: [{ url: data.data.music }], | |
| image: data.data.images, | |
| }; | |
| /* | |
| if (res.image) { | |
| const outputDir = path.join(__dirname, "../tmp") | |
| await downloadImages(res.image, outputDir); | |
| const FNvd = `tt_video_${Date.now()}.mp4`; | |
| const FNad = `tt_audio_${Date.now()}.mp3`; | |
| const videoPath = `${outputDir}/${FNvd}`; | |
| const audioPath = `${outputDir}/${FNad}`; | |
| // β Unduh audio sebelum dipakai | |
| await downloadAudio(res.music[0].url, audioPath); | |
| const slideResult = await createSlideshow(outputDir, videoPath, audioPath); | |
| if (slideResult) { | |
| res.video.push({ resolusi: "Slideshow", url: `https://fullperr-hutatools.hf.space/tmp/${FNvd}`}); | |
| } | |
| }*/ | |
| return res; | |
| } catch (e) { | |
| return { status: 500, message: e.message }; | |
| } | |
| } | |
| async function downloadAudio(audioUrl, outputFile) { | |
| const response = await axios({ | |
| url: audioUrl, | |
| responseType: "stream", | |
| }); | |
| return new Promise((resolve, reject) => { | |
| const writer = fs.createWriteStream(outputFile); | |
| response.data.pipe(writer); | |
| writer.on("finish", () => { | |
| console.log(`β Audio telah diunduh: ${outputFile}`); | |
| resolve(outputFile); | |
| }); | |
| writer.on("error", reject); | |
| }); | |
| } | |
| async function downloadImages(imageUrls, outputDir) { | |
| if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir); | |
| let downloadPromises = imageUrls.map((url, index) => { | |
| const filePath = `${outputDir}/image${index + 1}.jpg`; | |
| return axios({ url, responseType: "stream" }).then((response) => { | |
| return new Promise((resolve, reject) => { | |
| const writer = fs.createWriteStream(filePath); | |
| response.data.pipe(writer); | |
| writer.on("finish", resolve); | |
| writer.on("error", reject); | |
| }); | |
| }); | |
| }); | |
| await Promise.all(downloadPromises); | |
| console.log("β Semua gambar telah diunduh."); | |
| } | |
| async function getAudioDuration(audioFile) { | |
| return new Promise((resolve, reject) => { | |
| exec( | |
| `ffprobe -i ${audioFile} -show_entries format=duration -v quiet -of csv="p=0"`, | |
| (error, stdout) => { | |
| if (error) { | |
| reject("β Gagal mendapatkan durasi audio."); | |
| return; | |
| } | |
| resolve(parseFloat(stdout.trim())); | |
| } | |
| ); | |
| }); | |
| } | |
| async function createSlideshow(imagesDir, outputVideo, audioFile) { | |
| const imageFiles = fs | |
| .readdirSync(imagesDir) | |
| .filter((file) => file.endsWith(".jpg")); | |
| const totalImages = imageFiles.length; | |
| if (totalImages === 0) { | |
| console.error("β Tidak ada gambar untuk slideshow."); | |
| return null; | |
| } | |
| try { | |
| let audioDuration = await getAudioDuration(audioFile); | |
| if (audioDuration > 25) { | |
| console.log("β³ Audio lebih dari 25 detik, membatasi ke 25 detik."); | |
| audioDuration = 25; | |
| } | |
| const outputDir = path.join(__dirname, "../tmp") | |
| const frameDuration = audioDuration / totalImages; | |
| const tempDirv = `${outputDir}/temp_video_${Date.now()}.mp4`; | |
| console.log(`π Durasi audio: ${audioDuration}s`); | |
| console.log(`πΌ Durasi per gambar: ${frameDuration}s`); | |
| const ffmpegCommand = `ffmpeg -framerate 1/${frameDuration} -i ${imagesDir}/image%d.jpg -filter_complex "pad=iw:ih:(ow-iw)/2:(oh-ih)/2" -r 25 -c:v libx264 -pix_fmt yuv420p ${tempDirv}`; | |
| console.log("π Membuat slideshow..."); | |
| return new Promise((resolve, reject) => { | |
| exec(ffmpegCommand, (error) => { | |
| if (error) { | |
| console.error("β Error saat membuat slideshow:", error); | |
| reject(null); | |
| return; | |
| } | |
| console.log("β Slideshow selesai dibuat."); | |
| const audioCommand = `ffmpeg -i ${tempDirv} -i ${audioFile} -filter_complex "[1:a]apad" -shortest -c:v libx264 -pix_fmt yuv420p -c:a aac -b:a 128k ${outputVideo}`; | |
| exec(audioCommand, (audioError) => { | |
| if (audioError) { | |
| console.error("β Error saat menambahkan audio:", audioError); | |
| reject(null); | |
| return; | |
| } | |
| console.log("β Video dengan audio selesai dibuat."); | |
| console.log("π§Ή Folder gambar sementara dihapus."); | |
| resolve(outputVideo); | |
| }); | |
| }); | |
| }); | |
| } catch (error) { | |
| console.error(error); | |
| return null; | |
| } | |
| } | |
| module.exports = { ttt }; |