diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 21e6bebf31dd..ae7376b958e4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -107,6 +107,7 @@ /healthcare/**/* @GoogleCloudPlatform/healthcare-life-sciences @GoogleCloudPlatform/python-samples-reviewers /retail/**/* @GoogleCloudPlatform/cloud-retail-team @GoogleCloudPlatform/python-samples-reviewers /billing/**/* @GoogleCloudPlatform/billing-samples-maintainers @GoogleCloudPlatform/python-samples-reviewers +/video/live-stream/* @GoogleCloudPlatform/cloud-media-team @GoogleCloudPlatform/python-samples-reviewers /video/stitcher/* @GoogleCloudPlatform/cloud-media-team @GoogleCloudPlatform/python-samples-reviewers # Deprecated diff --git a/.github/blunderbuss.yml b/.github/blunderbuss.yml index e694c33100c7..26c3c6efb879 100644 --- a/.github/blunderbuss.yml +++ b/.github/blunderbuss.yml @@ -307,6 +307,7 @@ assign_prs_by: - labels: - "api: cloudmedia" - "api: videostitcher" + - "api: videolivestream" to: - GoogleCloudPlatform/cloud-media-team - labels: diff --git a/video/live-stream/README.md b/video/live-stream/README.md new file mode 100644 index 000000000000..64f68e8ef5ba --- /dev/null +++ b/video/live-stream/README.md @@ -0,0 +1,45 @@ +# Live Stream API Python Samples + +This directory contains samples for the Live Stream API. Use this API to +transcode live, linear video streams into a variety of formats. The Live Stream +API benefits broadcasters, production companies, businesses, and individuals +looking to transform their live video content for use across a variety of user +devices. For more information, see the +[Live Stream API documentation](https://cloud.google.com/livestream/). + +## Setup + +To run the samples, you need to first follow the steps in +[Before you begin](https://cloud.google.com/livestream/docs/how-to/before-you-begin). + +For more information on authentication, refer to the +[Authentication Getting Started Guide](https://cloud.google.com/docs/authentication/getting-started). + +## Install Dependencies + +1. Clone python-docs-samples repository and change directories to the sample directory +you want to use. + + $ git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git + +1. Install [pip](https://pip.pypa.io/) and +[virtualenv](https://virtualenv.pypa.io/) if you do not already have them. You +may want to refer to the +[Python Development Environment Setup Guide](https://cloud.google.com/python/setup) +for Google Cloud Platform for instructions. + +1. Create a virtualenv. Samples are compatible with Python 3.6+. + + $ virtualenv env + $ source env/bin/activate + +1. Install the dependencies needed to run the samples. + + $ pip install -r requirements.txt + +## Testing + +Make sure to enable the Live Stream API on the test project. Set the following +environment variable: + +* `GOOGLE_CLOUD_PROJECT` diff --git a/video/live-stream/asset_test.py b/video/live-stream/asset_test.py new file mode 100644 index 000000000000..857d08fc69ee --- /dev/null +++ b/video/live-stream/asset_test.py @@ -0,0 +1,64 @@ +# Copyright 2023 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import uuid + +from google.api_core.exceptions import FailedPrecondition, NotFound +from google.protobuf import empty_pb2 as empty +import pytest + +import create_asset +import delete_asset +import get_asset +import list_assets +import utils + +project_name = os.environ["GOOGLE_CLOUD_PROJECT"] +location = "us-central1" +asset_id = f"my-python-test-asset-{uuid.uuid4()}" +asset_uri = "gs://cloud-samples-data/media/ForBiggerEscapes.mp4" + + +def test_asset_operations(capsys: pytest.fixture) -> None: + # Clean up old resources in the test project + responses = list_assets.list_assets(project_name, location) + for response in responses: + next_asset_id = response.name.rsplit("/", 1)[-1] + if utils.is_resource_stale(response.create_time): + try: + delete_asset.delete_asset(project_name, location, next_asset_id) + except FailedPrecondition as e: + print(f"Ignoring FailedPrecondition, details: {e}") + except NotFound as e: + print(f"Ignoring NotFound, details: {e}") + + asset_name_project_id = ( + f"projects/{project_name}/locations/{location}/assets/{asset_id}" + ) + + # Tests + + response = create_asset.create_asset(project_name, location, asset_id, asset_uri) + assert asset_name_project_id in response.name + + list_assets.list_assets(project_name, location) + out, _ = capsys.readouterr() + assert asset_name_project_id in out + + response = get_asset.get_asset(project_name, location, asset_id) + assert asset_name_project_id in response.name + + response = delete_asset.delete_asset(project_name, location, asset_id) + assert response == empty.Empty() diff --git a/video/live-stream/channel_event_test.py b/video/live-stream/channel_event_test.py new file mode 100644 index 000000000000..ff3bc1146d1e --- /dev/null +++ b/video/live-stream/channel_event_test.py @@ -0,0 +1,78 @@ +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import uuid + +import pytest + +import create_channel +import create_channel_event +import create_input +import delete_channel +import delete_channel_event +import delete_input +import get_channel_event +import list_channel_events +import start_channel +import stop_channel + +project_name = os.environ["GOOGLE_CLOUD_PROJECT"] +location = "us-central1" +input_id = f"python-test-input-{uuid.uuid4()}" +channel_id = f"python-test-channel-{uuid.uuid4()}" +event_id = f"python-test-event-{uuid.uuid4()}" +output_bucket_name = f"python-test-bucket-{uuid.uuid4()}" +output_uri = f"gs://{output_bucket_name}/channel-test/" + + +def test_channel_event_operations(capsys: pytest.fixture) -> None: + # Set up + + event_name_project_id = f"projects/{project_name}/locations/{location}/channels/{channel_id}/events/{event_id}" + + create_input.create_input(project_name, location, input_id) + + create_channel.create_channel( + project_name, location, channel_id, input_id, output_uri + ) + + start_channel.start_channel(project_name, location, channel_id) + + # Tests + + response = create_channel_event.create_channel_event( + project_name, location, channel_id, event_id + ) + assert event_name_project_id in response.name + + response = get_channel_event.get_channel_event( + project_name, location, channel_id, event_id + ) + assert event_name_project_id in response.name + + list_channel_events.list_channel_events(project_name, location, channel_id) + out, _ = capsys.readouterr() + assert event_name_project_id in out + + response = delete_channel_event.delete_channel_event( + project_name, location, channel_id, event_id + ) + assert response is None + + # Clean up + + stop_channel.stop_channel(project_name, location, channel_id) + delete_channel.delete_channel(project_name, location, channel_id) + delete_input.delete_input(project_name, location, input_id) diff --git a/video/live-stream/channel_test.py b/video/live-stream/channel_test.py new file mode 100644 index 000000000000..8c2fbf20f9b9 --- /dev/null +++ b/video/live-stream/channel_test.py @@ -0,0 +1,143 @@ +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import uuid + +from google.api_core.exceptions import FailedPrecondition, NotFound +from google.protobuf import empty_pb2 as empty +import pytest + +import create_channel +import create_channel_with_backup_input +import create_input +import delete_channel +import delete_channel_event +import delete_input +import get_channel +import list_channel_events +import list_channels +import start_channel +import stop_channel +import update_channel +import utils + +project_name = os.environ["GOOGLE_CLOUD_PROJECT"] +location = "us-central1" +input_id = f"python-test-input-{uuid.uuid4()}" +updated_input_id = f"python-test-up-input-{uuid.uuid4()}" +channel_id = f"python-test-channel-{uuid.uuid4()}" +output_bucket_name = f"python-test-bucket-{uuid.uuid4()}" +output_uri = f"gs://{output_bucket_name}/channel-test/" + + +def test_channel_operations(capsys: pytest.fixture) -> None: + # Clean up old resources in the test project + channel_responses = list_channels.list_channels(project_name, location) + + for response in channel_responses: + next_channel_id = response.name.rsplit("/", 1)[-1] + input_attachments = response.input_attachments + if utils.is_resource_stale(response.create_time): + try: + event_responses = list_channel_events.list_channel_events( + project_name, location, next_channel_id + ) + for response in event_responses: + next_event_id = response.name.rsplit("/", 1)[-1] + try: + delete_channel_event.delete_channel_event( + project_name, location, next_channel_id, next_event_id + ) + except NotFound as e: + print(f"Ignoring NotFound, details: {e}") + try: + stop_channel.stop_channel(project_name, location, next_channel_id) + except FailedPrecondition as e: + print(f"Ignoring FailedPrecondition, details: {e}") + try: + delete_channel.delete_channel( + project_name, location, next_channel_id + ) + except FailedPrecondition as e: + print(f"Ignoring FailedPrecondition, try to stop channel: {e}") + try: + stop_channel.stop_channel( + project_name, location, next_channel_id + ) + except FailedPrecondition as e: + print(f"Ignoring FailedPrecondition, details: {e}") + except NotFound as e: + print(f"Ignoring NotFound, details: {e}") + except NotFound as e: + print(f"Ignoring NotFound, details: {e}") + + for input_attachment in input_attachments: + next_input_id = input_attachment.input.rsplit("/", 1)[-1] + try: + delete_input.delete_input(project_name, location, next_input_id) + except NotFound as e: + print(f"Ignoring NotFound, details: {e}") + + # Set up + + channel_name_project_id = ( + f"projects/{project_name}/locations/{location}/channels/{channel_id}" + ) + + create_input.create_input(project_name, location, input_id) + create_input.create_input(project_name, location, updated_input_id) + + # Tests + + response = create_channel.create_channel( + project_name, location, channel_id, input_id, output_uri + ) + assert channel_name_project_id in response.name + + list_channels.list_channels(project_name, location) + out, _ = capsys.readouterr() + assert channel_name_project_id in out + + response = update_channel.update_channel( + project_name, location, channel_id, updated_input_id + ) + assert channel_name_project_id in response.name + for input_attachment in response.input_attachments: + assert "updated-input" in input_attachment.key + + response = get_channel.get_channel(project_name, location, channel_id) + assert channel_name_project_id in response.name + + start_channel.start_channel(project_name, location, channel_id) + out, _ = capsys.readouterr() + assert "Started channel" in out + + stop_channel.stop_channel(project_name, location, channel_id) + out, _ = capsys.readouterr() + assert "Stopped channel" in out + + response = delete_channel.delete_channel(project_name, location, channel_id) + assert response == empty.Empty() + + response = create_channel_with_backup_input.create_channel_with_backup_input( + project_name, location, channel_id, input_id, updated_input_id, output_uri + ) + assert channel_name_project_id in response.name + + # Clean up + + delete_channel.delete_channel(project_name, location, channel_id) + delete_input.delete_input(project_name, location, input_id) + delete_input.delete_input(project_name, location, updated_input_id) diff --git a/video/live-stream/create_asset.py b/video/live-stream/create_asset.py new file mode 100644 index 000000000000..f282eef3f5b8 --- /dev/null +++ b/video/live-stream/create_asset.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for creating an asset. You use an + asset to create a slate. +Example usage: + python create_asset.py --project_id --location \ + --asset_id --asset_uri +""" + +# [START livestream_create_asset] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) + + +def create_asset( + project_id: str, location: str, asset_id: str, asset_uri: str +) -> live_stream_v1.types.Asset: + """Creates an asset. + Args: + project_id: The GCP project ID. + location: The location in which to create the asset. + asset_id: The user-defined asset ID. + asset_uri: The asset URI (e.g., 'gs://my-bucket/my-video.mp4').""" + + client = LivestreamServiceClient() + + parent = f"projects/{project_id}/locations/{location}" + + asset = live_stream_v1.types.Asset( + video=live_stream_v1.types.Asset.VideoAsset( + uri=asset_uri, + ) + ) + operation = client.create_asset(parent=parent, asset=asset, asset_id=asset_id) + response = operation.result(600) + print(f"Asset: {response.name}") + + return response + + +# [END livestream_create_asset] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location in which to create the asset.", + default="us-central1", + ) + parser.add_argument( + "--asset_id", + help="The user-defined asset ID.", + required=True, + ) + parser.add_argument( + "--asset_uri", + help="The asset URI (e.g., 'gs://my-bucket/my-video.mp4').", + required=True, + ) + args = parser.parse_args() + create_asset( + args.project_id, + args.location, + args.asset_id, + args.asset_uri, + ) diff --git a/video/live-stream/create_channel.py b/video/live-stream/create_channel.py new file mode 100644 index 000000000000..20c3c3a3e94b --- /dev/null +++ b/video/live-stream/create_channel.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for creating a channel. A channel resource + represents the processor that performs a user-defined "streaming" operation. +Example usage: + python create_channel.py --project_id --location \ + --channel_id --input_id --output_uri +""" + +# [START livestream_create_channel] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) +from google.protobuf import duration_pb2 as duration + + +def create_channel( + project_id: str, location: str, channel_id: str, input_id: str, output_uri: str +) -> live_stream_v1.types.Channel: + """Creates a channel. + Args: + project_id: The GCP project ID. + location: The location in which to create the channel. + channel_id: The user-defined channel ID. + input_id: The user-defined input ID. + output_uri: Uri of the channel output folder in a Cloud Storage bucket.""" + + client = LivestreamServiceClient() + parent = f"projects/{project_id}/locations/{location}" + input = f"projects/{project_id}/locations/{location}/inputs/{input_id}" + name = f"projects/{project_id}/locations/{location}/channels/{channel_id}" + + channel = live_stream_v1.types.Channel( + name=name, + input_attachments=[ + live_stream_v1.types.InputAttachment( + key="my-input", + input=input, + ), + ], + output=live_stream_v1.types.Channel.Output( + uri=output_uri, + ), + elementary_streams=[ + live_stream_v1.types.ElementaryStream( + key="es_video", + video_stream=live_stream_v1.types.VideoStream( + h264=live_stream_v1.types.VideoStream.H264CodecSettings( + profile="high", + width_pixels=1280, + height_pixels=720, + bitrate_bps=3000000, + frame_rate=30, + ), + ), + ), + live_stream_v1.types.ElementaryStream( + key="es_audio", + audio_stream=live_stream_v1.types.AudioStream( + codec="aac", channel_count=2, bitrate_bps=160000 + ), + ), + ], + mux_streams=[ + live_stream_v1.types.MuxStream( + key="mux_video", + elementary_streams=["es_video"], + segment_settings=live_stream_v1.types.SegmentSettings( + segment_duration=duration.Duration( + seconds=2, + ), + ), + ), + live_stream_v1.types.MuxStream( + key="mux_audio", + elementary_streams=["es_audio"], + segment_settings=live_stream_v1.types.SegmentSettings( + segment_duration=duration.Duration( + seconds=2, + ), + ), + ), + ], + manifests=[ + live_stream_v1.types.Manifest( + file_name="manifest.m3u8", + type_="HLS", + mux_streams=["mux_video", "mux_audio"], + max_segment_count=5, + ), + ], + ) + operation = client.create_channel( + parent=parent, channel=channel, channel_id=channel_id + ) + response = operation.result(600) + print(f"Channel: {response.name}") + + return response + + +# [END livestream_create_channel] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location in which to create the channel.", + default="us-central1", + ) + parser.add_argument( + "--channel_id", + help="The user-defined channel ID.", + required=True, + ) + parser.add_argument( + "--input_id", + help="The user-defined input ID.", + required=True, + ) + parser.add_argument( + "--output_uri", + help="The Cloud Storage bucket (and optional folder) in which to save the livestream output.", + required=True, + ) + args = parser.parse_args() + create_channel( + args.project_id, + args.location, + args.channel_id, + args.input_id, + args.output_uri, + ) diff --git a/video/live-stream/create_channel_event.py b/video/live-stream/create_channel_event.py new file mode 100644 index 000000000000..a1cc885f1648 --- /dev/null +++ b/video/live-stream/create_channel_event.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for creating a channel event. An event is a + sub-resource of a channel, which can be scheduled by the user to execute + operations on a channel resource without having to stop the channel. +Example usage: + python create_channel_event.py --project_id --location \ + --channel_id --event_id +""" + +# [START livestream_create_channel_event] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) +from google.protobuf import duration_pb2 as duration + + +def create_channel_event( + project_id: str, location: str, channel_id: str, event_id: str +) -> live_stream_v1.types.Event: + """Creates a channel event. + Args: + project_id: The GCP project ID. + location: The location of the channel. + channel_id: The user-defined channel ID. + event_id: The user-defined event ID.""" + + client = LivestreamServiceClient() + parent = f"projects/{project_id}/locations/{location}/channels/{channel_id}" + name = f"projects/{project_id}/locations/{location}/channels/{channel_id}/events/{event_id}" + + event = live_stream_v1.types.Event( + name=name, + ad_break=live_stream_v1.types.Event.AdBreakTask( + duration=duration.Duration( + seconds=30, + ), + ), + execute_now=True, + ) + + response = client.create_event(parent=parent, event=event, event_id=event_id) + print(f"Channel event: {response.name}") + + return response + + +# [END livestream_create_channel_event] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the channel.", + default="us-central1", + ) + parser.add_argument( + "--channel_id", + help="The user-defined channel ID.", + required=True, + ) + parser.add_argument( + "--event_id", + help="The user-defined event ID.", + required=True, + ) + args = parser.parse_args() + create_channel_event( + args.project_id, + args.location, + args.channel_id, + args.event_id, + ) diff --git a/video/live-stream/create_channel_with_backup_input.py b/video/live-stream/create_channel_with_backup_input.py new file mode 100644 index 000000000000..79256385be6d --- /dev/null +++ b/video/live-stream/create_channel_with_backup_input.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for creating a channel with a backup input. + A channel resource represents the processor that performs a user-defined + "streaming" operation. +Example usage: + python create_channel_with_backup_input.py --project_id \ + --location --channel_id \ + --primary_input_id \ + --backup_input_id --output_uri +""" + +# [START livestream_create_channel_with_backup_input] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) +from google.protobuf import duration_pb2 as duration + + +def create_channel_with_backup_input( + project_id: str, + location: str, + channel_id: str, + primary_input_id: str, + backup_input_id: str, + output_uri: str, +) -> live_stream_v1.types.Channel: + """Creates a channel. + Args: + project_id: The GCP project ID. + location: The location in which to create the channel. + channel_id: The user-defined channel ID. + primary_input_id: The user-defined primary input ID. + backup_input_id: The user-defined backup input ID. + output_uri: Uri of the channel output folder in a Cloud Storage bucket.""" + + client = LivestreamServiceClient() + parent = f"projects/{project_id}/locations/{location}" + primary_input = ( + f"projects/{project_id}/locations/{location}/inputs/{primary_input_id}" + ) + backup_input = ( + f"projects/{project_id}/locations/{location}/inputs/{backup_input_id}" + ) + name = f"projects/{project_id}/locations/{location}/channels/{channel_id}" + + channel = live_stream_v1.types.Channel( + name=name, + input_attachments=[ + live_stream_v1.types.InputAttachment( + key="my-primary-input", + input=primary_input, + automatic_failover=live_stream_v1.types.InputAttachment.AutomaticFailover( + input_keys=["my-backup-input"], + ), + ), + live_stream_v1.types.InputAttachment( + key="my-backup-input", + input=backup_input, + ), + ], + output=live_stream_v1.types.Channel.Output( + uri=output_uri, + ), + elementary_streams=[ + live_stream_v1.types.ElementaryStream( + key="es_video", + video_stream=live_stream_v1.types.VideoStream( + h264=live_stream_v1.types.VideoStream.H264CodecSettings( + profile="high", + width_pixels=1280, + height_pixels=720, + bitrate_bps=3000000, + frame_rate=30, + ), + ), + ), + live_stream_v1.types.ElementaryStream( + key="es_audio", + audio_stream=live_stream_v1.types.AudioStream( + codec="aac", channel_count=2, bitrate_bps=160000 + ), + ), + ], + mux_streams=[ + live_stream_v1.types.MuxStream( + key="mux_video", + elementary_streams=["es_video"], + segment_settings=live_stream_v1.types.SegmentSettings( + segment_duration=duration.Duration( + seconds=2, + ), + ), + ), + live_stream_v1.types.MuxStream( + key="mux_audio", + elementary_streams=["es_audio"], + segment_settings=live_stream_v1.types.SegmentSettings( + segment_duration=duration.Duration( + seconds=2, + ), + ), + ), + ], + manifests=[ + live_stream_v1.types.Manifest( + file_name="manifest.m3u8", + type_="HLS", + mux_streams=["mux_video", "mux_audio"], + max_segment_count=5, + ), + ], + ) + operation = client.create_channel( + parent=parent, channel=channel, channel_id=channel_id + ) + response = operation.result(600) + print(f"Channel: {response.name}") + + return response + + +# [END livestream_create_channel_with_backup_input] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location in which to create the channel.", + default="us-central1", + ) + parser.add_argument( + "--channel_id", + help="The user-defined channel ID.", + required=True, + ) + parser.add_argument( + "--primary_input_id", + help="The user-defined primary input ID.", + required=True, + ) + parser.add_argument( + "--backup_input_id", + help="The user-defined backup input ID.", + required=True, + ) + parser.add_argument( + "--output_uri", + help="The Cloud Storage bucket (and optional folder) in which to save the livestream output.", + required=True, + ) + args = parser.parse_args() + create_channel_with_backup_input( + args.project_id, + args.location, + args.channel_id, + args.primary_input_id, + args.backup_input_id, + args.output_uri, + ) diff --git a/video/live-stream/create_input.py b/video/live-stream/create_input.py new file mode 100644 index 000000000000..31d0b9ac33ab --- /dev/null +++ b/video/live-stream/create_input.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for creating an input endpoint. You send an + input video stream to this endpoint. +Example usage: + python create_input.py --project_id --location --input_id +""" + +# [START livestream_create_input] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) + + +def create_input( + project_id: str, location: str, input_id: str +) -> live_stream_v1.types.Input: + """Creates an input. + Args: + project_id: The GCP project ID. + location: The location in which to create the input. + input_id: The user-defined input ID.""" + + client = LivestreamServiceClient() + + parent = f"projects/{project_id}/locations/{location}" + + input = live_stream_v1.types.Input( + type_="RTMP_PUSH", + ) + operation = client.create_input(parent=parent, input=input, input_id=input_id) + response = operation.result(900) + print(f"Input: {response.name}") + + return response + + +# [END livestream_create_input] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location in which to create the input.", + default="us-central1", + ) + parser.add_argument( + "--input_id", + help="The user-defined input ID.", + required=True, + ) + args = parser.parse_args() + create_input( + args.project_id, + args.location, + args.input_id, + ) diff --git a/video/live-stream/delete_asset.py b/video/live-stream/delete_asset.py new file mode 100644 index 000000000000..1215756bd6a4 --- /dev/null +++ b/video/live-stream/delete_asset.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for deleting an asset. +Example usage: + python delete_asset.py --project_id --location --asset_id +""" + +# [START livestream_delete_asset] + +import argparse + +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) +from google.protobuf import empty_pb2 as empty + + +def delete_asset(project_id: str, location: str, asset_id: str) -> empty.Empty: + """Deletes an asset. + Args: + project_id: The GCP project ID. + location: The location of the asset. + asset_id: The user-defined asset ID.""" + + client = LivestreamServiceClient() + + name = f"projects/{project_id}/locations/{location}/assets/{asset_id}" + operation = client.delete_asset(name=name) + response = operation.result(600) + print("Deleted asset") + + return response + + +# [END livestream_delete_asset] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the asset.", + required=True, + ) + parser.add_argument( + "--asset_id", + help="The user-defined asset ID.", + required=True, + ) + args = parser.parse_args() + delete_asset( + args.project_id, + args.location, + args.asset_id, + ) diff --git a/video/live-stream/delete_channel.py b/video/live-stream/delete_channel.py new file mode 100644 index 000000000000..3f608b241783 --- /dev/null +++ b/video/live-stream/delete_channel.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for deleting a channel. +Example usage: + python delete_channel.py --project_id --location --channel_id +""" + +# [START livestream_delete_channel] + +import argparse + +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) +from google.protobuf import empty_pb2 as empty + + +def delete_channel(project_id: str, location: str, channel_id: str) -> empty.Empty: + """Deletes a channel. + Args: + project_id: The GCP project ID. + location: The location of the channel. + channel_id: The user-defined channel ID.""" + + client = LivestreamServiceClient() + + name = f"projects/{project_id}/locations/{location}/channels/{channel_id}" + operation = client.delete_channel(name=name) + response = operation.result(600) + print("Deleted channel") + + return response + + +# [END livestream_delete_channel] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the channel.", + required=True, + ) + parser.add_argument( + "--channel_id", + help="The user-defined channel ID.", + required=True, + ) + args = parser.parse_args() + delete_channel( + args.project_id, + args.location, + args.channel_id, + ) diff --git a/video/live-stream/delete_channel_event.py b/video/live-stream/delete_channel_event.py new file mode 100644 index 000000000000..f8f920480318 --- /dev/null +++ b/video/live-stream/delete_channel_event.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for deleting a channel event. +Example usage: + python delete_channel_event.py --project_id --location \ + --channel_id --event_id +""" + +# [START livestream_delete_channel_event] + +import argparse + +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) + + +def delete_channel_event( + project_id: str, location: str, channel_id: str, event_id: str +) -> None: + """Deletes a channel event. + Args: + project_id: The GCP project ID. + location: The location of the channel. + channel_id: The user-defined channel ID. + event_id: The user-defined event ID.""" + + client = LivestreamServiceClient() + + name = f"projects/{project_id}/locations/{location}/channels/{channel_id}/events/{event_id}" + response = client.delete_event(name=name) + print("Deleted channel event") + + return response + + +# [END livestream_delete_channel_event] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the channel.", + required=True, + ) + parser.add_argument( + "--channel_id", + help="The user-defined channel ID.", + required=True, + ) + parser.add_argument( + "--event_id", + help="The user-defined event ID.", + required=True, + ) + args = parser.parse_args() + delete_channel_event( + args.project_id, + args.location, + args.channel_id, + args.event_id, + ) diff --git a/video/live-stream/delete_input.py b/video/live-stream/delete_input.py new file mode 100644 index 000000000000..c2751196c842 --- /dev/null +++ b/video/live-stream/delete_input.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for deleting an input. +Example usage: + python delete_input.py --project_id --location --input_id +""" + +# [START livestream_delete_input] + +import argparse + +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) +from google.protobuf import empty_pb2 as empty + + +def delete_input(project_id: str, location: str, input_id: str) -> empty.Empty: + """Deletes an input. + Args: + project_id: The GCP project ID. + location: The location of the input. + input_id: The user-defined input ID.""" + + client = LivestreamServiceClient() + + name = f"projects/{project_id}/locations/{location}/inputs/{input_id}" + operation = client.delete_input(name=name) + response = operation.result(600) + print("Deleted input") + + return response + + +# [END livestream_delete_input] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the input.", + required=True, + ) + parser.add_argument( + "--input_id", + help="The user-defined input ID.", + required=True, + ) + args = parser.parse_args() + delete_input( + args.project_id, + args.location, + args.input_id, + ) diff --git a/video/live-stream/get_asset.py b/video/live-stream/get_asset.py new file mode 100644 index 000000000000..a2606eb238eb --- /dev/null +++ b/video/live-stream/get_asset.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for getting an asset. +Example usage: + python get_asset.py --project_id --location --asset_id +""" + +# [START livestream_get_asset] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) + + +def get_asset( + project_id: str, location: str, asset_id: str +) -> live_stream_v1.types.Asset: + """Gets an asset. + Args: + project_id: The GCP project ID. + location: The location of the asset. + asset_id: The user-defined asset ID.""" + + client = LivestreamServiceClient() + + name = f"projects/{project_id}/locations/{location}/assets/{asset_id}" + response = client.get_asset(name=name) + print(f"Asset: {response.name}") + + return response + + +# [END livestream_get_asset] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the asset.", + required=True, + ) + parser.add_argument( + "--asset_id", + help="The user-defined asset ID.", + required=True, + ) + args = parser.parse_args() + get_asset( + args.project_id, + args.location, + args.asset_id, + ) diff --git a/video/live-stream/get_channel.py b/video/live-stream/get_channel.py new file mode 100644 index 000000000000..03ee0d19188f --- /dev/null +++ b/video/live-stream/get_channel.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for getting a channel. +Example usage: + python get_channel.py --project_id --location --channel_id +""" + +# [START livestream_get_channel] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) + + +def get_channel( + project_id: str, location: str, channel_id: str +) -> live_stream_v1.types.Channel: + """Gets a channel. + Args: + project_id: The GCP project ID. + location: The location of the channel. + channel_id: The user-defined channel ID.""" + + client = LivestreamServiceClient() + + name = f"projects/{project_id}/locations/{location}/channels/{channel_id}" + response = client.get_channel(name=name) + print(f"Channel: {response.name}") + + return response + + +# [END livestream_get_channel] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the channel.", + required=True, + ) + parser.add_argument( + "--channel_id", + help="The user-defined channel ID.", + required=True, + ) + args = parser.parse_args() + get_channel( + args.project_id, + args.location, + args.channel_id, + ) diff --git a/video/live-stream/get_channel_event.py b/video/live-stream/get_channel_event.py new file mode 100644 index 000000000000..81d01844dd1b --- /dev/null +++ b/video/live-stream/get_channel_event.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for getting a channel event. +Example usage: + python get_channel.py --project_id --location \ + --channel_id --event_id +""" + +# [START livestream_get_channel_event] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) + + +def get_channel_event( + project_id: str, location: str, channel_id: str, event_id: str +) -> live_stream_v1.types.Event: + """Gets a channel. + Args: + project_id: The GCP project ID. + location: The location of the channel. + channel_id: The user-defined channel ID. + event_id: The user-defined event ID.""" + + client = LivestreamServiceClient() + + name = f"projects/{project_id}/locations/{location}/channels/{channel_id}/events/{event_id}" + response = client.get_event(name=name) + print(f"Channel event: {response.name}") + + return response + + +# [END livestream_get_channel_event] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the channel.", + required=True, + ) + parser.add_argument( + "--channel_id", + help="The user-defined channel ID.", + required=True, + ) + parser.add_argument( + "--event_id", + help="The user-defined event ID.", + required=True, + ) + args = parser.parse_args() + get_channel_event( + args.project_id, + args.location, + args.channel_id, + args.event_id, + ) diff --git a/video/live-stream/get_input.py b/video/live-stream/get_input.py new file mode 100644 index 000000000000..bc4e2d4b468c --- /dev/null +++ b/video/live-stream/get_input.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for getting an input. +Example usage: + python get_input.py --project_id --location --input_id +""" + +# [START livestream_get_input] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) + + +def get_input( + project_id: str, location: str, input_id: str +) -> live_stream_v1.types.Input: + """Gets an input. + Args: + project_id: The GCP project ID. + location: The location of the input. + input_id: The user-defined input ID.""" + + client = LivestreamServiceClient() + + name = f"projects/{project_id}/locations/{location}/inputs/{input_id}" + response = client.get_input(name=name) + print(f"Input: {response.name}") + + return response + + +# [END livestream_get_input] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the input.", + required=True, + ) + parser.add_argument( + "--input_id", + help="The user-defined input ID.", + required=True, + ) + args = parser.parse_args() + get_input( + args.project_id, + args.location, + args.input_id, + ) diff --git a/video/live-stream/get_pool.py b/video/live-stream/get_pool.py new file mode 100644 index 000000000000..8b48e7c1302f --- /dev/null +++ b/video/live-stream/get_pool.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for getting a pool. +Example usage: + python get_pool.py --project_id --location --pool_id +""" + +# [START livestream_get_pool] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) + + +def get_pool(project_id: str, location: str, pool_id: str) -> live_stream_v1.types.Pool: + """Gets a pool. + Args: + project_id: The GCP project ID. + location: The location of the pool. + pool_id: The user-defined pool ID.""" + + client = LivestreamServiceClient() + + name = f"projects/{project_id}/locations/{location}/pools/{pool_id}" + response = client.get_pool(name=name) + print(f"Pool: {response.name}") + + return response + + +# [END livestream_get_pool] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the pool.", + required=True, + ) + parser.add_argument( + "--pool_id", + help="The user-defined pool ID.", + required=True, + ) + args = parser.parse_args() + get_pool( + args.project_id, + args.location, + args.pool_id, + ) diff --git a/video/live-stream/input_test.py b/video/live-stream/input_test.py new file mode 100644 index 000000000000..585c3dc7bfae --- /dev/null +++ b/video/live-stream/input_test.py @@ -0,0 +1,68 @@ +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import uuid + +from google.api_core.exceptions import FailedPrecondition, NotFound +from google.protobuf import empty_pb2 as empty +import pytest + +import create_input +import delete_input +import get_input +import list_inputs +import update_input +import utils + +project_name = os.environ["GOOGLE_CLOUD_PROJECT"] +location = "us-central1" +input_id = f"my-python-test-input-{uuid.uuid4()}" + + +def test_input_operations(capsys: pytest.fixture) -> None: + # Clean up old resources in the test project + responses = list_inputs.list_inputs(project_name, location) + for response in responses: + next_input_id = response.name.rsplit("/", 1)[-1] + if utils.is_resource_stale(response.create_time): + try: + delete_input.delete_input(project_name, location, next_input_id) + except FailedPrecondition as e: + print(f"Ignoring FailedPrecondition, details: {e}") + except NotFound as e: + print(f"Ignoring NotFound, details: {e}") + + input_name_project_id = ( + f"projects/{project_name}/locations/{location}/inputs/{input_id}" + ) + + # Tests + + response = create_input.create_input(project_name, location, input_id) + assert input_name_project_id in response.name + + list_inputs.list_inputs(project_name, location) + out, _ = capsys.readouterr() + assert input_name_project_id in out + + response = update_input.update_input(project_name, location, input_id) + assert input_name_project_id in response.name + assert response.preprocessing_config.crop.top_pixels == 5 + + response = get_input.get_input(project_name, location, input_id) + assert input_name_project_id in response.name + + response = delete_input.delete_input(project_name, location, input_id) + assert response == empty.Empty() diff --git a/video/live-stream/list_assets.py b/video/live-stream/list_assets.py new file mode 100644 index 000000000000..6e5bb4731c95 --- /dev/null +++ b/video/live-stream/list_assets.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for listing all assets in a location. +Example usage: + python list_assets.py --project_id --location +""" + +# [START livestream_list_assets] + +import argparse + +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, + pagers, +) + + +def list_assets(project_id: str, location: str) -> pagers.ListAssetsPager: + """Lists all assets in a location. + Args: + project_id: The GCP project ID. + location: The location of the assets.""" + + client = LivestreamServiceClient() + + parent = f"projects/{project_id}/locations/{location}" + page_result = client.list_assets(parent=parent) + print("Assets:") + + responses = [] + for response in page_result: + print(response.name) + responses.append(response) + + return responses + + +# [END livestream_list_assets] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the assets.", + required=True, + ) + args = parser.parse_args() + list_assets( + args.project_id, + args.location, + ) diff --git a/video/live-stream/list_channel_events.py b/video/live-stream/list_channel_events.py new file mode 100644 index 000000000000..cd3c96d6d47f --- /dev/null +++ b/video/live-stream/list_channel_events.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for listing all events for a channel. +Example usage: + python list_channel_events.py --project_id --location --channel_id +""" + +# [START livestream_list_channel_events] + +import argparse + +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, + pagers, +) + + +def list_channel_events( + project_id: str, location: str, channel_id: str +) -> pagers.ListEventsPager: + """Lists all events for a channel. + Args: + project_id: The GCP project ID. + location: The location of the channel. + channel_id: The user-defined channel ID.""" + + client = LivestreamServiceClient() + + parent = f"projects/{project_id}/locations/{location}/channels/{channel_id}" + page_result = client.list_events(parent=parent) + print("Events:") + + responses = [] + for response in page_result: + print(response.name) + responses.append(response) + + return responses + + +# [END livestream_list_channel_events] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the channel.", + required=True, + ) + parser.add_argument( + "--channel_id", + help="The user-defined channel ID.", + required=True, + ) + args = parser.parse_args() + list_channel_events( + args.project_id, + args.location, + args.channel_id, + ) diff --git a/video/live-stream/list_channels.py b/video/live-stream/list_channels.py new file mode 100644 index 000000000000..d31969c830eb --- /dev/null +++ b/video/live-stream/list_channels.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for listing all channels in a location. +Example usage: + python list_channels.py --project_id --location +""" + +# [START livestream_list_channels] + +import argparse + +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, + pagers, +) + + +def list_channels(project_id: str, location: str) -> pagers.ListChannelsPager: + """Lists all channels in a location. + Args: + project_id: The GCP project ID. + location: The location of the channels.""" + + client = LivestreamServiceClient() + + parent = f"projects/{project_id}/locations/{location}" + page_result = client.list_channels(parent=parent) + print("Channels:") + + responses = [] + for response in page_result: + print(response.name) + responses.append(response) + + return responses + + +# [END livestream_list_channels] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the channels.", + required=True, + ) + args = parser.parse_args() + list_channels( + args.project_id, + args.location, + ) diff --git a/video/live-stream/list_inputs.py b/video/live-stream/list_inputs.py new file mode 100644 index 000000000000..f72da76a2bde --- /dev/null +++ b/video/live-stream/list_inputs.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for listing all inputs in a location. +Example usage: + python list_inputs.py --project_id --location +""" + +# [START livestream_list_inputs] + +import argparse + +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, + pagers, +) + + +def list_inputs(project_id: str, location: str) -> pagers.ListInputsPager: + """Lists all inputs in a location. + Args: + project_id: The GCP project ID. + location: The location of the inputs.""" + + client = LivestreamServiceClient() + + parent = f"projects/{project_id}/locations/{location}" + page_result = client.list_inputs(parent=parent) + print("Inputs:") + + responses = [] + for response in page_result: + print(response.name) + responses.append(response) + + return responses + + +# [END livestream_list_inputs] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the inputs.", + required=True, + ) + args = parser.parse_args() + list_inputs( + args.project_id, + args.location, + ) diff --git a/video/live-stream/noxfile_config.py b/video/live-stream/noxfile_config.py new file mode 100644 index 000000000000..7b5773a22a10 --- /dev/null +++ b/video/live-stream/noxfile_config.py @@ -0,0 +1,42 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Default TEST_CONFIG_OVERRIDE for python repos. + +# You can copy this file into your directory, then it will be imported from +# the noxfile.py. + +# The source of truth: +# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py + +TEST_CONFIG_OVERRIDE = { + # You can opt out from the test for specific Python versions. + "ignored_versions": ["2.7", "3.7", "3.9", "3.10"], + # Old samples are opted out of enforcing Python type hints + # All new samples should feature them + "enforce_type_hints": True, + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + # If you need to use a specific version of pip, + # change pip_version_override to the string representation + # of the version number, for example, "20.2.4" + "pip_version_override": None, + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + "envs": {}, +} diff --git a/video/live-stream/pool_test.py b/video/live-stream/pool_test.py new file mode 100644 index 000000000000..8b157a155034 --- /dev/null +++ b/video/live-stream/pool_test.py @@ -0,0 +1,41 @@ +# Copyright 2023 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import pytest + +import get_pool + +# import update_pool + +project_name = os.environ["GOOGLE_CLOUD_PROJECT"] +location = "us-central1" +pool_id = "default" # only 1 pool supported per location +peered_network = "" + + +def test_pool_operations(capsys: pytest.fixture) -> None: + pool_name_project_id = ( + f"projects/{project_name}/locations/{location}/pools/{pool_id}" + ) + + # All channels must be stopped to update the pool. Pool operations take a + # long time to complete, so don't run this test on the test network. + # response = update_pool.update_pool(project_name, location, pool_id, peered_network) + # assert pool_name_project_id in response.name + # assert response.network_config.peered_network == peered_network + + response = get_pool.get_pool(project_name, location, pool_id) + assert pool_name_project_id in response.name diff --git a/video/live-stream/requirements-test.txt b/video/live-stream/requirements-test.txt new file mode 100644 index 000000000000..6950eb5a7b6a --- /dev/null +++ b/video/live-stream/requirements-test.txt @@ -0,0 +1 @@ +pytest==7.4.0 \ No newline at end of file diff --git a/video/live-stream/requirements.txt b/video/live-stream/requirements.txt new file mode 100644 index 000000000000..9f75faad35ce --- /dev/null +++ b/video/live-stream/requirements.txt @@ -0,0 +1,2 @@ +grpcio==1.57.0 +google-cloud-video-live-stream==1.5.1 diff --git a/video/live-stream/start_channel.py b/video/live-stream/start_channel.py new file mode 100644 index 000000000000..afcd385b4849 --- /dev/null +++ b/video/live-stream/start_channel.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for starting a channel. +Example usage: + python start_channel.py --project_id --location --channel_id +""" + +# [START livestream_start_channel] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) + + +def start_channel( + project_id: str, location: str, channel_id: str +) -> live_stream_v1.types.ChannelOperationResponse: + """Starts a channel. + Args: + project_id: The GCP project ID. + location: The location of the channel. + channel_id: The user-defined channel ID.""" + + client = LivestreamServiceClient() + + name = f"projects/{project_id}/locations/{location}/channels/{channel_id}" + operation = client.start_channel(name=name) + response = operation.result(900) + print("Started channel") + + return response + + +# [END livestream_start_channel] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the channel.", + required=True, + ) + parser.add_argument( + "--channel_id", + help="The user-defined channel ID.", + required=True, + ) + args = parser.parse_args() + start_channel( + args.project_id, + args.location, + args.channel_id, + ) diff --git a/video/live-stream/stop_channel.py b/video/live-stream/stop_channel.py new file mode 100644 index 000000000000..41f641672aa7 --- /dev/null +++ b/video/live-stream/stop_channel.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for stopping a channel. +Example usage: + python stop_channel.py --project_id --location --channel_id +""" + +# [START livestream_stop_channel] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) + + +def stop_channel( + project_id: str, location: str, channel_id: str +) -> live_stream_v1.types.ChannelOperationResponse: + """Stops a channel. + Args: + project_id: The GCP project ID. + location: The location of the channel. + channel_id: The user-defined channel ID.""" + + client = LivestreamServiceClient() + + name = f"projects/{project_id}/locations/{location}/channels/{channel_id}" + operation = client.stop_channel(name=name) + response = operation.result(600) + print("Stopped channel") + + return response + + +# [END livestream_stop_channel] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the channel.", + required=True, + ) + parser.add_argument( + "--channel_id", + help="The user-defined channel ID.", + required=True, + ) + args = parser.parse_args() + stop_channel( + args.project_id, + args.location, + args.channel_id, + ) diff --git a/video/live-stream/update_channel.py b/video/live-stream/update_channel.py new file mode 100644 index 000000000000..6bc606005644 --- /dev/null +++ b/video/live-stream/update_channel.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for updating a channel with a different input. +Example usage: + python update_channel.py --project_id --location \ + --channel_id --input_id +""" + +# [START livestream_update_channel] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) +from google.protobuf import field_mask_pb2 as field_mask + + +def update_channel( + project_id: str, location: str, channel_id: str, input_id: str +) -> live_stream_v1.types.Channel: + """Updates a channel. + Args: + project_id: The GCP project ID. + location: The location of the channel. + channel_id: The user-defined channel ID. + input_id: The user-defined input ID for the new input.""" + + client = LivestreamServiceClient() + input = f"projects/{project_id}/locations/{location}/inputs/{input_id}" + name = f"projects/{project_id}/locations/{location}/channels/{channel_id}" + + channel = live_stream_v1.types.Channel( + name=name, + input_attachments=[ + live_stream_v1.types.InputAttachment( + key="updated-input", + input=input, + ), + ], + ) + update_mask = field_mask.FieldMask(paths=["input_attachments"]) + + operation = client.update_channel(channel=channel, update_mask=update_mask) + response = operation.result(600) + print(f"Updated channel: {response.name}") + + return response + + +# [END livestream_update_channel] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location in of the channel.", + required=True, + ) + parser.add_argument( + "--channel_id", + help="The user-defined channel ID.", + required=True, + ) + parser.add_argument( + "--input_id", + help="The user-defined input ID.", + required=True, + ) + args = parser.parse_args() + update_channel( + args.project_id, + args.location, + args.channel_id, + args.input_id, + ) diff --git a/video/live-stream/update_input.py b/video/live-stream/update_input.py new file mode 100644 index 000000000000..f270d5fe5972 --- /dev/null +++ b/video/live-stream/update_input.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for updating an input endpoint. This sample + adds a preprocessing configuration to an existing input. +Example usage: + python update_input.py --project_id --location --input_id +""" + +# [START livestream_update_input] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) +from google.protobuf import field_mask_pb2 as field_mask + + +def update_input( + project_id: str, location: str, input_id: str +) -> live_stream_v1.types.Input: + """Updates an input. + Args: + project_id: The GCP project ID. + location: The location of the input. + input_id: The user-defined input ID.""" + + client = LivestreamServiceClient() + + name = f"projects/{project_id}/locations/{location}/inputs/{input_id}" + + input = live_stream_v1.types.Input( + name=name, + preprocessing_config=live_stream_v1.types.PreprocessingConfig( + crop=live_stream_v1.types.PreprocessingConfig.Crop( + top_pixels=5, + bottom_pixels=5, + ) + ), + ) + update_mask = field_mask.FieldMask(paths=["preprocessing_config"]) + + operation = client.update_input(input=input, update_mask=update_mask) + response = operation.result(600) + print(f"Updated input: {response.name}") + + return response + + +# [END livestream_update_input] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the input.", + required=True, + ) + parser.add_argument( + "--input_id", + help="The user-defined input ID.", + required=True, + ) + args = parser.parse_args() + update_input( + args.project_id, + args.location, + args.input_id, + ) diff --git a/video/live-stream/update_pool.py b/video/live-stream/update_pool.py new file mode 100644 index 000000000000..6ed9a4ce3cbd --- /dev/null +++ b/video/live-stream/update_pool.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +# Copyright 2023 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Live Stream sample for updating a pool's peered network. +Example usage: + python update_pool.py --project_id --location \ + --pool_id --peered_network +""" + +# [START livestream_update_pool] + +import argparse + +from google.cloud.video import live_stream_v1 +from google.cloud.video.live_stream_v1.services.livestream_service import ( + LivestreamServiceClient, +) +from google.protobuf import field_mask_pb2 as field_mask + + +def update_pool( + project_id: str, location: str, pool_id: str, peered_network: str +) -> live_stream_v1.types.Pool: + """Updates an pool. + Args: + project_id: The GCP project ID. + location: The location of the pool. + pool_id: The user-defined pool ID. + peered_network: The updated peer network (e.g., + 'projects/my-network-project-number/global/networks/my-network-name').""" + + client = LivestreamServiceClient() + + name = f"projects/{project_id}/locations/{location}/pools/{pool_id}" + + pool = live_stream_v1.types.Pool( + name=name, + network_config=live_stream_v1.types.Pool.NetworkConfig( + peered_network=peered_network, + ), + ) + update_mask = field_mask.FieldMask(paths=["network_config"]) + + operation = client.update_pool(pool=pool, update_mask=update_mask) + response = operation.result() + print(f"Updated pool: {response.name}") + + return response + + +# [END livestream_update_pool] + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--project_id", help="Your Cloud project ID.", required=True) + parser.add_argument( + "--location", + help="The location of the pool.", + required=True, + ) + parser.add_argument( + "--pool_id", + help="The user-defined pool ID.", + required=True, + ) + parser.add_argument( + "--peered_network", + help="The updated peer network.", + required=True, + ) + args = parser.parse_args() + update_pool( + args.project_id, + args.location, + args.pool_id, + args.peered_network, + ) diff --git a/video/live-stream/utils.py b/video/live-stream/utils.py new file mode 100644 index 000000000000..30d34f3c1612 --- /dev/null +++ b/video/live-stream/utils.py @@ -0,0 +1,31 @@ +# Copyright 2023 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.protobuf import timestamp_pb2 + +seconds_per_hour = 3600 + + +def is_resource_stale(create_time: str) -> bool: + """Checks the create timestamp to see if the resource is stale (and should be deleted). + Args: + create_time: Creation time in Timestamp format.""" + timestamp = timestamp_pb2.Timestamp() + timestamp.FromDatetime(create_time) + now = timestamp_pb2.Timestamp() + now.GetCurrentTime() + if (now.seconds - timestamp.seconds) > (3 * seconds_per_hour): + return True + else: + return False