# -*- coding: utf-8 -*-
"""
:Module: khorosjx.content.videos
:Synopsis: Collection of functions relating to videos (e.g. https://community.example.com/videos/1234)
:Usage: ``from khorosjx.content import videos``
:Example: ``content_id = videos.get_content_id(url)``
:Created By: Jeff Shurtliff
:Last Modified: Jeff Shurtliff
:Modified Date: 22 Sep 2021
"""
import os.path
from .. import core, errors
from . import base
from ..utils import core_utils
# Define global variables
base_url, api_credentials = '', None
[docs]def verify_core_connection():
"""This function verifies that the core connection information (Base URL and API credentials) has been defined.
.. versionchanged:: 3.1.0
Refactored the function to be more pythonic and to avoid depending on a try/except block.
:returns: None
:raises: :py:exc:`khorosjx.errors.exceptions.KhorosJXError`,
:py:exc:`khorosjx.errors.exceptions.NoCredentialsError`
"""
if not base_url or not api_credentials:
retrieve_connection_info()
return
[docs]def retrieve_connection_info():
"""This function initializes and defines the global variables for the connection information.
.. versionchanged:: 3.1.0
Refactored the function to be more efficient.
:returns: None
:raises: :py:exc:`khorosjx.errors.exceptions.KhorosJXError`,
:py:exc:`khorosjx.errors.exceptions.NoCredentialsError`
"""
# Define the global variables at this module level
global base_url
global api_credentials
base_url, api_credentials = core.get_connection_info()
return
[docs]def get_video_id(lookup_value, lookup_type='url'):
"""This function returns the video ID for a video when given its URL or a Content ID.
:param lookup_value: The URL or Content ID with which to identify the video
:type lookup_value: str, int
:param lookup_type: Defines the type of value given to identify the video as ``url`` (default) or ``content_id``
:type lookup_type: str
:returns: The Video ID for the video
:raises: :py:exc:`khorosjx.errors.exceptions.InvalidLookupTypeError`
"""
if lookup_type == 'url':
video_id = lookup_value.split('videos/')[1]
elif lookup_type == 'content_id' or lookup_type == 'id':
video_json = core.get_data('contents', lookup_value, return_json=True)
video_id = video_json['id']
else:
errors.handlers.bad_lookup_type(lookup_type, ('url', 'content_id'))
return video_id
def __construct_url_from_id(_video_id):
"""This function returns the URL to a video when supplied its Video ID."""
return f"{core.get_base_url(api_base=False)}/videos/{_video_id}"
# Define function to get the content ID from a URL
[docs]def get_content_id(lookup_value, lookup_type='url'):
"""This function obtains the Content ID for a particular video.
:param lookup_value: The URL or Video ID associated with the video
:type lookup_value: str, int
:param lookup_type: Defines the type of value given to identify the video as ``url`` (default) or ``id``
:returns: The Content ID for the video
:raises: :py:exc:`ValueError`, :py:exc:`khorosjx.errors.exceptions.InvalidLookupTypeError`
"""
accepted_lookup_types = ['url', 'id', 'video_id']
if lookup_type not in accepted_lookup_types:
errors.handlers.bad_lookup_type(lookup_type, ('url', 'id'))
if lookup_type != 'url':
lookup_value = __construct_url_from_id(lookup_value)
content_id = base.get_content_id(lookup_value, 'video')
return content_id
# Define function to download a video when supplied with its download URL
[docs]def download_video(video_url, output_path, output_name="", default_type="mp4", verbose=False):
"""This function downloads a video file when provided its URLs and an output name and location.
:param video_url: The direct download URL with its accompanying authorization token
:type video_url: str
:param output_path: The full path to the directory where the video file should be downloaded
:type output_path: str
:param output_name: The name of the output file (e.g. ``video.mp4``)
:type output_name: str
:param default_type: Defines a default file extension (``mp4`` by default) if an extension is not found
:type default_type: str
:param verbose: Determines if verbose console output should be displayed (``False`` by default)
:type verbose: bool
:returns: None
"""
try:
if "." not in output_name:
output_name = f"{output_name}.{default_type}"
output_path = os.path.join(output_path, output_name)
api_response = core.get_request_with_retries(video_url)
core_utils.print_if_verbose('Processing...', verbose)
f = open(output_path, 'wb')
for chunk in api_response.iter_content(chunk_size=255):
# filter out keep-alive new chunks
if chunk:
f.write(chunk)
core_utils.print_if_verbose(f'The video has been exported here: {output_path}', verbose)
f.close()
except Exception as exception_msg:
print(f"The video could not be downloaded due to the following error: {exception_msg}")
return
def __append_videos(_all_videos, _query, _start_index, _return_fields, _ignore_exceptions):
"""This function retrieves paginated results and appends them to a master list."""
_videos = core.get_paginated_results(_query, 'video', _start_index, filter_info=('type', 'video'),
return_fields=_return_fields, ignore_exceptions=_ignore_exceptions)
_all_videos = core_utils.add_to_master_list(_videos, _all_videos)
return _all_videos, len(_videos)
[docs]def get_native_videos_for_space(browse_id, return_fields=[], return_type='list', ignore_exceptions=False):
"""This function returns information on all native (i.e. non-attachment and not third party) for a given space.
:param browse_id: The Browse ID associated with the space
:type browse_id: int, str
:param return_fields: Specific fields to return if not all of the default fields are needed (Optional)
:type return_fields: list
:param return_type: Determines if the data should be returned in a list or a pandas dataframe (Default: ``list``)
:type return_type: str
:param ignore_exceptions: Determines whether nor not exceptions should be ignored (Default: ``False``)
:type ignore_exceptions: bool
:returns: A list of dictionaries or a dataframe containing information for each group
:raises: :py:exc:`khorosjx.errors.exceptions.InvalidDatasetError`
"""
# Initialize the master list that will contain all of the video data
all_videos = []
# Perform the first query to get up to the first 100 groups
start_index = 0
query = core.get_query_url('places', browse_id, 'contents')
all_videos, assets_returned = __append_videos(all_videos, query, start_index, return_fields, ignore_exceptions)
# Continue querying for videos until none are returned
while assets_returned > 0:
start_index += 100
all_videos, assets_returned = __append_videos(all_videos, query, start_index, return_fields, ignore_exceptions)
# Return the data as a master list of group dictionaries or a pandas dataframe
if return_type == "dataframe":
all_videos = core_utils.convert_dict_list_to_dataframe(all_videos)
return all_videos
[docs]def find_video_attachments(document_attachments):
"""This function identifies any attached videos in a collection of document attachments.
:param document_attachments: Attachments associated with a document
:type document_attachments: list, dict
:returns: A list of dictionaries containing info on any video attachments
"""
if isinstance(document_attachments, dict):
document_attachments = [document_attachments]
video_info_list = []
for collection in document_attachments:
if "video" in collection['contentType']:
size = round(collection['size']/1048576, 2)
video_info_list.append({"download_url": collection['url'], "size": size})
return video_info_list
[docs]def get_video_info(lookup_value, lookup_type='content_id'):
"""This function retrieves information about a given video in JSON format.
:param lookup_value: The value with which to look up the video
:type lookup_value: str, int
:param lookup_type: Defines whether the lookup value is a ``content_id`` (default), ``video_id`` or ``url``
:type lookup_type: str
:returns: The video information in JSON format
:raises: :py:exc:`khorosjx.errors.exceptions.InvalidLookupTypeError`
"""
accepted_lookup_types = ['url', 'content_id', 'video_id']
if lookup_type not in accepted_lookup_types:
errors.handlers.bad_lookup_type(lookup_type, ('url', 'content_id'))
if lookup_type != 'content_id':
if lookup_type == 'video_id':
lookup_value = __construct_url_from_id(lookup_type)
lookup_value = get_content_id(lookup_value, 'url')
return core.get_data('contents', lookup_value, return_json=True)
[docs]def check_if_embedded(lookup_value, lookup_type='content_id'):
"""This function checks to see if a native video is embedded in a document or just a standalone video.
:param lookup_value: The value with which to look up the video
:type lookup_value: str, int
:param lookup_type: Defines if the lookup value is a ``content_id`` (default), ``video_id`` or ``url``
:type lookup_type: str
:returns: A Boolean value indicating whether or not the video is embedded
:raises: :py:exc:`khorosjx.errors.exceptions.InvalidLookupTypeError`
"""
video_json = get_video_info(lookup_value, lookup_type)
return video_json['embedded']
[docs]def get_video_dimensions(lookup_value, lookup_type='content_id'):
"""This function returns the dimensions of a given video.
:param lookup_value: The value with which to look up the video
:type lookup_value: str, int
:param lookup_type: Defines if the lookup value is a ``content_id`` (default), ``video_id`` or ``url``
:type lookup_type: str
:returns: The video dimensions in string format
:raises: :py:exc:`khorosjx.errors.exceptions.InvalidLookupTypeError`
"""
video_json = get_video_info(lookup_value, lookup_type)
dimensions = f"{video_json['width']}x{video_json['height']}"
return dimensions