Skip to content

Commit

Permalink
single JSON file for config, not included in package
Browse files Browse the repository at this point in the history
use newer importlib.resources api
  • Loading branch information
scivision committed Dec 1, 2022
1 parent 12cd924 commit 7ef2a97
Show file tree
Hide file tree
Showing 18 changed files with 266 additions and 227 deletions.
1 change: 0 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
include src/pylivestream/pylivestream.ini
graft src/pylivestream/data
63 changes: 28 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ FFmpeg is used from Python `subprocess` to stream to sites including:
* [Twitch](#twitch)
* also Ustream, Vimeo, Restream.io and more for streaming broadcasts.

![PyLivestream diagram showing screen capture or webcam simultaneously livestreaming to multiple services.](./doc/logo.png)
![PyLivestream diagram showing screen capture or camera simultaneously livestreaming to multiple services.](./doc/logo.png)

[Troubleshooting](./Troubleshooting.md)

## PyLivestream benefits

* Python scripts compute good streaming parameters, and emit the command used to copy and paste if desired.
* Works on any OS (Mac, Linux, Windows) and computing platform, including PC, Mac, and Raspberry Pi.
* Uses a [pylivestream.ini](./src/pylivestream/pylivestream.ini) file to adjust all parameters.
* Uses single JSON file pylivestream.json to adjust parameters.

### PyLivestream limitations

Expand Down Expand Up @@ -68,45 +68,40 @@ cd PyLivestream
python3 -m pip install -e .
```

## Configuration: pylivestream.ini
## Configuration: pylivestream.json

You can skip past this section to "stream start" if it's confusing.
The defaults might work to get you started.

The [pylivestream.ini](./src/pylivestream/pylivestream.ini) file contains parameters relevant to your stream.
The pylivestream.json file has parameters relevant to the live stream.
This file is copied into your `sys.prefix`/share/pylivestream directory upon `pip install pylivestream`.

We suggest copying this file to another location on your hard drive and editing, then specifying it for your streams.

The `[DEFAULT]` section has parameters that can be overridden for each site, if desired.

* `screencap_origin`: origin (upper left corner) of screen capture region in pixels.
* `screencap_res`: resolution of screen capture (area to capture, starting from origin)
* `screencap_size`: resolution of screen capture (area to capture, starting from origin)
* `screencap_fps`: frames/sec of screen capture
* `video_kbps`: override automatic video bitrate in kbps

* `audiofs`: audio sampling frequency. Typically 44100 Hz (CD quality).
* `audio_rate`: audio sampling frequency. Typically 44100 Hz (CD quality).
* `audio_bps`: audio data rate--**leave blank if you want no audio** (usually used for "file", to make an animated GIF in post-processing)
* `preset`: `veryfast` or `ultrafast` if CPU not able to keep up.

Next are `sys.platform` specific parameters.

Seek help in FFmpeg documentation, try capturing to a file first and then update
[pylivestream.ini](./src/pylivestream/pylivestream.ini) for your `sys.platform`.
Seek help in FFmpeg documentation, try capturing to a file first and then update ~/pylivestream.json for `sys.platform`.

* exe: override path to desired FFmpeg executable. In case you have multiple FFmpeg versions installed (say, from Anaconda Python).

Finally are the per-site parameters.
The only thing you would possibly need to change here is the `server` for best performance for your geographic region.
The included [pylivestream.ini](./src/pylivestream/pylivestream.ini) is with default servers for the Northeastern USA.

### Deduce inputs

Each computer will need distinct pylivestream.ini device input parameters:
Each computer will need distinct pylivestream.json device input parameters:

* audiochan: audio device
* webcamchan: webcam device
* screenchan: desktop capture software port name
* audio_chan: audio device
* camera_chan: camera device
* screen_chan: desktop capture software port name

Loopback devices that let you "record what you hear" are operating system dependent.
You may need to search documentation for your operating system to enable such a virtual loopback device.
Expand Down Expand Up @@ -144,7 +139,7 @@ Both do the same thing.
* `import pylivestream.api as pls` from within your Python script. For more information type `help(pls)` or `help(pls.stream_microphone)`
* pls.stream_file()
* pls.stream_microphone()
* pls.stream_webcam()
* pls.stream_camera()

## Authentication

Expand Down Expand Up @@ -181,46 +176,44 @@ TODO

### Twitch

1. create stream from [Twitch Dashboard](https://dashboard.twitch.tv/settings/channel#stream-preferences). Edit [pylivestream.ini](./src/pylivestream/pylivestream.ini) to have the [closest ingest server](https://stream.twitch.tv/ingests/).
2. put Twitch stream key into file `~/twitch.json`
3. Run Python script for Twitch with chosen input
Create stream from [Twitch Dashboard](https://dashboard.twitch.tv/settings/channel#stream-preferences).
Create pylivestream.json file with "url" and "streamid" for Twitch.
Run Python script for Twitch with chosen input:

```sh
python -m pylivestream.screen twitch
python -m pylivestream.screen twitch ./pylivestream.json
```

## Usage

Due to the complexity of streaming and the non-specific error codes FFmpeg emits, the default behavior is that if FFmpeg detects one stream has failed, ALL streams will stop streaming and the program ends.

* [pylivestream.ini](./src/pylivestream/pylivestream.ini) is setup for your computer and desired parameters
* `site` is `facebook`, `twitch`, `youtube`, etc.
* For `pylivestream.camera` and `pylivestream.screen`, more than one `site` can be specified for simultaneous multi-streaming
* Setup a JSON file "pylivestream.json" with values you determine, not these dummy values:
Setup ~/pylivestream.json for computer and desired parameters.
Setup a JSON file "pylivestream.json" with values you determine, not these dummy values:

```json
{
"facebook": {
"url": "rtmps://their.server",
"streamid": "your-facebook-key",
"url": "rtmps://live-api-s.facebook.com:443/rtmp",
"streamid": "your-stream-id",
},
"youtube": {
"url": "rtmp://your.value",
"streamid": "your-key"
"url": "rtmp://a.rtmp.youtube.com/live2",
"streamid": "your-stream-id"
}
}
```

[File-Streaming](./File-Streaming.md)

### Webcam
### Camera

Note: your system may not have a camera, particularly if it's a virtual machine.

Config:
JSON:

* `webcam_res`: camera resolution -- find from `v4l2-ctl --list-formats-ext` or camera spec sheet.
* `webcam_fps`: camera fps -- found from command above or camera spec sheet
* `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:

Expand Down Expand Up @@ -279,8 +272,8 @@ python -m pylivestream.screen2disk myvid.avi
### FFmpeg References

* [streaming](https://trac.ffmpeg.org/wiki/EncodingForStreamingSites)
* [webcam](https://trac.ffmpeg.org/wiki/Capture/Webcam)
* webcam [overlay](https://trac.ffmpeg.org/wiki/EncodingForStreamingSites#Withwebcamoverlay)
* [camera](https://trac.ffmpeg.org/wiki/Capture/Webcam)
* Camera [overlay](https://trac.ffmpeg.org/wiki/EncodingForStreamingSites#Withwebcamoverlay)

### Windows

Expand Down
4 changes: 2 additions & 2 deletions archive/visual_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ def main():
# %% loop video
print("Looping video")
subprocess.check_call([sys.executable, "-m", "pylivestream.loopfile", "-y", str(VIDEO), HOST])
# %% Webcam
print("Webcam test - will fail if no webcam present on your system")
# %% Camera
print("Camera test - will fail if no camera present on your system")
subprocess.check_call([sys.executable, "-m", "pylivestream.camera", "-y", HOST])


Expand Down
2 changes: 1 addition & 1 deletion src/pylivestream/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .utils import meta_caption
from .base import FileIn, Microphone, SaveDisk, Screenshare, Webcam, Livestream
from .base import FileIn, Microphone, SaveDisk, Screenshare, Camera, Livestream
8 changes: 4 additions & 4 deletions src/pylivestream/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
from __future__ import annotations
from pathlib import Path

from .base import FileIn, Microphone, SaveDisk, Webcam
from .base import FileIn, Microphone, SaveDisk, Camera
from .glob import stream_files
from .screen import stream_screen

__all__ = [
"stream_file",
"stream_files",
"stream_microphone",
"stream_webcam",
"stream_camera",
"stream_screen",
"capture_screen",
]
Expand Down Expand Up @@ -83,8 +83,8 @@ def capture_screen(
s.save()


def stream_webcam(ini_file: Path, websites: list[str], *, assume_yes: bool, timeout: float):
S = Webcam(ini_file, websites, yes=assume_yes, timeout=timeout)
def stream_camera(ini_file: Path, websites: list[str], *, assume_yes: bool, timeout: float):
S = Camera(ini_file, websites, yes=assume_yes, timeout=timeout)
sites: list[str] = list(S.streams.keys())
# %% Go live
if assume_yes:
Expand Down
16 changes: 9 additions & 7 deletions src/pylivestream/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from .stream import Stream
from .utils import run, check_device

__all__ = ["FileIn", "Microphone", "SaveDisk", "Screenshare", "Webcam"]
__all__ = ["FileIn", "Microphone", "SaveDisk", "Screenshare", "Camera"]


class Livestream(Stream):
Expand All @@ -16,7 +16,7 @@ def __init__(self, inifn: Path, site: str, **kwargs) -> None:

self.site = site.lower()

self.osparam()
self.osparam(inifn)

self.docheck = kwargs.get("docheck")

Expand Down Expand Up @@ -72,11 +72,13 @@ def __init__(self, inifn: Path, site: str, **kwargs) -> None:
+ self.videoIn(quick=True)
+ self.audioIn(quick=True)
+ ["-t", CHECKTIMEOUT]
+ ["-f", "null", "-"] # webcam needs at output
+ ["-f", "null", "-"] # camera needs at output
)

def startlive(self, sinks: list[str] = None):
"""finally start the stream(s)"""
"""
start the stream(s)
"""

if self.docheck:
self.check_device()
Expand Down Expand Up @@ -113,7 +115,7 @@ def startlive(self, sinks: list[str] = None):
if self.vidsource == "file":
# picks first video and audio stream, often correct
cmd += ["-map", "0:v", "-map", "0:a:0"]
else: # device (webcam)
else: # device (Camera)
# connect video device to video stream,
# audio device to audio stream
cmd += ["-map", "0:v", "-map", "1:a"]
Expand Down Expand Up @@ -181,7 +183,7 @@ def golive(self) -> None:
pass


class Webcam:
class Camera:
def __init__(self, inifn: Path, websites: list[str], **kwargs):

if isinstance(websites, str):
Expand Down Expand Up @@ -259,7 +261,7 @@ def __init__(self, inifn: Path, outfn: Path = None, **kwargs):

self.outfn = Path(outfn).expanduser() if outfn else None

self.osparam()
self.osparam(inifn)

vidIn: list[str] = self.videoIn()
vidOut: list[str] = self.videoOut()
Expand Down
6 changes: 3 additions & 3 deletions src/pylivestream/camera.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import signal
import argparse

from .api import stream_webcam
from .api import stream_camera


if __name__ == "__main__":
signal.signal(signal.SIGINT, signal.SIG_DFL)

p = argparse.ArgumentParser(description="livestream webcam")
p = argparse.ArgumentParser(description="livestream camera")
p.add_argument(
"websites",
help="site to stream, e.g. localhost youtube facebook twitch",
Expand All @@ -18,4 +18,4 @@
p.add_argument("-t", "--timeout", help="stop streaming after --timeout seconds", type=int)
P = p.parse_args()

stream_webcam(ini_file=P.json, websites=P.websites, assume_yes=P.yes, timeout=P.timeout)
stream_camera(ini_file=P.json, websites=P.websites, assume_yes=P.yes, timeout=P.timeout)
55 changes: 55 additions & 0 deletions src/pylivestream/data/pylivestream.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"screencap_origin": [50,30],
"screencap_size": [640, 480],
"screencap_fps": 30,
"camera_size": [640, 480],
"camera_fps": 30,
"audio_rate": 44100,
"audio_bps": 128000,
"preset": "veryfast",
"exe": "ffmpeg",
"ffprobe_exe": "ffprobe",
"win32": {
"vcap": "gdigrab",
"acap": "dshow",
"hcam": "dshow",
"screen_chan": "desktop",
"camera_chan": "video=Integrated Camera",
"audio_chan": "audio=Internal Microphone"
},
"darwin": {
"screen_chan": "1:0",
"audio_chan": "default",
"vcap": "avfoundation",
"hcam": "avfoundation"
},
"linux": {
"camera_chan": "/dev/video0",
"screen_chan": ":0.0",
"audio_chan": "default",
"vcap": "x11grab",
"acap": "pulse",
"hcam": "v4l2"
},
"sites":{
"localhost": {
"keyframe_sec": 2,
"url": "rtmp://localhost"
},
"file": {
"keyframe_sec": 2,
"video_kbps": 2000,
"screencap_size": [640, 480]
},
"facebook": {
"keyframe_sec": 2,
"url": "rtmps://live-api-s.facebook.com:443/rtmp",
"streamid": "xxxx-xxxx-xxxx-xxxx"
},
"youtube": {
"keyframe_sec": 2,
"url": "rtmp://a.rtmp.youtube.com/live2",
"streamid": "xxxx-xxxx-xxxx-xxxx"
}
}
}
2 changes: 1 addition & 1 deletion src/pylivestream/glob.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ def cli():
help="site to stream, e.g. localhost youtube facebook twitch",
nargs="+",
)
p.add_argument("-glob", help="file glob pattern to stream.")
p.add_argument("json", help="JSON file with stream parameters such as key")
p.add_argument("-glob", help="file glob pattern to stream.")
p.add_argument("-image", help="static image to display, for audio-only files.")
p.add_argument("-shuffle", help="shuffle the globbed file list", action="store_true")
p.add_argument("-loop", help="repeat the globbed file list endlessly", action="store_true")
Expand Down
Loading

0 comments on commit 7ef2a97

Please sign in to comment.