diff --git a/src/pyffstream/cli.py b/src/pyffstream/cli.py index c35a3d4..7e01ea0 100644 --- a/src/pyffstream/cli.py +++ b/src/pyffstream/cli.py @@ -388,9 +388,9 @@ def setup_pyffserver_stream(fv: encode.EncodeSession) -> None: json_payload = { "vstandard": fv.ev.vstandard, "pixfmt": fv.ev.pix_fmt, - "astandard": fv.ev.astandard, - "sample_rate": fv.ev.samplerate, - "chlayout": fv.ev.chlayout, + "astandard": fv.ev.astandard if fv.ev.include_audio else "unknown", + "sample_rate": fv.ev.samplerate if fv.ev.include_audio else "unknown", + "chlayout": fv.ev.chlayout if fv.ev.include_audio else "unknown", "framerate": fv.ev.framerate, "keyframe_int": fv.ev.kf_int, "keyframe_sec": fv.ev.kf_sec, @@ -551,7 +551,10 @@ def stream_file(fopts: encode.FileOpts, args: argparse.Namespace) -> None: fv = encode.EncodeSession(fopts, encode.StaticEncodeVars.from_args(args)) futures: list[concurrent.futures.Future[Any]] = [] encode.determine_timeseek(fv) - if not fv.ev.copy_audio: + if fv.fv("a", "codec_type") != "audio": + fv.ev.include_audio = False + logger.warning("no audio found, disabling audio") + if not fv.ev.copy_audio and fv.ev.include_audio: futures.append(fv.executor.submit(encode.determine_afilters, fv)) if not fv.ev.copy_video: futures.append(fv.executor.submit(encode.determine_vfilters, fv)) @@ -1430,6 +1433,12 @@ def ffmpeg_vencoder(value: str) -> str: decimate_group.add_argument( "--sixtyfps", help="don't halve 60 fps obs input to 30 fps", action="store_true" ) + audio_parser.add_argument( + "--audio", + help="attempt to include audio in output (default: %(default)s)", + default=True, + action=argparse.BooleanOptionalAction, + ) audio_parser.add_argument( "-n", "--anormalize", diff --git a/src/pyffstream/encode.py b/src/pyffstream/encode.py index c42648b..aade17b 100644 --- a/src/pyffstream/encode.py +++ b/src/pyffstream/encode.py @@ -54,6 +54,7 @@ class EncodeSession: } ASTREAMS: Final = { "index", + "codec_type", "start_time", "codec_name", "sample_rate", @@ -142,7 +143,7 @@ def __init__(self, fopts: FileOpts, ev: StaticEncodeVars) -> None: if self.ev.crop: self.crop = StatusThread(self, "crop") self.statuses.append(self.crop) - if self.ev.anormalize: + if self.ev.anormalize and self.ev.include_audio: self.norm = StatusThread(self, "normalize") self.statuses.append(self.norm) if self.ev.subs: @@ -404,6 +405,7 @@ class EncType(enum.Enum): SOFTWARE = enum.auto() NVIDIA = enum.auto() QSV = enum.auto() + VAAPI = enum.auto() @dataclasses.dataclass @@ -517,6 +519,7 @@ class StaticEncodeVars: realtime: bool = False vstandard: str = "h264" astandard: str = "aac" + include_audio: bool = True protocol: str = "srt" # these strings are processed into int by argparse vbitrate: str = "6M" @@ -621,6 +624,7 @@ def from_args(cls, args: argparse.Namespace) -> StaticEncodeVars: evars.astandard = "aac" elif evars.aencoder in Params.OPUS_ENCODERS: evars.astandard = "opus" + evars.include_audio = args.audio evars.encode_preset = args.preset evars.encode_tune = args.tune evars.realtime = args.realtime @@ -669,6 +673,7 @@ def from_args(cls, args: argparse.Namespace) -> StaticEncodeVars: evars.passfile = args.passfile if args.npass in (1, 3): evars.outfile = pathlib.Path("-") + evars.include_audio = False evars.vindex = args.vindex evars.aindex = args.aindex evars.sindex = args.sindex @@ -1747,7 +1752,7 @@ def get_nvenc_h264_flags(fv: EncodeSession) -> list[str]: def get_aflags(fv: EncodeSession) -> list[str]: aflags: list[str] = [] - if fv.ev.npass in (1, 3): + if not fv.ev.include_audio: return ["-an"] if fv.ev.copy_audio: fv.ev.astandard = fv.v("a", "codec_name") @@ -1961,7 +1966,7 @@ def set_filter_flags(fv: EncodeSession) -> None: "-map", "[b]", ] # fmt: on - if fv.ev.npass not in (1, 3): + if fv.ev.include_audio: if fv.ev.copy_audio: filter_flags += ["-map", f"0:a:{fv.ev.aindex}?"] else: