mirror of https://github.com/VERT-sh/VERT.git
feat: conversion sample rate
This commit is contained in:
parent
76ff9cc704
commit
0be741e5f6
|
@ -98,7 +98,8 @@
|
||||||
"quality_description": "This changes the default output quality of the converted files (in its category). Higher values may result in longer conversion times and file size.",
|
"quality_description": "This changes the default output quality of the converted files (in its category). Higher values may result in longer conversion times and file size.",
|
||||||
"quality_video": "This changes the default output quality of the converted video files. Higher values may result in longer conversion times and file size.",
|
"quality_video": "This changes the default output quality of the converted video files. Higher values may result in longer conversion times and file size.",
|
||||||
"quality_audio": "Audio (kbps)",
|
"quality_audio": "Audio (kbps)",
|
||||||
"quality_images": "Image (%)"
|
"quality_images": "Image (%)",
|
||||||
|
"rate": "Sample rate (Hz)"
|
||||||
},
|
},
|
||||||
"vertd": {
|
"vertd": {
|
||||||
"title": "Video conversion",
|
"title": "Video conversion",
|
||||||
|
|
|
@ -34,7 +34,8 @@
|
||||||
{disabled}
|
{disabled}
|
||||||
class="w-full p-3 rounded-lg bg-panel border-2 border-button
|
class="w-full p-3 rounded-lg bg-panel border-2 border-button
|
||||||
{prefix ? 'pl-[2rem]' : 'pl-3'}
|
{prefix ? 'pl-[2rem]' : 'pl-3'}
|
||||||
{extension ? 'pr-[4rem]' : 'pr-3'}"
|
{extension ? 'pr-[4rem]' : 'pr-3'}
|
||||||
|
{disabled && 'opacity-50 cursor-not-allowed'}"
|
||||||
/>
|
/>
|
||||||
{#if prefix}
|
{#if prefix}
|
||||||
<div class="absolute left-0 top-0 bottom-0 flex items-center px-2">
|
<div class="absolute left-0 top-0 bottom-0 flex items-center px-2">
|
||||||
|
|
|
@ -174,7 +174,7 @@ export class FFmpegConverter extends Converter {
|
||||||
const bitrateListener = (event: { message: string }) => {
|
const bitrateListener = (event: { message: string }) => {
|
||||||
if (bitrate !== null) return;
|
if (bitrate !== null) return;
|
||||||
const n = parseInt(event.message.trim(), 10);
|
const n = parseInt(event.message.trim(), 10);
|
||||||
if (!n) return null;
|
if (!n) return;
|
||||||
bitrate = Math.round(n / 1000);
|
bitrate = Math.round(n / 1000);
|
||||||
log(
|
log(
|
||||||
["converters", this.name],
|
["converters", this.name],
|
||||||
|
@ -195,6 +195,48 @@ export class FFmpegConverter extends Converter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async detectAudioSampleRate(
|
||||||
|
ffmpeg: FFmpeg,
|
||||||
|
): Promise<number | null> {
|
||||||
|
const args = [
|
||||||
|
"-v",
|
||||||
|
"quiet",
|
||||||
|
"-select_streams",
|
||||||
|
"a:0",
|
||||||
|
"-show_entries",
|
||||||
|
"stream=sample_rate",
|
||||||
|
"-of",
|
||||||
|
"default=noprint_wrappers=1:nokey=1",
|
||||||
|
"input",
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
let sampleRate: number | null = null;
|
||||||
|
|
||||||
|
const sampleRateListener = (event: { message: string }) => {
|
||||||
|
if (sampleRate !== null) return;
|
||||||
|
const n = parseInt(event.message.trim(), 10);
|
||||||
|
if (!n) return;
|
||||||
|
sampleRate = n;
|
||||||
|
log(
|
||||||
|
["converters", this.name],
|
||||||
|
`Detected stream audio sample rate: ${sampleRate} Hz`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ffmpeg.on("log", sampleRateListener);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ffmpeg.ffprobe.call(ffmpeg, args);
|
||||||
|
return sampleRate;
|
||||||
|
} finally {
|
||||||
|
ffmpeg.off("log", sampleRateListener);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async buildConversionCommand(
|
private async buildConversionCommand(
|
||||||
ffmpeg: FFmpeg,
|
ffmpeg: FFmpeg,
|
||||||
input: VertFile,
|
input: VertFile,
|
||||||
|
@ -203,23 +245,74 @@ export class FFmpegConverter extends Converter {
|
||||||
const inputFormat = input.from.slice(1);
|
const inputFormat = input.from.slice(1);
|
||||||
const outputFormat = to.slice(1);
|
const outputFormat = to.slice(1);
|
||||||
|
|
||||||
const lossless = ["flac", "alac", "wav"];
|
const lossless = ["flac", "alac", "wav", "dsd", "dsf", "dff"];
|
||||||
const userSetting = Settings.instance.settings.ffmpegQuality;
|
const userSetting = Settings.instance.settings.ffmpegQuality;
|
||||||
log(["converters", this.name], `using user setting for audio bitrate: ${userSetting}`);
|
const userSampleRate = Settings.instance.settings.ffmpegSampleRate;
|
||||||
|
const customSampleRate =
|
||||||
|
Settings.instance.settings.ffmpegCustomSampleRate;
|
||||||
let audioBitrateArgs: string[] = [];
|
let audioBitrateArgs: string[] = [];
|
||||||
|
let sampleRateArgs: string[] = [];
|
||||||
|
|
||||||
|
const isLosslessToLossy =
|
||||||
|
lossless.includes(inputFormat) && !lossless.includes(outputFormat);
|
||||||
if (userSetting !== "auto") {
|
if (userSetting !== "auto") {
|
||||||
// user's setting
|
// user's setting
|
||||||
audioBitrateArgs = ["-b:a", `${userSetting}k`];
|
audioBitrateArgs = ["-b:a", `${userSetting}k`];
|
||||||
|
log(
|
||||||
|
["converters", this.name],
|
||||||
|
`using user setting for audio bitrate: ${userSetting}`,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// detect bitrate of original file and use
|
// detect bitrate of original file and use
|
||||||
if (lossless.includes(inputFormat) && !lossless.includes(outputFormat)) {
|
if (isLosslessToLossy) {
|
||||||
audioBitrateArgs = ["-b:a", "320k"];
|
// use safe default
|
||||||
log(["converters", this.name], `using default audio bitrate: 320k`);
|
audioBitrateArgs = ["-b:a", "128k"];
|
||||||
|
log(
|
||||||
|
["converters", this.name],
|
||||||
|
`converting from lossless to lossy, using default audio bitrate: 128k`,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const inputBitrate = await this.detectAudioBitrate(ffmpeg);
|
const inputBitrate = await this.detectAudioBitrate(ffmpeg);
|
||||||
audioBitrateArgs = inputBitrate ? ["-b:a", `${inputBitrate}k`] : [];
|
audioBitrateArgs = inputBitrate
|
||||||
log(["converters", this.name], `using detected audio bitrate: ${inputBitrate}k`);
|
? ["-b:a", `${inputBitrate}k`]
|
||||||
|
: [];
|
||||||
|
log(
|
||||||
|
["converters", this.name],
|
||||||
|
`using detected audio bitrate: ${inputBitrate}k`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sample rate setting
|
||||||
|
if (userSampleRate !== "auto") {
|
||||||
|
if (userSampleRate === "custom") {
|
||||||
|
sampleRateArgs = ["-ar", customSampleRate.toString()];
|
||||||
|
} else {
|
||||||
|
sampleRateArgs = ["-ar", userSampleRate];
|
||||||
|
}
|
||||||
|
log(
|
||||||
|
["converters", this.name],
|
||||||
|
`using user setting for sample rate: ${userSampleRate}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// detect sample rate of original file and use
|
||||||
|
if (isLosslessToLossy) {
|
||||||
|
// use safe default
|
||||||
|
sampleRateArgs = ["-ar", "44100"];
|
||||||
|
log(
|
||||||
|
["converters", this.name],
|
||||||
|
`converting from lossless to lossy, using default sample rate: 44100Hz`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const inputSampleRate =
|
||||||
|
await this.detectAudioSampleRate(ffmpeg);
|
||||||
|
sampleRateArgs = inputSampleRate
|
||||||
|
? ["-ar", inputSampleRate.toString()]
|
||||||
|
: [];
|
||||||
|
log(
|
||||||
|
["converters", this.name],
|
||||||
|
`using detected audio sample rate: ${inputSampleRate}Hz`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,6 +328,7 @@ export class FFmpegConverter extends Converter {
|
||||||
"-map",
|
"-map",
|
||||||
"0:a:0",
|
"0:a:0",
|
||||||
...audioBitrateArgs,
|
...audioBitrateArgs,
|
||||||
|
...sampleRateArgs,
|
||||||
"output" + to,
|
"output" + to,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -270,6 +364,7 @@ export class FFmpegConverter extends Converter {
|
||||||
"1",
|
"1",
|
||||||
...codecArgs,
|
...codecArgs,
|
||||||
...audioBitrateArgs,
|
...audioBitrateArgs,
|
||||||
|
...sampleRateArgs,
|
||||||
"output" + to,
|
"output" + to,
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
|
@ -288,6 +383,7 @@ export class FFmpegConverter extends Converter {
|
||||||
"1",
|
"1",
|
||||||
...codecArgs,
|
...codecArgs,
|
||||||
...audioBitrateArgs,
|
...audioBitrateArgs,
|
||||||
|
...sampleRateArgs,
|
||||||
"output" + to,
|
"output" + to,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -305,6 +401,7 @@ export class FFmpegConverter extends Converter {
|
||||||
"-c:a",
|
"-c:a",
|
||||||
audioCodec,
|
audioCodec,
|
||||||
...audioBitrateArgs,
|
...audioBitrateArgs,
|
||||||
|
...sampleRateArgs,
|
||||||
"output" + to,
|
"output" + to,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -486,3 +583,16 @@ export const CONVERSION_BITRATES = [
|
||||||
32,
|
32,
|
||||||
] as const;
|
] as const;
|
||||||
export type ConversionBitrate = (typeof CONVERSION_BITRATES)[number];
|
export type ConversionBitrate = (typeof CONVERSION_BITRATES)[number];
|
||||||
|
|
||||||
|
export const SAMPLE_RATES = [
|
||||||
|
"auto",
|
||||||
|
"custom",
|
||||||
|
"48000",
|
||||||
|
"44100",
|
||||||
|
"32000",
|
||||||
|
"22050",
|
||||||
|
"16000",
|
||||||
|
"11025",
|
||||||
|
"8000",
|
||||||
|
] as const;
|
||||||
|
export type SampleRate = (typeof SAMPLE_RATES)[number];
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
import {
|
import {
|
||||||
CONVERSION_BITRATES,
|
CONVERSION_BITRATES,
|
||||||
type ConversionBitrate,
|
type ConversionBitrate,
|
||||||
|
SAMPLE_RATES,
|
||||||
|
type SampleRate,
|
||||||
} from "$lib/converters/ffmpeg.svelte";
|
} from "$lib/converters/ffmpeg.svelte";
|
||||||
import { m } from "$lib/paraglide/messages";
|
import { m } from "$lib/paraglide/messages";
|
||||||
import Dropdown from "$lib/components/functional/Dropdown.svelte";
|
import Dropdown from "$lib/components/functional/Dropdown.svelte";
|
||||||
|
@ -81,6 +83,34 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-3">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-sm font-bold">
|
||||||
|
{m["settings.conversion.rate"]()}
|
||||||
|
</p>
|
||||||
|
<Dropdown
|
||||||
|
options={SAMPLE_RATES.map((r) => r.toString())}
|
||||||
|
selected={settings.ffmpegSampleRate.toString()}
|
||||||
|
onselect={(option: string) => {
|
||||||
|
settings.ffmpegSampleRate =
|
||||||
|
option as SampleRate;
|
||||||
|
}}
|
||||||
|
settingsStyle
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-sm font-bold select-none"> </p>
|
||||||
|
<FancyInput
|
||||||
|
bind:value={
|
||||||
|
settings.ffmpegCustomSampleRate as unknown as string
|
||||||
|
}
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
extension={"Hz"}
|
||||||
|
disabled={settings.ffmpegSampleRate !== "custom"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div></Panel
|
</div></Panel
|
||||||
|
|
|
@ -14,6 +14,8 @@ export interface ISettings {
|
||||||
vertdSpeed: ConversionSpeed; // videos
|
vertdSpeed: ConversionSpeed; // videos
|
||||||
magickQuality: number; // images
|
magickQuality: number; // images
|
||||||
ffmpegQuality: ConversionBitrate; // audio (or audio <-> video)
|
ffmpegQuality: ConversionBitrate; // audio (or audio <-> video)
|
||||||
|
ffmpegSampleRate: string; // audio (or audio <-> video)
|
||||||
|
ffmpegCustomSampleRate: number; // audio (or audio <-> video) - only used when ffmpegSampleRate is "custom"
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Settings {
|
export class Settings {
|
||||||
|
@ -26,6 +28,8 @@ export class Settings {
|
||||||
vertdSpeed: "slow",
|
vertdSpeed: "slow",
|
||||||
magickQuality: 100,
|
magickQuality: 100,
|
||||||
ffmpegQuality: "auto",
|
ffmpegQuality: "auto",
|
||||||
|
ffmpegSampleRate: "auto",
|
||||||
|
ffmpegCustomSampleRate: 44100,
|
||||||
});
|
});
|
||||||
|
|
||||||
public save() {
|
public save() {
|
||||||
|
|
Loading…
Reference in New Issue