-
Notifications
You must be signed in to change notification settings - Fork 64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(feat): Windows support #85
Changes from 16 commits
49f70fb
c9d275e
1a79cfb
3d2c926
87f9559
a8cac17
22d907f
222471c
8fff654
0b875c1
eac2d66
0a8905d
5e52c41
30987f4
c5a1201
78b3210
f4c6619
9708518
d52d65d
1f5f0b4
f42bbc0
6917446
e1e285f
d30dc73
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,4 +11,5 @@ shaka_streamer.egg-info/ | |
.idea/ | ||
venv/ | ||
dev/ | ||
.vscode/ | ||
.vscode/ | ||
packager.exe |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -243,25 +243,12 @@ def disallow_field(name: str, reason: str) -> None: | |
disallow_field('start_time', reason) | ||
disallow_field('end_time', reason) | ||
|
||
# A path to a pipe into which this input's contents are fed. | ||
# None for most input types. | ||
self._pipe: Optional[str] = None | ||
|
||
def set_pipe(self, pipe: str) -> None: | ||
"""Set the path to a pipe into which this input's contents are fed. | ||
|
||
If set, this is what TranscoderNode will read from instead of .name. | ||
""" | ||
|
||
self._pipe = pipe | ||
|
||
def get_path_for_transcode(self) -> str: | ||
"""Get the path which the transcoder will use to read the input. | ||
|
||
For some input types, this is a named pipe. For others, this is .name. | ||
def reset_name(self, pipe: str) -> None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Now that we have a Pipe class, calling this param There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I overlooked this one. |
||
"""Set the name to a pipe into which this input's contents are fed. | ||
""" | ||
|
||
return self._pipe or self.name | ||
self.name = pipe | ||
|
||
def get_stream_specifier(self) -> str: | ||
"""Get an FFmpeg stream specifier for this input. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,34 +16,33 @@ | |
|
||
from streamer.bitrate_configuration import AudioCodec, AudioChannelLayout, VideoCodec, VideoResolution | ||
from streamer.input_configuration import Input, MediaType | ||
from typing import Dict, Optional, Union | ||
from streamer.pipe import Pipe | ||
from typing import Dict, Union | ||
|
||
|
||
class OutputStream(object): | ||
"""Base class for output streams.""" | ||
|
||
def __init__(self, | ||
type: MediaType, | ||
pipe: Optional[str], | ||
input: Input, | ||
codec: Union[AudioCodec, VideoCodec, None]) -> None: | ||
codec: Union[AudioCodec, VideoCodec, None], | ||
pipe_dir: str, | ||
skip_transcoding: bool = False, | ||
pipe_suffix: str = '') -> None: | ||
|
||
self.type: MediaType = type | ||
# If "pipe" is None, then this will not be transcoded. | ||
self.pipe: Optional[str] = pipe | ||
self.skip_transcoding = skip_transcoding | ||
self.input: Input = input | ||
self.codec: Union[AudioCodec, VideoCodec, None] = codec | ||
self._features: Dict[str, str] = {} | ||
self.codec: Union[AudioCodec, VideoCodec, None] = codec | ||
|
||
def fill_template(self, template: str, **kwargs) -> str: | ||
"""Fill in a template string using **kwargs and features of the output.""" | ||
|
||
value_map: Dict[str, str] = {} | ||
# First take any feature values from this object. | ||
value_map.update(self._features) | ||
# Then fill in any values from kwargs. | ||
value_map.update(kwargs) | ||
# Now fill in the template with these values. | ||
return template.format(**value_map) | ||
if self.skip_transcoding: | ||
# If skip_transcoding is specified, let the Packager read from a plain | ||
# file instead of an IPC pipe. | ||
self.ipc_pipe = Pipe.create_file_pipe(self.input.name, mode='r') | ||
else: | ||
self.ipc_pipe = Pipe.create_ipc_pipe(pipe_dir, pipe_suffix) | ||
|
||
def is_hardware_accelerated(self) -> bool: | ||
"""Returns True if this output stream uses hardware acceleration.""" | ||
|
@@ -60,16 +59,44 @@ def is_dash_only(self) -> bool: | |
"""Returns True if the output format is restricted to DASH protocol""" | ||
assert self.codec is not None | ||
return self.codec.get_output_format() is 'webm' | ||
|
||
def get_init_seg_file(self, **kwargs) -> Pipe: | ||
INIT_SEGMENT = { | ||
MediaType.AUDIO: '{dir}/audio_{language}_{channels}c_{bitrate}_{codec}_init.{format}', | ||
MediaType.VIDEO: '{dir}/video_{resolution_name}_{bitrate}_{codec}_init.{format}', | ||
MediaType.TEXT: '{dir}/text_{language}_init.{format}', | ||
} | ||
path_templ = INIT_SEGMENT[self.type].format(**self._features, **kwargs) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: I think I would prefer the complete words, for clarity: But then I look again and realize it's not a template, it's an actual path. The template was the value from the dictionary, but after format(), you get a concrete path. So how about |
||
return Pipe.create_file_pipe(path_templ, mode='w') | ||
|
||
def get_media_seg_file(self, **kwargs) -> Pipe: | ||
MEDIA_SEGMENT = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like that you decided to move these here. I imagine this will make some concat tasks easier, since you can get the output path. |
||
MediaType.AUDIO: '{dir}/audio_{language}_{channels}c_{bitrate}_{codec}_$Number$.{format}', | ||
MediaType.VIDEO: '{dir}/video_{resolution_name}_{bitrate}_{codec}_$Number$.{format}', | ||
MediaType.TEXT: '{dir}/text_{language}_$Number$.{format}', | ||
} | ||
path_templ = MEDIA_SEGMENT[self.type].format(**self._features, **kwargs) | ||
return Pipe.create_file_pipe(path_templ, mode='w') | ||
|
||
def get_single_seg_file(self, **kwargs) -> Pipe: | ||
SINGLE_SEGMENT = { | ||
MediaType.AUDIO: '{dir}/audio_{language}_{channels}c_{bitrate}_{codec}.{format}', | ||
MediaType.VIDEO: '{dir}/video_{resolution_name}_{bitrate}_{codec}.{format}', | ||
MediaType.TEXT: '{dir}/text_{language}.{format}', | ||
} | ||
path_templ = SINGLE_SEGMENT[self.type].format(**self._features, **kwargs) | ||
return Pipe.create_file_pipe(path_templ, mode='w') | ||
|
||
|
||
class AudioOutputStream(OutputStream): | ||
|
||
def __init__(self, | ||
pipe: str, | ||
input: Input, | ||
pipe_dir: str, | ||
codec: AudioCodec, | ||
channels: int) -> None: | ||
|
||
super().__init__(MediaType.AUDIO, pipe, input, codec) | ||
super().__init__(MediaType.AUDIO, input, codec, pipe_dir) | ||
# Override the codec type and specify that it's an audio codec | ||
self.codec: AudioCodec = codec | ||
|
||
|
@@ -105,11 +132,11 @@ def get_bitrate(self) -> str: | |
class VideoOutputStream(OutputStream): | ||
|
||
def __init__(self, | ||
pipe: str, | ||
input: Input, | ||
pipe_dir: str, | ||
codec: VideoCodec, | ||
resolution: VideoResolution) -> None: | ||
super().__init__(MediaType.VIDEO, pipe, input, codec) | ||
super().__init__(MediaType.VIDEO, input, codec, pipe_dir) | ||
# Override the codec type and specify that it's an audio codec | ||
self.codec: VideoCodec = codec | ||
self.resolution = resolution | ||
|
@@ -130,14 +157,16 @@ def get_bitrate(self) -> str: | |
class TextOutputStream(OutputStream): | ||
|
||
def __init__(self, | ||
pipe: Optional[str], | ||
input: Input): | ||
input: Input, | ||
pipe_dir: str, | ||
skip_transcoding: bool): | ||
# We don't have a codec per se for text, but we'd like to generically | ||
# process OutputStream objects in ways that are easier with this attribute | ||
# set, so set it to None. | ||
codec = None | ||
|
||
super().__init__(MediaType.TEXT, pipe, input, codec) | ||
super().__init__(MediaType.TEXT, input, codec, pipe_dir, | ||
skip_transcoding, pipe_suffix='.vtt') | ||
|
||
# The features that will be used to generate the output filename. | ||
self._features = { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will only run on the system where we build the package, which will usually be Linux. Checking
os.name
here won't work, because no matter what platform we install it on later, this part is only run once when we build the package.Instead, to express a platform-specific dependency, I just found out there is a syntax for that:
'pypiwin32 ; platform_system=="Windows"'
See https://stackoverflow.com/a/42946495