1
- from __future__ import annotations
2
1
from pathlib import Path
3
- import logging
4
2
import os
5
- import typing
6
3
7
4
from .stream import Stream
8
5
from .utils import run , check_device
@@ -75,7 +72,7 @@ def __init__(self, inifn: Path, site: str, **kwargs) -> None:
75
72
+ ["-f" , "null" , "-" ] # camera needs at output
76
73
)
77
74
78
- def startlive (self , sinks : list [ str ] | None = None ):
75
+ def startlive (self ):
79
76
"""
80
77
start the stream(s)
81
78
"""
@@ -94,53 +91,7 @@ def startlive(self, sinks: list[str] | None = None):
94
91
# listener stopped prematurely, probably due to error
95
92
raise RuntimeError (f"listener stopped with code { proc .poll ()} " )
96
93
# %% RUN STREAM
97
- if not sinks : # single stream
98
- run (self .cmd )
99
- elif self .movingimage :
100
- if len (sinks ) > 1 :
101
- logging .warning (f"streaming only to { sinks [0 ]} " )
102
-
103
- run (self .cmd )
104
- elif len (sinks ) == 1 :
105
- run (self .cmd )
106
- else :
107
- """
108
- multi-stream output tee
109
- https://trac.ffmpeg.org/wiki/Creating%20multiple%20outputs#Teepseudo-muxer
110
- https://trac.ffmpeg.org/wiki/EncodingForStreamingSites#Outputtingtomultiplestreamingserviceslocalfile
111
- """
112
- cmdstem : list [str ] = self .cmd [:- 3 ]
113
- # +global_header is necessary to tee to multiple services
114
- cmd : list [str ] = cmdstem + ["-flags:v" , "+global_header" , "-f" , "tee" ]
115
-
116
- if self .image :
117
- # connect image to video stream, audio file to audio stream
118
- cmd += ["-map" , "0:v" , "-map" , "1:a" ]
119
- else :
120
- if self .vidsource == "file" :
121
- # picks first video and audio stream, often correct
122
- cmd += ["-map" , "0:v" , "-map" , "0:a:0" ]
123
- else :
124
- # device (Camera)
125
- # connect video device to video stream,
126
- # audio device to audio stream
127
- cmd += ["-map" , "0:v" , "-map" , "1:a" ]
128
-
129
- # cannot have double quotes for Mac/Linux,
130
- # but need double quotes for Windows
131
- if os .name == "nt" :
132
- sink = f'"[f=flv]{ sinks [0 ][1 :- 1 ]} '
133
- for s in sinks [1 :]:
134
- sink += f"|[f=flv]{ s [1 :- 1 ]} "
135
- sink += '"'
136
- else :
137
- sink = f"[f=flv]{ sinks [0 ]} "
138
- for s in sinks [1 :]:
139
- sink += f"|[f=flv]{ s } "
140
-
141
- cmd .append (sink )
142
-
143
- run (cmd )
94
+ run (self .cmd )
144
95
145
96
# %% stop the listener before starting the next process, or upon final process closing.
146
97
if proc is not None and proc .poll () is None :
@@ -168,92 +119,44 @@ def check_device(self, site: str | None = None) -> bool:
168
119
169
120
# %% operators
170
121
class Screenshare :
171
- def __init__ (self , inifn : Path , websites : list [str ], ** kwargs ) -> None :
172
-
173
- if isinstance (websites , str ):
174
- websites = [websites ]
122
+ def __init__ (self , inifn : Path , websites : str , ** kwargs ) -> None :
175
123
176
- streams = {}
177
- for site in websites :
178
- streams [site ] = Livestream (inifn , site , vidsource = "screen" , ** kwargs )
179
-
180
- self .streams : typing .Mapping [str , Livestream ] = streams
124
+ self .streams = Livestream (inifn , websites , vidsource = "screen" , ** kwargs )
181
125
182
126
def golive (self ) -> None :
183
127
184
- sinks : list [str ] = [self .streams [stream ].sink for stream in self .streams ]
185
-
186
- try :
187
- next (self .streams [unify_streams (self .streams )].startlive (sinks ))
188
- except StopIteration :
189
- pass
128
+ self .streams .startlive ()
190
129
191
130
192
131
class Camera :
193
- def __init__ (self , inifn : Path , websites : list [str ], ** kwargs ):
194
-
195
- if isinstance (websites , str ):
196
- websites = [websites ]
197
-
198
- streams = {}
199
- for site in websites :
200
- streams [site ] = Livestream (inifn , site , vidsource = "camera" , ** kwargs )
132
+ def __init__ (self , inifn : Path , websites : str , ** kwargs ):
201
133
202
- self .streams : dict [ str , Livestream ] = streams
134
+ self .streams = Livestream ( inifn , websites , vidsource = "camera" , ** kwargs )
203
135
204
136
def golive (self ) -> None :
205
137
206
- sinks : list [str ] = [self .streams [stream ].sink for stream in self .streams ]
207
-
208
- try :
209
- next (self .streams [unify_streams (self .streams )].startlive (sinks ))
210
- except StopIteration :
211
- pass
138
+ self .streams .startlive ()
212
139
213
140
214
141
class Microphone :
215
- def __init__ (self , inifn : Path , websites : list [str ], ** kwargs ):
216
-
217
- if isinstance (websites , str ):
218
- websites = [websites ]
142
+ def __init__ (self , inifn : Path , websites : str , ** kwargs ):
219
143
220
- streams = {}
221
- for site in websites :
222
- streams [site ] = Livestream (inifn , site , ** kwargs )
223
-
224
- self .streams : dict [str , Livestream ] = streams
144
+ self .streams = Livestream (inifn , websites , ** kwargs )
225
145
226
146
def golive (self ) -> None :
227
147
228
- sinks : list [str ] = [self .streams [stream ].sink for stream in self .streams ]
229
-
230
- try :
231
- next (self .streams [unify_streams (self .streams )].startlive (sinks ))
232
- except StopIteration :
233
- pass
148
+ self .streams .startlive ()
234
149
235
150
236
151
# %% File-based inputs
237
152
class FileIn :
238
- def __init__ (self , inifn : Path , websites : str | list [ str ] , ** kwargs ):
153
+ def __init__ (self , inifn : Path , websites : str , ** kwargs ):
239
154
240
- if isinstance (websites , str ):
241
- websites = [websites ]
242
-
243
- streams = {}
244
- for site in websites :
245
- streams [site ] = Livestream (inifn , site , vidsource = "file" , ** kwargs )
246
-
247
- self .streams : dict [str , Livestream ] = streams
155
+ self .streams = Livestream (inifn , websites , vidsource = "file" , ** kwargs )
248
156
249
157
def golive (self ) -> None :
250
158
251
- sinks : list [str ] = [self .streams [stream ].sink for stream in self .streams ]
252
-
253
- try :
254
- next (self .streams [unify_streams (self .streams )].startlive (sinks ))
255
- except StopIteration :
256
- pass
159
+ self .streams .startlive ()
257
160
258
161
259
162
class SaveDisk (Stream ):
@@ -295,23 +198,3 @@ def save(self):
295
198
296
199
else :
297
200
print ("specify filename to save screen capture w/ audio to disk." )
298
-
299
-
300
- def unify_streams (streams : typing .Mapping [str , Stream ]) -> str :
301
- """
302
- find least common denominator stream settings,
303
- so "tee" output can generate multiple streams.
304
- First try: use stream with lowest video bitrate.
305
-
306
- Exploits that Python has guaranteed dict() ordering.
307
-
308
- fast native Python argmin()
309
- https://stackoverflow.com/a/11825864
310
- """
311
- vid_bw : list [int ] = [streams [s ].video_kbps for s in streams ]
312
-
313
- argmin : int = min (range (len (vid_bw )), key = vid_bw .__getitem__ )
314
-
315
- key : str = list (streams .keys ())[argmin ]
316
-
317
- return key
0 commit comments