-const{mkdirSync:mkdirSync}=require("fs"),chalk=require("chalk"),path=require("path");let ffmpegGlobalProcess=null;function decodeUnicodeEscape(e){return e.replace(/\\u([\d\w]{4})/gi,((e,t)=>String.fromCharCode(parseInt(t,16))))}const videoProcessing=async({liveUrl:e,rtmpServer:t=null,rtmpKey:o=null,isInfiniteMode:r=!1,streamDuration:s=null,baseFilePath:a="",mode:i="stream"})=>{console.log(chalk.blue("Starting the streaming process. Please wait..."));const n=require("child_process"),{existsSync:l}=require("fs");let c,d=null;"restream"===i?t&&o?(c=`${t}${o}`,d=`${a}.flv`):c=`${a}.flv`:c=`${t}${o}`;const generateUniqueFileName=(e,t)=>{let o=0,r=`${e}${o>0?`-${o}`:""}.${t}`;for(;l(r);)o++,r=`${e}${o>0?`-${o}`:""}.${t}`;return r};if(d&&l(d)&&(d=generateUniqueFileName(a,"flv")),l(c)){const e=c.split(".").pop();c=generateUniqueFileName(a,e)}process.stdout.clearLine(0),process.stdout.cursorTo(0);const p=[chalk.white("Live Url : ")+chalk.red(`${e}`),chalk.white("Output Destination : ")+chalk.yellow(`${c}`),chalk.white("Secondary Output : ")+chalk.green(`${d||"Not Applicable"}`)].join("\n"),m=Math.max(0,p.length-chalk.reset(p).length+20),u=chalk.blue("=".repeat(m));process.stdout.write(`${u}\n${p}\n${u}`);const h=["-re","-stream_loop","-1","-i",e,"-r","30","-b:v","2000k","-c:v","libx264","-preset","veryfast","-c:a","aac","-f","flv","-loglevel","info","-hide_banner",c];d&&h.push(d),s&&h.push("-t",s.toString()),ffmpegGlobalProcess=n.spawn("ffmpeg",h);let f=0,g=0,S=0;ffmpegGlobalProcess.stderr.on("data",(e=>{const t=e.toString(),o=t.match(/Duration: (\d{2}):(\d{2}):(\d{2})\.\d{2},/),r=t.match(/time=(\d{2}):(\d{2}):(\d{2})\.\d{2}/);if(o&&(g=3600*parseInt(o[1])+60*parseInt(o[2])+parseInt(o[3])),r){S=3600*parseInt(r[1])+60*parseInt(r[2])+parseInt(r[3]);const e=(S/g*100).toFixed(2);s&&S>=s&&ffmpegGlobalProcess.kill("SIGINT"),process.stdout.clearLine(0),process.stdout.cursorTo(0),process.stdout.write(chalk.green(`Streaming Progress: [${e}%] | Time Elapsed: ${S} seconds`))}t.includes("error")&&(f+=1,f>5&&(console.log(chalk.red("We encountered a problem and need to stop. Please try again.")),ffmpegGlobalProcess.kill("SIGINT")))})),ffmpegGlobalProcess.on("close",((e,n)=>{const l=`Streaming process exited with code: ${e} and signal: ${n}`;0!==e?(console.log(chalk.magenta(l)),console.log(chalk.yellow("The streaming process has ended unexpectedly. Attempting to restart..."))):console.log(chalk.green("Streaming completed successfully.")),(r||f<=5&&"restream"===i&&t&&o)&&(console.log(chalk.blue("Restarting in 3 seconds...")),setTimeout((()=>{try{videoProcessing({liveUrl:d,rtmpServer:t,rtmpKey:o,isInfiniteMode:r,streamDuration:s,baseFilePath:a,mode:i})}catch(e){console.error(chalk.red(`Error restarting streaming process: ${e.message}`))}}),3e3))})),process.on("SIGINT",(()=>{console.log(chalk.magenta("Streaming has been stopped. Thank you for using our service.")),ffmpegGlobalProcess&&ffmpegGlobalProcess.kill("SIGINT"),process.exit()}))},reStreamShopee=async({videoUrl:e,rtmpServer:t=null,rtmpKey:o=null,isInfiniteMode:r=!1,streamDuration:s=null})=>new Promise((async(a,i)=>{try{let a=null,i=`${__dirname}/../${e}`,n="stream";if(!e.includes("mp4")&&!e.includes("flv")){if(a=await getShopeeStreamDetails(e),!a)throw new Error("Stream details could not be retrieved.");i=decodeURIComponent(a.play_url),n="restream"}const l=`${__dirname}/../stream_output/${a?.room_id}-${a?.username}`;console.info("\nCtrl+C to stop downloading and exit"),await videoProcessing({liveUrl:i,rtmpServer:t,rtmpKey:o,isInfiniteMode:r,streamDuration:s,baseFilePath:l,mode:n})}catch(e){i(e),console.error("Error in reStreamShopee function: ",e)}})),streamDownloader=async({videoUrl:e,durasiVideo:t=null,rtmpServer:o=null,rtmpKey:r=null,isInfinite:s=!1})=>{try{const a=getStreamProvider(e);let i=null;switch(a){case"shopee":case"filestream":i=await reStreamShopee({videoUrl:e,streamDuration:t?60*t:null,rtmpServer:o,rtmpKey:r,isInfiniteMode:s});break;case"tiktok":i=await tiktokDownload({videoUrl:e,duration:t?60*parseInt(t):null,rtmpServer:o,rtmpKey:r,isInfiniteMode:s});break;default:throw Error(`Platform "${a}" Not Supported Yet!`)}return i}catch(e){throw Error(e)}},getStreamProvider=e=>{let t="";if(e.includes("http")){t=new URL(e).hostname}else t="filestream";return t.includes("tiktok.com")?"tiktok":t.includes("youtube.com")?"youtube":t.includes("twitch.tv")?"twitch":t.includes("facebook.com")?"facebook":t.includes("tokopedia.com")?"tokopedia":t.includes("shopee.co.id")?"shopee":t.includes("file")?"filestream":t},getShopeeStreamDetails=async e=>{const t=new URL(e).searchParams.get("session");return fetch(`https://live.shopee.co.id/api/v1/session/${t}`,{headers:{Host:"live.shopee.co.id","Sec-Ch-Ua":'"Brave";v="119", "Chromium";v="119", "Not?A_Brand";v="24"',"Sec-Ch-Ua-Mobile":"?0","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36","X-Api-Source":"pc","Content-Type":"application/json",Accept:"application/json","X-Shopee-Language":"id","X-Requested-With":"XMLHttpRequest","Sec-Ch-Ua-Platform":'"macOS"',"Sec-Gpc":"1","Accept-Language":"id-ID,id;q=0.6","Sec-Fetch-Site":"same-origin","Sec-Fetch-Mode":"cors","Sec-Fetch-Dest":"empty",Referer:"https://shopee.co.id/?is_from_login=true&is_from_login=true","Accept-Encoding":"gzip, deflate, br"}}).then((async e=>{const t=await e.json();return 0==t.err_code?t?.data?.session:t}))},tiktokStreamData=async e=>{const t=`https://www.tiktok.com/@${e}/live`,o=(await fetch(t,{headers:{"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"}}).then((e=>e.text()))).match(/room_id=(\d+)/);if(!o)throw new Error("No live stream found");const r=o[1];console.info(`\nFound live stream with room id ${r}!`);const s=`https://www.tiktok.com/api/live/detail/?aid=1988&roomID=${r}`,{LiveRoomInfo:a}=await fetch(s).then((e=>e.json())),{title:i,liveUrl:n}=a;return{title:i,liveUrl:n}},tiktokDownload=async({videoUrl:e,output:t="stream_output",format:o="mp4",duration:r=0,rtmpKey:s=null,rtmpServer:a=null,isInfiniteMode:i=!1})=>new Promise((async(n,l)=>{try{const n=e.split("/"),l=n[n.length-2].replace("@",""),{title:c,liveUrl:d}=await tiktokStreamData(l),p=t.endsWith(o)?t:`${t.replace(/\/$/,"")}/${l}-${Date.now()}.${o}`;mkdirSync(path.dirname(p),{recursive:!0}),console.info("\nCtrl+C to stop downloading and exit"),await videoProcessing({liveUrl:d,rtmpServer:a,rtmpKey:s,isInfiniteMode:i,streamDuration:r,baseFilePath:p,mode:"restream"})}catch(e){l(e)}}));module.exports={ffmpegGlobalProcess:ffmpegGlobalProcess,reStreamShopee:reStreamShopee,getShopeeStreamDetails:getShopeeStreamDetails,decodeUnicodeEscape:decodeUnicodeEscape,streamDownloader:streamDownloader};
0 commit comments