Stream-Localhost - A secured interface to stream videos

Main Module

async pystream.main.redirect_exception_handler(request: Request, exception: RedirectException) JSONResponse

Custom exception handler to handle redirect.

Parameters:
  • request – Takes the Request object as an argument.

  • exception – Takes the RedirectException object inherited from Exception as an argument.

Returns:

Returns the JSONResponse with content, status code and cookie.

Return type:

JSONResponse

async pystream.main.startup_tasks() None

Tasks that need to run during the API startup.

async pystream.main.shutdown_tasks() None

Tasks that need to run during the API shutdown.

async pystream.main.start(**kwargs) None

Starter function for the streaming API.

Parameters:

**kwargs – Keyword arguments to load the env config.

Models

Authenticator

async pystream.models.authenticator.failed_auth_counter(request: Request) None

Keeps track of failed login attempts from each host, and redirects if failed for 3 or more times.

Parameters:

request – Takes the Request object as an argument.

async pystream.models.authenticator.extract_credentials(request: Request) List[str]

Extract the credentials from Authorization headers and decode it before returning as a list of strings.

async pystream.models.authenticator.raise_error(request) NoReturn

Raises a 401 Unauthorized error in case of bad credentials.

async pystream.models.authenticator.verify_login(request: Request) Dict[str, Union[str, int]]

Verifies authentication and generates session token for each user.

Returns:

Returns a dictionary with the payload required to create the session token.

Return type:

Dict[str, str]

async pystream.models.authenticator.verify_token(token: str) None

Decrypts the symmetric encrypted token and validates the session token and expiration.

Parameters:

token – Symmetric encrypted key.

Config

pystream.models.config.as_dict(pairs: List[Tuple[str, str]]) Dict[str, str]

Custom decoder for json.loads passed via object_pairs_hook raising error on duplicate keys.

Parameters:

pairs – Takes the ordered list of pairs as an argument.

Returns:

A dictionary of key-value pairs.

Return type:

Dict[str, str]

class pystream.models.config.EnvConfig(_case_sensitive: bool | None = None, _env_prefix: str | None = None, _env_file: DotenvType | None = PosixPath('.'), _env_file_encoding: str | None = None, _env_nested_delimiter: str | None = None, _secrets_dir: str | Path | None = None, *, authorization: Any, video_source: Path, video_host: IPv4Address = '127.0.0.1', video_port: int = 8000, session_duration: int = 3600, file_formats: Sequence[str] = ('.mov', '.mp4'), workers: int = 1, websites: Optional[List[str]] = [], auto_thumbnail: bool = True, key_file: Optional[Path] = None, cert_file: Optional[Path] = None, secure_session: bool = False)

Configure all env vars and validate using pydantic to share across modules.

>>> EnvConfig

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

__init__ uses __pydantic_self__ instead of the more common self for the first arg to allow self as a field name.

authorization: Any
video_source: Path
video_host: IPv4Address
video_port: int
session_duration: int
file_formats: Sequence[str]
workers: int
websites: Optional[List[str]]
auto_thumbnail: bool
key_file: Optional[Path]
cert_file: Optional[Path]
secure_session: bool
class Config

Environment variables configuration.

env_prefix = ''
env_file = '.env'
extra = 'ignore'
hide_input_in_errors = True
classmethod parse_authorization(value: Any) Dict[str, SecretStr]

Validates the authorization parameter and converts plain text passwords into SecretStr objects.

classmethod parse_video_host(value: IPv4Address) str

Returns the string notion of IPv4Address object.

classmethod parse_websites(value: str) List[str]

Evaluates the string as a list and returns the list of strings.

class pystream.models.config.FileIO(*, index: str = 'index.html', listing: str = 'list.html', landing: str = 'land.html')

Loads all the files’ path required/created.

>>> FileIO

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

__init__ uses __pydantic_self__ instead of the more common self for the first arg to allow self as a field name.

index: str
listing: str
landing: str
class pystream.models.config.Static(*, track: str = 'track', stream: str = 'stream', preview: str = 'preview', query_param: str = 'file', home_endpoint: str = '/home', login_endpoint: str = '/login', logout_endpoint: str = '/logout', streaming_endpoint: str = '/video', chunk_size: int = 1048576, deletions: ~typing.Set[~pathlib.PosixPath] = {}, cipher_suite: ~cryptography.fernet.Fernet = <cryptography.fernet.Fernet object>)

Object to store static values.

>>> Static

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

__init__ uses __pydantic_self__ instead of the more common self for the first arg to allow self as a field name.

track: str
stream: str
preview: str
query_param: str
home_endpoint: str
login_endpoint: str
logout_endpoint: str
streaming_endpoint: str
chunk_size: int
deletions: Set[PosixPath]
cipher_suite: Fernet
class Config

Static configuration.

arbitrary_types_allowed = True
class pystream.models.config.Session(*, info: dict = {}, invalid: dict = {}, mapping: dict = {})

Object to store session information.

>>> Session

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

__init__ uses __pydantic_self__ instead of the more common self for the first arg to allow self as a field name.

info: dict
invalid: dict
mapping: dict
class pystream.models.config.WebToken(*, token: str, username: str, timestamp: int)

Object to store and validate the symmetric ecrypted payload.

>>> WebToken

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

__init__ uses __pydantic_self__ instead of the more common self for the first arg to allow self as a field name.

token: str
username: str
timestamp: int
exception pystream.models.config.RedirectException(location: str, detail: Optional[str] = '')

Custom RedirectException raised within the API since HTTPException doesn’t support returning HTML content.

>>> RedirectException

See also

  • RedirectException allows the API to redirect on demand in cases where returning is not a solution.

  • There are alternatives to raise HTML content as an exception but none work with our use-case with JavaScript.

  • This way of exception handling comes handy for many unexpected scenarios.

References

https://fastapi.tiangolo.com/tutorial/handling-errors/#install-custom-exception-handlers

Instantiates the RedirectException object with the required parameters.

Parameters:
  • location – Location for redirect.

  • detail – Reason for redirect.

pystream.models.config.env

alias of EnvConfig

Images

class pystream.models.images.Images(filepath: PosixPath)

Initiates Images object to generate thumbnails and capture frames for a particular video.

>>> Images

Instantiates the object using opencv’s VideoCapture object.

Parameters:

filepath – Path of the video file.

generate_thumbnails(interval: int = 1, output_dir: Optional[PosixPath] = None) bool

Generate thumbnails for a video file.

Parameters:
  • interval – Interval in seconds to capture frame as thumbnail.

  • output_dir – Output directory to store the thumbnails.

Returns:

Returns a boolean flag to indicate success/failure.

Return type:

bool

get_video_length() Tuple[int, timedelta]

Get the number of frames to calculate length of the video.

Returns:

A tuple of seconds and the timedelta value.

Return type:

Tuple[int, datetime.timedelta]

generate_preview(path: str, at_second: Optional[int] = None) bool

Generate preview image for a video.

Parameters:
  • path – Filepath to store the preview image.

  • at_second – Time in seconds at which the image should be captured for preview.

Returns:

Returns a boolean flag to indicate success/failure.

Return type:

bool

Squire

pystream.models.squire.log_connection(request: Request) None

Logs the connection information.

See also

  • Only logs the first connection from a device.

  • This avoids multiple logs when same device requests different videos.

pystream.models.squire.natural_sort_key(filename: str) List[Union[int, str]]

Key function for sorting filenames in a natural way.

Parameters:

filename – Takes the filename as an argument.

Returns:

Returns a list of elements derived from splitting the filename into parts using a regular expression.

Return type:

List[Union[int, str]]

pystream.models.squire.get_dir_stream_content(parent: PosixPath, subdir: str) List[Dict[str, str]]

Get the video files inside a particular directory.

Parameters:
  • parent – Parent directory as displayed in the login page.

  • subdir – Subdirectory within which video files exist.

Returns:

A list of dictionaries with filename and the filepath as key-value pairs.

Return type:

List[Dict[str, str]]

pystream.models.squire.get_all_stream_content() Dict[str, List[Dict[str, str]]]

Get video files or folders that contain video files to be streamed.

Returns:

Dictionary of files and directories with name and path as key-value pairs on each section.

Return type:

Dict[str, List[str]]

pystream.models.squire.get_iter(filename: PurePath) Union[Tuple[str, str], Tuple[None, None]]

Sort video files at the currently served file’s directory, and return the previous and next filenames.

Parameters:

filename – Path to the video file currently rendered.

Returns:

Tuple of previous file and next file.

Return type:

Tuple[str, str]

pystream.models.squire.remove_thumbnail(img_path: PosixPath) None

Triggered by timer to remove the thumbnail file.

Parameters:

img_path – PosixPath to the thumbnail image.

pystream.models.squire.keygen() str

Generate session token from secrets module, so that users are forced to log in when the server restarts.

Returns:

Returns a URL safe 64-bit token.

Return type:

str

Stream

pystream.models.stream.send_bytes_range_requests(file_obj: BinaryIO, start_range: int, end_range: int) AsyncIterable[Union[str, ByteString]]

Send a file in chunks using Range Requests specification RFC7233.

Parameters:
  • file_obj – File data as bytes.

  • start_range – Start of range.

  • end_range – End of range.

Yields:

ByteString – Bytes as iterable.

pystream.models.stream.get_range_header(range_header: str, file_size: int) Tuple[int, int]

Proces range header.

Parameters:
  • range_header – Range values from the headers.

  • file_size – Size of the file.

Returns:

Start and end of the video file.

Return type:

Tuple

pystream.models.stream.range_requests_response(range_header: str, file_path: str) StreamingResponse

Returns StreamingResponse using Range Requests of a given file.

Parameters:
  • range_header – Range values from the headers.

  • file_path – Path of the file.

Returns:

Streaming response from fastapi.

Return type:

StreamingResponse

Subtitles

async pystream.models.subtitles.srt_to_vtt(filename: PosixPath) None

Convert a .srt file to .vtt for subtitles to be compatible with video-js.

Parameters:

filename – Name of the srt file.

async pystream.models.subtitles.vtt_to_srt(filename: PosixPath)

Convert a .vtt file to .srt for subtitles to be compatible with video-js.

Parameters:

filename – Name of the srt file.

Routers

Authentication

pystream.routers.auth.get_expiry(lease_start: int, lease_duration: int) str

Get expiry datetime as string using max age.

Parameters:
  • lease_start – Time when the authentication was made.

  • lease_duration – Number of seconds until expiry.

Returns:

Returns the date and time of expiry in GMT.

Return type:

str

async pystream.routers.auth.home_page(request: Request, session_token: str = Cookie(None)) TemplateResponse

Serves the home/index page for the UI.

Parameters:
  • request – Takes the Request object as an argument.

  • session_token – Session token set after verifying username and password.

Returns:

Returns the listing page for video streaming.

Return type:

TemplateResponse

async pystream.routers.auth.login(request: Request) JSONResponse

Authenticates the user input and returns a redirect response with the session token set as a cookie.

Parameters:

request – Takes the Request object as an argument.

Returns:

Returns the JSONResponse with content, status code and cookie.

Return type:

JSONResponse

async pystream.routers.auth.logout(request: Request, session_token: str = Cookie(None)) HTMLResponse

Terminates the user’s session by deleting the cookie and redirecting back to login page upon refresh.

Parameters:
  • request – Takes the Request object as an argument.

  • session_token – Session token set after verifying username and password.

Returns:

HTML page for logout with content rendered based on current login status.

Return type:

HTMLResponse

Basics

async pystream.routers.basics.get_favicon() FileResponse

Gets the favicon.ico and adds to the API endpoint.

Returns:

Uses FileResponse to send the favicon.ico to support the robinhood script’s robinhood.html.

Return type:

FileResponse

async pystream.routers.basics.root(request: Request) RedirectResponse

Reads the root request to render HTMl page.

Returns:

Redirects to login page.

Return type:

RedirectResponse

async pystream.routers.basics.error(detail: str = Cookie(None)) HTMLResponse

Endpoint to serve broken pages as HTML response.

Parameters:

detail – Optional session related information.

Returns:

Rendered HTML response with deleted cookie.

Return type:

HTMLResponse

Video

async pystream.routers.video.preview_loader(request: Request, img_path: str, session_token: str = Cookie(None)) FileResponse

Returns the file for preview image.

Parameters:
  • request – Takes the Request object as an argument.

  • img_path – Path of the image file that has to be rendered.

  • session_token – Token setup for each session.

Returns:

FileResponse for preview image.

Return type:

FileResponse

async pystream.routers.video.track_loader(request: Request, track_path: str, session_token: str = Cookie(None)) FileResponse

Returns the file for subtitles.

Parameters:
  • request – Takes the Request object as an argument.

  • track_path – Path of the subtitle track that has to be rendered.

  • session_token – Token setup for each session.

Returns:

FileResponse for subtitle track.

Return type:

FileResponse

async pystream.routers.video.stream_video(request: Request, video_path: str, session_token: str = Cookie(None)) TemplateResponse

Returns the template for streaming page.

Parameters:
  • request – Takes the Request object as an argument.

  • video_path – Path of the video file that has to be rendered.

  • session_token – Token setup for each session.

Returns:

Returns the listing page for video streaming.

Return type:

templates.TemplateResponse

async pystream.routers.video.video_endpoint(request: Request, range: Optional[str] = Header(None), session_token: str = Cookie(None)) Union[RedirectResponse, StreamingResponse]

Streams the video file by sending bytes using StreamingResponse.

Parameters:
  • request – Takes the Request object as an argument.

  • range – Header information.

  • session_token – Token setup for each session.

Returns:

Streams the video name received as cookie.

Return type:

Union[RedirectResponse, StreamingResponse]

Support Modules

Logger

class pystream.logger.RootFilter(name='')

Class to initiate / filter in logs while preserving other access logs.

>>> RootFilter

See also

  • Filters "GET /login HTTP/1.1" 200 OK, "GET / HTTP/1.1" 307 Temporary Redirect, /video?vid_name=...

  • Includes redundant logging for each iterable passed in StreamingResponse

  • Overrides logging by implementing a subclass of logging.Filter

  • The method filter(record), that examines the log record and returns True to log it or False to discard it.

Initialize a filter.

Initialize with the name of the logger which, together with its children, will have its events allowed through the filter. If no name is specified, allow every event.

filter(record: LogRecord) bool

Filter out logging at / from log streams.

Parameters:

recordLogRecord represents an event which is created every time something is logged.

Returns:

False flag for the endpoint that needs to be filtered.

Return type:

bool

Utils

pystream.utils.get_local_ip() IPv4Address

Uses simple check on network id to retrieve the local IP address.

Returns:

Private IP address of host machine.

Return type:

IPv4Address

pystream.utils.get_public_ip() IPv4Address

Extract public IP address of the connection by making a request to external source.

Returns:

Public IP address of host machine’s connection.

Return type:

IPv4Address

Indices and tables