Compare commits

..

No commits in common. "main" and "1.8.3" have entirely different histories.
main ... 1.8.3

9 changed files with 640 additions and 1311 deletions

View file

@ -1,28 +0,0 @@
name: Upload release
on:
push:
tags:
- "*"
jobs:
build:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: |
npm install --platform=win32 &&
apt-get update &&
apt-get install -y zip
- name: Build the app
run: npm run make -- --platform=win32
- name: Create release
uses: akkuman/gitea-release-action@v1
with:
token: ${{ secrets.TOKEN }}
files: out/make/zip/win32/x64/dsr-win32-x64-${{ github.ref_name }}.zip
draft: true

View file

@ -1,10 +1,10 @@
# Discord Video Sharing [![status-badge](https://git.mylloon.fr/Anri/dsr/badges/workflows/release.yml/badge.svg)](https://git.mylloon.fr/Anri/dsr/actions?workflow=release.yml) # Discord Video Sharing
Tool for sharing video to Discord. Tool for sharing video to Discord.
> This tool was primarily made for video captured by NVidia Shadowplay. > This tool was primarily made for video captured by NVidia Shadowplay.
## Download/Install/Update ## Download/Install
2 choices : 2 choices :
@ -15,40 +15,22 @@ Tool for sharing video to Discord.
irm https://git.mylloon.fr/Anri/dsr/raw/branch/main/install.ps1 | iex irm https://git.mylloon.fr/Anri/dsr/raw/branch/main/install.ps1 | iex
``` ```
## Available flags > - If you have Discord Nitro: add `/nitro` flag when running DSR.
> - If you have an NVidia GPU with NVenc: add `/nvenc` flag when running DSR.
You can add thoses flags in the `Target` field of your Windows shortcut.
| | |
| ------------- | ------------------------------------------------------ |
| `/nitro` | Increase the file limit to 500Mo |
| `/nitrobasic` | Increase the file limit to 50Mo |
| | |
| `/nvenc_h264` | Enable NVenc with H.264 encoder (NVidia GPU required) |
| `/nvenc_h265` | Enable NVenc with H.265 encoder (NVidia GPU required) |
| `/amd_h264` | Enable AMF using DX11 with H.264 encoder (for AMD GPU) |
| `/amd_h265` | Enable AMF using DX11 with H.265 encoder (for AMD GPU) |
| `/h265` | Enable the H.265 CPU encoder (slow compression) |
> NVidia and AMD hardware accelerators support is experimental, but faster
> than CPU counterparts.
## More info ## More info
- [x] KISS interface - [x] KISS interface
- [x] Support drag&drop into the icon - [x] Support drag&drop into the icon
- [x] Keep the video under discord limitation - [x] Keep the video under 25mb (discord limitation)
- [x] Defaults to H.264 CPU encoder
- [x] If already under the limit, the file won't be compressed
- [x] NVenc support - [x] NVenc support
- [x] AMD cards acceleration support - [x] If already under the limit, the file won't be compressed
- [x] Nitro suppport - [x] Nitro suppport via `/nitro` flag
- [x] Merge 2 audio files into one track when recorded with system audio and microphone - [x] Merge audio files into one track when recorded with system audio and microphone
split up, while keeping the original ones (with conveniant metadata) split up, while keeping the original ones (with conveninant metadata)
- [x] Works also with file with only one or more than 2 audio track, by doing - [x] Works also with file with only one audio track
nothing
- [x] Support multiples files at once - [x] Support multiples files at once
- [x] Always optimize for video streaming - [x] Optimize for video streaming
## Package the app for Windows ## Package the app for Windows

View file

@ -1,41 +1,30 @@
param ( # Enable TLSv1.2 for compatibility with older clients
[switch]$Force
)
# Enable TLSv1.2 for compatibility with older windows versions
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
$path = "$env:LOCALAPPDATA\DSR"
$update = Test-Path -Path $path\*
# Download # Download
$releases = "https://git.mylloon.fr/api/v1/repos/Anri/dsr/releases/latest" $releases = "https://git.mylloon.fr/api/v1/repos/Anri/dsr/releases/latest"
$link = (Invoke-WebRequest $releases | ConvertFrom-Json)[0].assets.browser_download_url $link = (Invoke-WebRequest $releases | ConvertFrom-Json)[0].assets.browser_download_url
$archive = "$env:TEMP\dsr.zip" $archive = "$env:TEMP\dsr.zip"
Invoke-WebRequest -Uri $link -OutFile $archive Invoke-WebRequest -Uri $link -OutFile $archive
Remove-Item "$path" -Recurse -ErrorAction SilentlyContinue Remove-Item "$env:LOCALAPPDATA\DSR" -Recurse -ErrorAction SilentlyContinue
# Close running DSR
Stop-Process -Name "DSR" -Force -ErrorAction SilentlyContinue
# Installation # Installation
Expand-Archive -Path $archive -DestinationPath "$path" -Force Expand-Archive -Path $archive -DestinationPath "$env:LOCALAPPDATA\DSR" -Force
Move-Item -Path "$path\dsr-win32-x64\*" -Destination "$path" -Force Move-Item -Path "$env:LOCALAPPDATA\DSR\dsr-win32-x64\*" -Destination "$env:LOCALAPPDATA\DSR" -Force
Remove-Item "$path\dsr-win32-x64" Remove-Item "$env:LOCALAPPDATA\DSR\dsr-win32-x64"
# Add shortcut
$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$env:APPDATA\Microsoft\Windows\Start Menu\Programs\DSR.lnk")
$Shortcut.TargetPath = "$env:LOCALAPPDATA\DSR\dsr.exe"
$Shortcut.Save()
# Add new app to registry
REG ADD "HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\dsr" /f /v DisplayName /t REG_SZ /d "DSR"
REG ADD "HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\dsr" /f /v InstallLocation /t REG_SZ /d "$env:LOCALAPPDATA\DSR"
# Ask user to add a shortcut to the desktop # Ask user to add a shortcut to the desktop
if (-not $update -Or $Force) { if ($Host.UI.PromptForChoice(
# Add shortcut
$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$env:APPDATA\Microsoft\Windows\Start Menu\Programs\DSR.lnk")
$Shortcut.TargetPath = "$path\dsr.exe"
$Shortcut.Save()
# Add new app to registry
REG ADD "HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\dsr" /f /v DisplayName /t REG_SZ /d "DSR"
REG ADD "HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\dsr" /f /v InstallLocation /t REG_SZ /d "$path"
if ($Host.UI.PromptForChoice(
"***********************", "***********************",
"Add a desktop shortcut?", "Add a desktop shortcut?",
@( @(
@ -45,7 +34,6 @@ if (-not $update -Or $Force) {
$WshShell = New-Object -comObject WScript.Shell $WshShell = New-Object -comObject WScript.Shell
$Desktop = [Environment]::GetFolderPath("Desktop") $Desktop = [Environment]::GetFolderPath("Desktop")
$Shortcut = $WshShell.CreateShortcut("$Desktop\DSR.lnk") $Shortcut = $WshShell.CreateShortcut("$Desktop\DSR.lnk")
$Shortcut.TargetPath = "$path\dsr.exe" $Shortcut.TargetPath = "$env:LOCALAPPDATA\DSR\dsr.exe"
$Shortcut.Save() $Shortcut.Save()
}
} }

1504
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "dsr", "name": "dsr",
"version": "1.11.3", "version": "1.8.3",
"description": "Discord Video Sharing", "description": "Discord Video Sharing",
"main": "./dist/main.js", "main": "./dist/main.js",
"scripts": { "scripts": {
@ -18,23 +18,20 @@
"author": "Mylloon", "author": "Mylloon",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@electron-forge/maker-zip": "^7.5", "@electron-forge/maker-zip": "^7.3",
"ffmpeg-static": "^5.2", "ffmpeg-static": "^5.2",
"ffprobe-static": "^3.1", "ffprobe-static": "^3.1",
"typescript": "^5.6" "typescript": "^5.4"
}, },
"devDependencies": { "devDependencies": {
"@electron-forge/cli": "^7.5", "@electron-forge/cli": "^7.3",
"@types/ffprobe-static": "^2.0", "@types/ffprobe-static": "^2.0",
"electron": "^33.2" "electron": "^29.2"
}, },
"config": { "config": {
"forge": { "forge": {
"packagerConfig": { "packagerConfig": {
"icon": "./image/icon.ico", "icon": "./image/icon.ico"
"asar": {
"unpack": "**/node_modules/*-static/**"
}
}, },
"makers": [ "makers": [
{ {

View file

@ -1,19 +1,15 @@
import { app, BrowserWindow, dialog, ipcMain, Notification } from "electron"; import { BrowserWindow, app, dialog, ipcMain } from "electron";
import { statSync, copyFileSync } from "fs"; import { statSync } from "fs";
import { import {
deleteFile, deleteFile,
deleteTwoPassFiles, deleteTwoPassFiles,
execute, execute,
getNewFilename, getNewFilename,
getVideoDuration, getVideoDuration,
getNumberOfAudioTracks,
printAndDevTool, printAndDevTool,
processes,
} from "./utils/misc"; } from "./utils/misc";
import path = require("path"); import path = require("path");
import ffmpegPath = require("ffmpeg-static");
import ffmpeg = require("ffmpeg-static");
const ffmpegPath = `${ffmpeg}`.replace("app.asar", "app.asar.unpacked");
let error = false; let error = false;
@ -22,12 +18,11 @@ const moviesFilter = {
extensions: ["mp4", "mkv"], extensions: ["mp4", "mkv"],
}; };
const metadataAudioSize = 3;
const metadataAudio = `-metadata:s:a:0 title="System sounds and microphone" \ const metadataAudio = `-metadata:s:a:0 title="System sounds and microphone" \
-metadata:s:a:1 title="System sounds" \ -metadata:s:a:1 title="System sounds" \
-metadata:s:a:2 title="Microphone"`; -metadata:s:a:2 title="Microphone"`;
const shareOpt = "-movflags +faststart"; const extraArgs = "-movflags +faststart";
/** Register a new error */ /** Register a new error */
const registerError = (win: BrowserWindow, err: string) => { const registerError = (win: BrowserWindow, err: string) => {
@ -52,11 +47,6 @@ const createWindow = () => {
return win; return win;
}; };
// For notification on Windows
if (process.platform === "win32") {
app.setAppUserModelId(app.name);
}
/* Ready to create the window */ /* Ready to create the window */
app.whenReady().then(() => { app.whenReady().then(() => {
const win = createWindow(); const win = createWindow();
@ -71,13 +61,6 @@ app.whenReady().then(() => {
/** Send confirmation to user */ /** Send confirmation to user */
const confirmation = async (message: string) => { const confirmation = async (message: string) => {
// Send notification
new Notification({
title: "Status",
body: message,
}).show();
// Open dialog
await dialog.showMessageBox(win, { message }); await dialog.showMessageBox(win, { message });
}; };
@ -85,15 +68,14 @@ app.whenReady().then(() => {
const getFilename = (filepath: string) => path.parse(filepath).base; const getFilename = (filepath: string) => path.parse(filepath).base;
/** Merge all audios track of a video into one /** Merge all audios track of a video into one
* In case video doesn't have exactly two audio streams, silently pass */ * In case video have only one track, silently pass */
const mergeAudio = async (file: string) => { const mergeAudio = async (file: string) => {
const tmpFile = getNewFilename(file, "TMP_"); const tmpFile = getNewFilename(file, "TMP_");
let outFile; let outFile;
let audioTracks = getNumberOfAudioTracks(file); // One track for the video
let nbTracks = 1;
switch (audioTracks.length) {
case 2:
// Merge 2 audio // Merge 2 audio
// See: https://trac.ffmpeg.org/wiki/AudioChannelManipulation#a2stereostereo // See: https://trac.ffmpeg.org/wiki/AudioChannelManipulation#a2stereostereo
await execute( await execute(
@ -102,10 +84,38 @@ app.whenReady().then(() => {
-filter_complex "[0:a]amerge=inputs=2[a]" -ac 2 -map 0:v -map "[a]" \ -filter_complex "[0:a]amerge=inputs=2[a]" -ac 2 -map 0:v -map "[a]" \
-c:v copy \ -c:v copy \
"${tmpFile}"` "${tmpFile}"`
); )
.catch(async (e) => {
if (
`${e}`.includes(
"Cannot find a matching stream for unlabeled input pad 1 on filter"
)
) {
// Only one audio in the file
outFile = getNewFilename(file, "(processed) ");
nbTracks += 1;
// Do a copy
await execute(`"${ffmpegPath}" -y \
-i "${file}" \
-codec copy \
${extraArgs} \
"${outFile}"`).catch((e) => registerError(win, e));
// We throw the error since we do not want to merge any audio
return Promise.resolve("skip");
} else {
// Error handling
registerError(win, e);
}
})
.then(async (val) => {
if (val == "skip") {
return;
}
outFile = getNewFilename(file, "(merged audio) "); outFile = getNewFilename(file, "(merged audio) ");
nbTracks += 3;
// Add merged audio as first position to original video and make it default // Add merged audio as first position to original video and make it default
// About disposition: https://ffmpeg.org/ffmpeg.html#Main-options // About disposition: https://ffmpeg.org/ffmpeg.html#Main-options
// Also rename all tracks accordingly to what they are // Also rename all tracks accordingly to what they are
@ -115,23 +125,13 @@ app.whenReady().then(() => {
-map 0 -map 1:a -c:v copy \ -map 0 -map 1:a -c:v copy \
-disposition:a 0 -disposition:a:0 default \ -disposition:a 0 -disposition:a:0 default \
${metadataAudio} \ ${metadataAudio} \
${extraArgs} \
"${outFile}"` "${outFile}"`
).catch((e) => registerError(win, e)); ).catch((e) => registerError(win, e));
// Delete the temporary video file // Delete the temporary video file
deleteFile(tmpFile); deleteFile(tmpFile);
});
audioTracks = getNumberOfAudioTracks(outFile);
break;
default:
// Other cases: no merge needed
outFile = getNewFilename(file, "(nomerge) ");
// Do a copy
copyFileSync(file, outFile);
break;
}
const duration = getVideoDuration(outFile); const duration = getVideoDuration(outFile);
const stats = statSync(outFile); const stats = statSync(outFile);
@ -140,34 +140,28 @@ app.whenReady().then(() => {
title: outFile, title: outFile,
size: stats.size / 1024 / 1024, size: stats.size / 1024 / 1024,
duration, duration,
audioTracks, nbTracks,
}; };
}; };
/** Reduce size of a file /** Reduce size of a file */
* Returns an empty string in case of failing
*/
const reduceSize = async ( const reduceSize = async (
file: string, file: string,
bitrate: number, bitrate: number,
audioTracks: number[] nbTracks: number
) => { ) => {
const audioBitrate = Math.ceil( const audioBitrate = 400; // keep some room
audioTracks.reduce((sum, current) => current + sum, 50) // initial value > 0 for extra room let videoBitrate = bitrate - audioBitrate;
);
const videoBitrate = bitrate - audioBitrate;
let finalFile;
if (videoBitrate > 0) { const finalFile = getNewFilename(file, "Compressed - ");
finalFile = getNewFilename(file, "Compressed - ");
// Trash the output, depends on the platform // Trash the output, depends on the platform
const nul = process.platform === "win32" ? "NUL" : "/dev/null"; const nul = process.platform === "win32" ? "NUL" : "/dev/null";
// Mapping of tracks for FFMPEG, adding 1 for the video stream // Mapping of tracks for FFMPEG
const mappingTracks = Array(audioTracks.length + 1) const mappingTracks = Array(nbTracks)
.fill("-map 0:") .fill("-map 0:")
.map((str, index) => { .map(function (str, index) {
return str + index; return str + index;
}) })
.join(" "); .join(" ");
@ -176,33 +170,13 @@ app.whenReady().then(() => {
let hwAcc = ""; let hwAcc = "";
const argv = process.argv; const argv = process.argv;
if (argv.includes("/nvenc_h264")) { if (argv.includes("/nvenc")) {
// Use NVenc H.264 // Use NVenc
codec = "h264_nvenc"; codec = "h264_nvenc";
hwAcc = "-hwaccel cuda"; hwAcc = "-hwaccel cuda";
}
if (argv.includes("/amd_h264")) { // Increase video bitrate
// Use AMF H.264 videoBitrate = Math.floor(videoBitrate * 1.85);
codec = "h264_amf";
hwAcc = "-hwaccel d3d11va";
}
if (argv.includes("/nvenc_h265")) {
// Use NVenc H.265
codec = "hevc_nvenc";
hwAcc = "-hwaccel cuda";
}
if (argv.includes("/amd_h265")) {
// Use AMF H.265
codec = "hevc_amf";
hwAcc = "-hwaccel d3d11va";
}
if (argv.includes("/h265")) {
// Use H.265 encoder
codec = "libx265";
} }
// Compress the video // Compress the video
@ -217,40 +191,16 @@ app.whenReady().then(() => {
-i "${file}" \ -i "${file}" \
-c:v ${codec} -b:v ${videoBitrate}k -pass 2 -c:a copy \ -c:v ${codec} -b:v ${videoBitrate}k -pass 2 -c:a copy \
${mappingTracks} -f mp4 \ ${mappingTracks} -f mp4 \
-profile:v main \ ${metadataAudio} \
${audioTracks.length === metadataAudioSize ? metadataAudio : ""} \ ${extraArgs} \
${shareOpt} \
"${finalFile}"` "${finalFile}"`
).catch((e) => registerError(win, e)); ).catch((e) => registerError(win, e));
// Delete the old video file
deleteFile(file);
// Delete the 2 pass temporary files // Delete the 2 pass temporary files
deleteTwoPassFiles(process.cwd()); deleteTwoPassFiles(process.cwd());
} else {
finalFile = "";
}
// Delete the old video file
deleteFile(file);
return finalFile;
};
/** Move metadata at the begenning of the file */
const moveMetadata = async (file: string, nbTracks: number) => {
const finalFile = getNewFilename(file, "Broadcastable - ");
// Optimize for streaming
await execute(
`"${ffmpegPath}" -y \
-i "${file}" \
-map 0 -codec copy \
${shareOpt} \
${nbTracks === metadataAudioSize ? metadataAudio : ""} \
"${finalFile}"`
).catch((e) => registerError(win, e));
// Delete the old video file
deleteFile(file);
return finalFile; return finalFile;
}; };
@ -263,20 +213,9 @@ app.whenReady().then(() => {
ipcMain.handle("mergeAudio", (_, file: string) => mergeAudio(file)); ipcMain.handle("mergeAudio", (_, file: string) => mergeAudio(file));
ipcMain.handle( ipcMain.handle(
"reduceSize", "reduceSize",
(_, file: string, bitrate: number, audioTracks: number[]) => (_, file: string, bitrate: number, nbTracks: number) =>
reduceSize(file, bitrate, audioTracks) reduceSize(file, bitrate, nbTracks)
);
ipcMain.handle("moveMetadata", (_, file: string, nbTracks: number) =>
moveMetadata(file, nbTracks)
); );
ipcMain.handle("exit", () => (error ? {} : app.quit())); ipcMain.handle("exit", () => (error ? {} : app.quit()));
ipcMain.handle("confirmation", (_, text: string) => confirmation(text)); ipcMain.handle("confirmation", (_, text: string) => confirmation(text));
}); });
app.on("window-all-closed", () => {
processes.forEach((process) => {
process.stdin.write("q");
});
app.quit();
});

View file

@ -13,10 +13,8 @@ contextBridge.exposeInMainWorld("internals", {
ipcRenderer.invoke("getFilename", filepath), ipcRenderer.invoke("getFilename", filepath),
askFiles: () => ipcRenderer.invoke("askFiles"), askFiles: () => ipcRenderer.invoke("askFiles"),
mergeAudio: (file: string) => ipcRenderer.invoke("mergeAudio", file), mergeAudio: (file: string) => ipcRenderer.invoke("mergeAudio", file),
reduceSize: (file: string, bitrate: number, audioTracks: number[]) => reduceSize: (file: string, bitrate: number, nbTracks: number) =>
ipcRenderer.invoke("reduceSize", file, bitrate, audioTracks), ipcRenderer.invoke("reduceSize", file, bitrate, nbTracks),
moveMetadata: (file: string, nbTracks: number) =>
ipcRenderer.invoke("moveMetadata", file, nbTracks),
exit: () => ipcRenderer.invoke("exit"), exit: () => ipcRenderer.invoke("exit"),
confirmation: (text: string) => ipcRenderer.invoke("confirmation", text), confirmation: (text: string) => ipcRenderer.invoke("confirmation", text),
}); });

View file

@ -11,14 +11,13 @@ let internals: {
title: string; title: string;
duration: number; duration: number;
size: number; size: number;
audioTracks: number[]; nbTracks: number;
}>; }>;
reduceSize: ( reduceSize: (
file: string, file: string,
bitrate: number, bitrate: number,
audioTracks: number[] nbTracks: number
) => Promise<string>; ) => Promise<string>;
moveMetadata: (file: string, nbTracks: number) => Promise<string>;
confirmation: (text: string) => Promise<void>; confirmation: (text: string) => Promise<void>;
}; };
@ -56,16 +55,13 @@ const getFiles = async () => {
/** Returns maximum allowed size for files in MB */ /** Returns maximum allowed size for files in MB */
const fetchMaxSize = async () => { const fetchMaxSize = async () => {
const argv = await internals.argv(); const argv = await internals.argv();
if (argv.includes("/nitrobasic")) { if (argv.includes("/nitro")) {
// Nitro Basic user
return 50;
} else if (argv.includes("/nitro")) {
// Nitro user // Nitro user
return 500; return 500;
} else {
// Free user
return 10;
} }
// Free user
return 25;
}; };
/** Either replace the message, or add some info */ /** Either replace the message, or add some info */
@ -103,7 +99,6 @@ const main = async () => {
updateMessage("Récupération des fichiers..."); updateMessage("Récupération des fichiers...");
const files = await getFiles(); const files = await getFiles();
let processedFiles = ""; let processedFiles = "";
let numberOfUncompressableFiles = 0;
// Iterate over all the retrieved files // Iterate over all the retrieved files
for (const [idx, file] of files.entries()) { for (const [idx, file] of files.entries()) {
@ -136,39 +131,19 @@ const main = async () => {
finalTitle = await internals.reduceSize( finalTitle = await internals.reduceSize(
newFile.title, newFile.title,
bitrate, bitrate,
newFile.audioTracks newFile.nbTracks
);
} else {
updateMessage(`\nPréparation pour le partage...`, true, Mode.Append);
// Move the metadata to make it playable before everything is downloaded
finalTitle = await internals.moveMetadata(
newFile.title,
newFile.audioTracks.length
); );
} }
// Append title to the list of processed files // Append title to the list of processed files
if (finalTitle.length > 0) {
processedFiles += `\n- ${finalTitle}`; processedFiles += `\n- ${finalTitle}`;
updateMessage(`Fichier ${counter} traités.`); updateMessage(`Fichier ${counter} traités.`);
} else {
processedFiles += `\n- ${file} [incompressable]`;
updateMessage(`Fichier ${counter} trop large pour être compressé.`);
numberOfUncompressableFiles++;
}
}
let errorMessage = "";
if (numberOfUncompressableFiles > 0) {
errorMessage += `\nNombre de fichier incompressable : ${numberOfUncompressableFiles}.`;
} }
// Send confirmation to the user that we're done // Send confirmation to the user that we're done
await internals.confirmation( await internals.confirmation(
`${files.length} fichiers traités : ${processedFiles}` + errorMessage `${files.length} fichiers traités : ${processedFiles}`
); );
await internals.exit(); await internals.exit();
}; };

View file

@ -1,13 +1,9 @@
import ffprobe = require("ffprobe-static");
import child_process = require("child_process"); import child_process = require("child_process");
import path = require("path"); import path = require("path");
import { BrowserWindow } from "electron"; import { BrowserWindow } from "electron";
import { existsSync, unlink } from "fs"; import { existsSync, unlink } from "fs";
import ffprobe = require("ffprobe-static");
const ffprobePath = ffprobe.path.replace("app.asar", "app.asar.unpacked");
export const processes: child_process.ChildProcess[] = [];
/** Create a new filename from the OG one */ /** Create a new filename from the OG one */
export const getNewFilename = (ogFile: string, part: string) => { export const getNewFilename = (ogFile: string, part: string) => {
const oldFile = path.parse(ogFile); const oldFile = path.parse(ogFile);
@ -16,20 +12,11 @@ export const getNewFilename = (ogFile: string, part: string) => {
/** Return the duration of a video in second */ /** Return the duration of a video in second */
export const getVideoDuration = (file: string) => { export const getVideoDuration = (file: string) => {
const command = `"${ffprobePath}" -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${file}"`; const command = `"${ffprobe.path}" -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${file}"`;
const durationString = child_process.execSync(command).toString().trim(); const durationString = child_process.execSync(command).toString().trim();
return parseFloat(durationString); return parseFloat(durationString);
}; };
/** Return the number of audio tracks */
export const getNumberOfAudioTracks = (file: string): number[] => {
const command = `"${ffprobePath}" -v error -show_entries stream=bit_rate -select_streams a -of json "${file}"`;
const result = child_process.execSync(command, { encoding: "utf8" });
return JSON.parse(result).streams.map(
(v: { bit_rate: string }) => Number(v.bit_rate) / 1000
);
};
/** Print an error to the console and open the dev tool panel */ /** Print an error to the console and open the dev tool panel */
export const printAndDevTool = (win: BrowserWindow, err: string) => { export const printAndDevTool = (win: BrowserWindow, err: string) => {
win.webContents.openDevTools(); win.webContents.openDevTools();
@ -41,18 +28,13 @@ export const execute = (
command: string command: string
): Promise<{ stdout: string; stderr: string }> => { ): Promise<{ stdout: string; stderr: string }> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const process = child_process.exec(command, (error, stdout, stderr) => { child_process.exec(command, (error, stdout, stderr) => {
if (error) { if (error) {
reject(error); reject(error);
} else { } else {
resolve({ stdout, stderr }); resolve({ stdout, stderr });
} }
}); });
processes.push(process);
process.on("exit", () => {
processes.splice(processes.indexOf(process), 1);
});
}); });
}; };