102 lines
2.9 KiB
Python
102 lines
2.9 KiB
Python
"""Prepare the licensed daytime riverside ambience used by exploration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
|
|
|
|
SOURCE_URL = (
|
|
"https://pixabay.com/sound-effects/"
|
|
"nature-stream-water-with-birds-459511/"
|
|
)
|
|
TARGET_DURATION_SECONDS = 32
|
|
SOURCE_OFFSET_SECONDS = 7
|
|
LOOP_CROSSFADE_SECONDS = 2
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--input", required=True, type=Path)
|
|
parser.add_argument("--output", required=True, type=Path)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> None:
|
|
args = parse_args()
|
|
source_path = args.input.resolve()
|
|
output_path = args.output.resolve()
|
|
if not source_path.is_file():
|
|
raise FileNotFoundError(source_path)
|
|
|
|
ffmpeg = os.environ.get("FFMPEG_PATH") or shutil.which("ffmpeg")
|
|
if not ffmpeg:
|
|
raise RuntimeError("ffmpeg is required to build the ambience track.")
|
|
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
loop_end = SOURCE_OFFSET_SECONDS + TARGET_DURATION_SECONDS
|
|
head_end = SOURCE_OFFSET_SECONDS + LOOP_CROSSFADE_SECONDS
|
|
body_start = head_end
|
|
tail_start = loop_end
|
|
tail_end = loop_end + LOOP_CROSSFADE_SECONDS
|
|
filter_graph = (
|
|
"[0:a]asplit=3[tailSource][headSource][bodySource];"
|
|
f"[tailSource]atrim=start={tail_start}:end={tail_end},"
|
|
f"asetpts=PTS-STARTPTS,afade=t=out:st=0:"
|
|
f"d={LOOP_CROSSFADE_SECONDS}[tail];"
|
|
f"[headSource]atrim=start={SOURCE_OFFSET_SECONDS}:end={head_end},"
|
|
f"asetpts=PTS-STARTPTS,afade=t=in:st=0:"
|
|
f"d={LOOP_CROSSFADE_SECONDS}[head];"
|
|
"[tail][head]amix=inputs=2:normalize=0:duration=first[seam];"
|
|
f"[bodySource]atrim=start={body_start}:end={loop_end},"
|
|
"asetpts=PTS-STARTPTS[body];"
|
|
"[seam][body]concat=n=2:v=0:a=1,"
|
|
"loudnorm=I=-24:TP=-3:LRA=7[out]"
|
|
)
|
|
command = [
|
|
ffmpeg,
|
|
"-hide_banner",
|
|
"-loglevel",
|
|
"error",
|
|
"-y",
|
|
"-i",
|
|
str(source_path),
|
|
"-filter_complex",
|
|
filter_graph,
|
|
"-map",
|
|
"[out]",
|
|
"-ar",
|
|
"44100",
|
|
"-ac",
|
|
"2",
|
|
"-codec:a",
|
|
"libmp3lame",
|
|
"-b:a",
|
|
"128k",
|
|
"-metadata",
|
|
"title=Riverside Village Day Ambience",
|
|
"-metadata",
|
|
"artist=loswin23",
|
|
"-metadata",
|
|
(
|
|
"comment=Derived from Stream Water with Birds; "
|
|
f"Pixabay Content License; {SOURCE_URL}"
|
|
),
|
|
str(output_path),
|
|
]
|
|
subprocess.run(command, check=True)
|
|
|
|
if output_path.stat().st_size <= 0:
|
|
raise RuntimeError(f"Empty ambience output: {output_path}")
|
|
print(
|
|
f"Built {output_path} ({TARGET_DURATION_SECONDS}s target, "
|
|
f"{output_path.stat().st_size} bytes)."
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|