MediaRouter / app /operations /extract_audio.py
basyx's picture
Upload 142 files
fba6023 verified
Raw
History Blame Contribute Delete
4.95 kB
from __future__ import annotations
import re
from collections.abc import Sequence
from pathlib import Path
from typing import Any
from app.core.exceptions import InputError
from app.models.media import InputMedia, OperationResult
from app.operations.common import audio_codec, execute, output_path, require_inputs
from app.services.ffmpeg_service import FFmpegService
from app.services.validator import bounded_number
async def extract_audio(
ffmpeg: FFmpegService, inputs: Sequence[InputMedia], params: dict[str, Any], output_dir: Path
) -> OperationResult:
require_inputs(inputs)
extension = str(params.get("format", "mp3")).lower().lstrip(".")
if extension not in {"mp3", "wav", "aac", "m4a", "flac", "ogg", "opus"}:
raise InputError("Unsupported extracted audio format")
output = output_path(output_dir, "audio", extension)
return await execute(
ffmpeg,
["-i", inputs[0].temp_path, "-vn", *audio_codec(extension), output],
output,
"audio.extract",
{"format": extension},
)
async def replace_audio(
ffmpeg: FFmpegService, inputs: Sequence[InputMedia], params: dict[str, Any], output_dir: Path
) -> OperationResult:
require_inputs(inputs, 2)
output = output_path(output_dir, "replaced-audio", "mp4")
args = [
"-i",
inputs[0].temp_path,
"-i",
inputs[1].temp_path,
"-map",
"0:v:0",
"-map",
"1:a:0",
"-c:v",
"copy",
"-c:a",
"aac",
"-shortest",
"-movflags",
"+faststart",
output,
]
return await execute(ffmpeg, args, output, "video.replace_audio")
async def remove_audio(
ffmpeg: FFmpegService, inputs: Sequence[InputMedia], params: dict[str, Any], output_dir: Path
) -> OperationResult:
require_inputs(inputs)
output = output_path(output_dir, "muted", "mp4")
return await execute(
ffmpeg,
["-i", inputs[0].temp_path, "-an", "-c:v", "copy", output],
output,
"video.remove_audio",
)
async def mute_video(
ffmpeg: FFmpegService, inputs: Sequence[InputMedia], params: dict[str, Any], output_dir: Path
) -> OperationResult:
result = await remove_audio(ffmpeg, inputs, params, output_dir)
result.metadata["operation"] = "video.mute"
return result
async def fade_audio(
ffmpeg: FFmpegService, inputs: Sequence[InputMedia], params: dict[str, Any], output_dir: Path
) -> OperationResult:
require_inputs(inputs)
start = bounded_number(params, "start", 0, 0, 86_400)
duration = bounded_number(params, "duration", 3, 0.01, 86_400)
fade_type = str(params.get("type", "in"))
if fade_type not in {"in", "out"}:
raise InputError("Fade requires type in/out, non-negative start, and positive duration")
output = output_path(output_dir, "faded", "mp3")
filter_name = "afade=t=in" if fade_type == "in" else "afade=t=out"
return await execute(
ffmpeg,
[
"-i",
inputs[0].temp_path,
"-af",
f"{filter_name}:st={start}:d={duration}",
*audio_codec("mp3"),
output,
],
output,
"audio.fade",
)
async def set_volume(
ffmpeg: FFmpegService, inputs: Sequence[InputMedia], params: dict[str, Any], output_dir: Path
) -> OperationResult:
require_inputs(inputs)
volume = bounded_number(params, "volume", 1, 0, 10)
output = output_path(output_dir, "volume", "mp3")
return await execute(
ffmpeg,
["-i", inputs[0].temp_path, "-af", f"volume={volume}", *audio_codec("mp3"), output],
output,
"audio.volume",
{"volume": volume},
)
async def remove_silence(
ffmpeg: FFmpegService, inputs: Sequence[InputMedia], params: dict[str, Any], output_dir: Path
) -> OperationResult:
require_inputs(inputs)
threshold = str(params.get("threshold", "-45dB"))
if not re.fullmatch(r"-?\d+(?:\.\d+)?dB", threshold):
raise InputError("threshold must be a decibel value such as -45dB")
output = output_path(output_dir, "no-silence", "mp3")
return await execute(
ffmpeg,
[
"-i",
inputs[0].temp_path,
"-af",
f"silenceremove=start_periods=1:start_duration=0.2:start_threshold={threshold}:stop_periods=-1:stop_duration=0.2:stop_threshold={threshold}",
*audio_codec("mp3"),
output,
],
output,
"audio.remove_silence",
)
async def noise_reduction(
ffmpeg: FFmpegService, inputs: Sequence[InputMedia], params: dict[str, Any], output_dir: Path
) -> OperationResult:
require_inputs(inputs)
output = output_path(output_dir, "denoised", "mp3")
return await execute(
ffmpeg,
["-i", inputs[0].temp_path, "-af", "afftdn", *audio_codec("mp3"), output],
output,
"audio.noise_reduction",
)