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_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_images": "Image (%)"
|
||||
"quality_images": "Image (%)",
|
||||
"rate": "Sample rate (Hz)"
|
||||
},
|
||||
"vertd": {
|
||||
"title": "Video conversion",
|
||||
|
|
|
@ -34,7 +34,8 @@
|
|||
{disabled}
|
||||
class="w-full p-3 rounded-lg bg-panel border-2 border-button
|
||||
{prefix ? 'pl-[2rem]' : 'pl-3'}
|
||||
{extension ? 'pr-[4rem]' : 'pr-3'}"
|
||||
{extension ? 'pr-[4rem]' : 'pr-3'}
|
||||
{disabled && 'opacity-50 cursor-not-allowed'}"
|
||||
/>
|
||||
{#if prefix}
|
||||
<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 }) => {
|
||||
if (bitrate !== null) return;
|
||||
const n = parseInt(event.message.trim(), 10);
|
||||
if (!n) return null;
|
||||
if (!n) return;
|
||||
bitrate = Math.round(n / 1000);
|
||||
log(
|
||||
["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(
|
||||
ffmpeg: FFmpeg,
|
||||
input: VertFile,
|
||||
|
@ -203,23 +245,74 @@ export class FFmpegConverter extends Converter {
|
|||
const inputFormat = input.from.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;
|
||||
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 sampleRateArgs: string[] = [];
|
||||
|
||||
const isLosslessToLossy =
|
||||
lossless.includes(inputFormat) && !lossless.includes(outputFormat);
|
||||
if (userSetting !== "auto") {
|
||||
// user's setting
|
||||
audioBitrateArgs = ["-b:a", `${userSetting}k`];
|
||||
log(
|
||||
["converters", this.name],
|
||||
`using user setting for audio bitrate: ${userSetting}`,
|
||||
);
|
||||
} else {
|
||||
// detect bitrate of original file and use
|
||||
if (lossless.includes(inputFormat) && !lossless.includes(outputFormat)) {
|
||||
audioBitrateArgs = ["-b:a", "320k"];
|
||||
log(["converters", this.name], `using default audio bitrate: 320k`);
|
||||
if (isLosslessToLossy) {
|
||||
// use safe default
|
||||
audioBitrateArgs = ["-b:a", "128k"];
|
||||
log(
|
||||
["converters", this.name],
|
||||
`converting from lossless to lossy, using default audio bitrate: 128k`,
|
||||
);
|
||||
} else {
|
||||
const inputBitrate = await this.detectAudioBitrate(ffmpeg);
|
||||
audioBitrateArgs = inputBitrate ? ["-b:a", `${inputBitrate}k`] : [];
|
||||
log(["converters", this.name], `using detected audio bitrate: ${inputBitrate}k`);
|
||||
audioBitrateArgs = inputBitrate
|
||||
? ["-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",
|
||||
"0:a:0",
|
||||
...audioBitrateArgs,
|
||||
...sampleRateArgs,
|
||||
"output" + to,
|
||||
];
|
||||
}
|
||||
|
@ -270,6 +364,7 @@ export class FFmpegConverter extends Converter {
|
|||
"1",
|
||||
...codecArgs,
|
||||
...audioBitrateArgs,
|
||||
...sampleRateArgs,
|
||||
"output" + to,
|
||||
];
|
||||
} else {
|
||||
|
@ -288,6 +383,7 @@ export class FFmpegConverter extends Converter {
|
|||
"1",
|
||||
...codecArgs,
|
||||
...audioBitrateArgs,
|
||||
...sampleRateArgs,
|
||||
"output" + to,
|
||||
];
|
||||
}
|
||||
|
@ -305,6 +401,7 @@ export class FFmpegConverter extends Converter {
|
|||
"-c:a",
|
||||
audioCodec,
|
||||
...audioBitrateArgs,
|
||||
...sampleRateArgs,
|
||||
"output" + to,
|
||||
];
|
||||
}
|
||||
|
@ -476,13 +573,26 @@ const getCodecs = (ext: string): { video: string; audio: string } => {
|
|||
};
|
||||
|
||||
export const CONVERSION_BITRATES = [
|
||||
"auto",
|
||||
320,
|
||||
256,
|
||||
192,
|
||||
128,
|
||||
96,
|
||||
64,
|
||||
32,
|
||||
"auto",
|
||||
320,
|
||||
256,
|
||||
192,
|
||||
128,
|
||||
96,
|
||||
64,
|
||||
32,
|
||||
] 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 {
|
||||
CONVERSION_BITRATES,
|
||||
type ConversionBitrate,
|
||||
SAMPLE_RATES,
|
||||
type SampleRate,
|
||||
} from "$lib/converters/ffmpeg.svelte";
|
||||
import { m } from "$lib/paraglide/messages";
|
||||
import Dropdown from "$lib/components/functional/Dropdown.svelte";
|
||||
|
@ -81,6 +83,34 @@
|
|||
/>
|
||||
</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></Panel
|
||||
|
|
|
@ -14,6 +14,8 @@ export interface ISettings {
|
|||
vertdSpeed: ConversionSpeed; // videos
|
||||
magickQuality: number; // images
|
||||
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 {
|
||||
|
@ -26,6 +28,8 @@ export class Settings {
|
|||
vertdSpeed: "slow",
|
||||
magickQuality: 100,
|
||||
ffmpegQuality: "auto",
|
||||
ffmpegSampleRate: "auto",
|
||||
ffmpegCustomSampleRate: 44100,
|
||||
});
|
||||
|
||||
public save() {
|
||||
|
|
Loading…
Reference in New Issue