diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de5cd10..a3b791f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,5 @@ name: ci -env: - HOMEBREW_NO_INSTALL_CLEANUP: 1 - on: push: paths: @@ -45,6 +42,8 @@ jobs: - name: install prereqs (macOS) if : runner.os == 'macOS' run: brew install ffmpeg + env: + HOMEBREW_NO_INSTALL_CLEANUP: 1 - name: Install winget if: runner.os == 'Windows' diff --git a/README.md b/README.md index 62852b8..9d348d5 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,9 @@ JSON: * `camera_size`: camera resolution -- find from `v4l2-ctl --list-formats-ext` or camera spec sheet. * `camera_fps`: camera fps -- found from command above or camera spec sheet -Stream to multiple sites, in this example Facebook Live and YouTube Live simultaneously: +Stream to multiple sites. This uses FFmpeg +[-f tee](https://trac.ffmpeg.org/wiki/Creating%20multiple%20outputs#Teepseudo-muxer). +For example, Facebook Live and YouTube Live simultaneously: ```sh python -m pylivestream.camera youtube facebook ./pylivestream.json diff --git a/src/pylivestream/base.py b/src/pylivestream/base.py index f9aa039..ca4834a 100644 --- a/src/pylivestream/base.py +++ b/src/pylivestream/base.py @@ -103,7 +103,12 @@ def startlive(self, sinks: list[str] | None = None): run(self.cmd) elif len(sinks) == 1: run(self.cmd) - else: # multi-stream output tee + else: + """ + multi-stream output tee + https://trac.ffmpeg.org/wiki/Creating%20multiple%20outputs#Teepseudo-muxer + https://trac.ffmpeg.org/wiki/EncodingForStreamingSites#Outputtingtomultiplestreamingserviceslocalfile + """ cmdstem: list[str] = self.cmd[:-3] # +global_header is necessary to tee to multiple services cmd: list[str] = cmdstem + ["-flags:v", "+global_header", "-f", "tee"] @@ -115,7 +120,8 @@ def startlive(self, sinks: list[str] | None = None): if self.vidsource == "file": # picks first video and audio stream, often correct cmd += ["-map", "0:v", "-map", "0:a:0"] - else: # device (Camera) + else: + # device (Camera) # connect video device to video stream, # audio device to audio stream cmd += ["-map", "0:v", "-map", "1:a"] diff --git a/src/pylivestream/ffmpeg.py b/src/pylivestream/ffmpeg.py index ad2c947..3c85270 100644 --- a/src/pylivestream/ffmpeg.py +++ b/src/pylivestream/ffmpeg.py @@ -21,6 +21,7 @@ def __init__(self): # default 8, increasing can help avoid warnings self.QUEUE = ["-thread_queue_size", "8"] + # https://trac.ffmpeg.org/wiki/StreamingGuide#The-reflag self.THROTTLE = "-re" def timelimit(self, t: str | int | float | None) -> list[str]: diff --git a/src/pylivestream/stream.py b/src/pylivestream/stream.py index e242a7e..2023ab0 100644 --- a/src/pylivestream/stream.py +++ b/src/pylivestream/stream.py @@ -212,17 +212,16 @@ def audioOut(self) -> list[str]: https://www.facebook.com/facebookmedia/get-started/live """ - if not (self.audio_bps and self.acap and self.audio_chan and self.audio_rate): - return [] + o = [] + + if self.audio_codec: + o += ["-codec:a", self.audio_codec] + if self.audio_bps: + o += ["-b:a", str(self.audio_bps)] + if self.audio_rate: + o += ["-ar", str(self.audio_rate)] - return [ - "-codec:a", - self.audio_codec, - "-b:a", - str(self.audio_bps), - "-ar", - str(self.audio_rate), - ] + return o def video_bitrate(self) -> None: """