diff --git a/video/stitcher/create_vod_config.py b/video/stitcher/create_vod_config.py new file mode 100644 index 00000000000..2f2b37e62de --- /dev/null +++ b/video/stitcher/create_vod_config.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +# Copyright 2024 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. + +"""Google Cloud Video Stitcher sample for creating a VOD config. VOD +configs are used to configure VOD sessions. +Example usage: + python create_vod_config.py --project_id --location \ + --vod_config_id --vod_uri --ad_tag_uri +""" + +# [START videostitcher_create_vod_config] + +import argparse + +from google.cloud.video import stitcher_v1 +from google.cloud.video.stitcher_v1.services.video_stitcher_service import ( + VideoStitcherServiceClient, +) + + +def create_vod_config( + project_id: str, + location: str, + vod_config_id: str, + vod_uri: str, + ad_tag_uri: str, +) -> stitcher_v1.types.VodConfig: + """Creates a VOD config. + Args: + project_id: The GCP project ID. + location: The location in which to create the VOD config. + vod_config_id: The user-defined VOD config ID. + vod_uri: URI of the VOD to stitch; this URI must reference either an + MPEG-DASH manifest (.mpd) file or an M3U playlist manifest + (.m3u8) file. + ad_tag_uri: Uri of the ad tag. + + Returns: + The VOD config resource. + """ + + client = VideoStitcherServiceClient() + + parent = f"projects/{project_id}/locations/{location}" + + vod_config = stitcher_v1.types.VodConfig( + source_uri=vod_uri, + ad_tag_uri=ad_tag_uri, + ) + + operation = client.create_vod_config( + parent=parent, vod_config_id=vod_config_id, vod_config=vod_config + ) + response = operation.result() + print(f"VOD config: {response.name}") + return response + + +# [END videostitcher_create_vod_config] + +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 VOD config.", + default="us-central1", + ) + parser.add_argument( + "--vod_config_id", + help="The user-defined VOD config ID.", + required=True, + ) + parser.add_argument( + "--vod_uri", + help="The URI of the VOD stream to stitch (.mpd or .m3u8 file) in double quotes.", + required=True, + ) + parser.add_argument( + "--ad_tag_uri", + help="URI of the ad tag in double quotes.", + required=True, + ) + args = parser.parse_args() + create_vod_config( + args.project_id, + args.location, + args.vod_config_id, + args.vod_uri, + args.ad_tag_uri, + ) diff --git a/video/stitcher/create_vod_session.py b/video/stitcher/create_vod_session.py index ae9ac97dc4c..047d0017412 100644 --- a/video/stitcher/create_vod_session.py +++ b/video/stitcher/create_vod_session.py @@ -18,7 +18,7 @@ session in which to insert ads. Example usage: python create_vod_session.py --project_id \ - --location --source_uri --ad_tag_uri + --location --vod_config_id """ # [START videostitcher_create_vod_session] @@ -32,16 +32,15 @@ def create_vod_session( - project_id: str, location: str, source_uri: str, ad_tag_uri: str + project_id: str, location: str, vod_config_id: str ) -> stitcher_v1.types.VodSession: """Creates a VOD session. VOD sessions are ephemeral resources that expire after a few hours. Args: project_id: The GCP project ID. location: The location in which to create the session. - source_uri: Uri of the media to stitch; this URI must reference either an MPEG-DASH - manifest (.mpd) file or an M3U playlist manifest (.m3u8) file. - ad_tag_uri: Uri of the ad tag. + vod_config_id: The user-defined VOD config ID to use to create the + session. Returns: The VOD session resource. @@ -50,9 +49,12 @@ def create_vod_session( client = VideoStitcherServiceClient() parent = f"projects/{project_id}/locations/{location}" + vod_config_name = ( + f"projects/{project_id}/locations/{location}/vodConfigs/{vod_config_id}" + ) vod_session = stitcher_v1.types.VodSession( - source_uri=source_uri, ad_tag_uri=ad_tag_uri, ad_tracking="SERVER" + vod_config=vod_config_name, ad_tracking="SERVER" ) response = client.create_vod_session(parent=parent, vod_session=vod_session) @@ -71,19 +73,13 @@ def create_vod_session( default="us-central1", ) parser.add_argument( - "--source_uri", - help="The Uri of the media to stitch (.mpd or .m3u8 file) in double quotes.", - required=True, - ) - parser.add_argument( - "--ad_tag_uri", - help="Uri of the ad tag in double quotes.", + "--vod_config_id", + help="The user-defined VOD config ID.", required=True, ) args = parser.parse_args() create_vod_session( args.project_id, args.location, - args.source_uri, - args.ad_tag_uri, + args.vod_config_id, ) diff --git a/video/stitcher/delete_vod_config.py b/video/stitcher/delete_vod_config.py new file mode 100644 index 00000000000..469958c95cf --- /dev/null +++ b/video/stitcher/delete_vod_config.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +# Copyright 2024 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. + +"""Google Cloud Video Stitcher sample for deleting a VOD config. +Example usage: + python delete_vod_config.py --project_id --location \ + --vod_config_id +""" + +# [START videostitcher_delete_vod_config] + +import argparse + +from google.cloud.video.stitcher_v1.services.video_stitcher_service import ( + VideoStitcherServiceClient, +) +from google.protobuf import empty_pb2 as empty + + +def delete_vod_config( + project_id: str, location: str, vod_config_id: str +) -> empty.Empty: + """Deletes a VOD config. + Args: + project_id: The GCP project ID. + location: The location of the VOD config. + vod_config_id: The user-defined VOD config ID.""" + + client = VideoStitcherServiceClient() + + name = f"projects/{project_id}/locations/{location}/vodConfigs/{vod_config_id}" + operation = client.delete_vod_config(name=name) + response = operation.result() + print("Deleted VOD config") + return response + + +# [END videostitcher_delete_vod_config] + +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 VOD config.", + required=True, + ) + parser.add_argument( + "--vod_config_id", + help="The user-defined VOD config ID.", + required=True, + ) + args = parser.parse_args() + delete_vod_config( + args.project_id, + args.location, + args.vod_config_id, + ) diff --git a/video/stitcher/get_vod_config.py b/video/stitcher/get_vod_config.py new file mode 100644 index 00000000000..e9e953db3c6 --- /dev/null +++ b/video/stitcher/get_vod_config.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python + +# Copyright 2024 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. + +"""Google Cloud Video Stitcher sample for getting a VOD config. +Example usage: + python get_vod_config.py --project_id --location \ + --vod_config_id +""" + +# [START videostitcher_get_vod_config] + +import argparse + +from google.cloud.video import stitcher_v1 +from google.cloud.video.stitcher_v1.services.video_stitcher_service import ( + VideoStitcherServiceClient, +) + + +def get_vod_config( + project_id: str, location: str, vod_config_id: str +) -> stitcher_v1.types.VodConfig: + """Gets a VOD config. + Args: + project_id: The GCP project ID. + location: The location of the VOD config. + vod_config_id: The user-defined VOD config ID. + + Returns: + The VOD config resource. + """ + + client = VideoStitcherServiceClient() + + name = f"projects/{project_id}/locations/{location}/vodConfigs/{vod_config_id}" + response = client.get_vod_config(name=name) + print(f"VOD config: {response.name}") + return response + + +# [END videostitcher_get_vod_config] + +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 VOD config.", + required=True, + ) + parser.add_argument( + "--vod_config_id", + help="The user-defined VOD config ID.", + required=True, + ) + args = parser.parse_args() + get_vod_config( + args.project_id, + args.location, + args.vod_config_id, + ) diff --git a/video/stitcher/list_vod_configs.py b/video/stitcher/list_vod_configs.py new file mode 100644 index 00000000000..8d0b4baac5f --- /dev/null +++ b/video/stitcher/list_vod_configs.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +# Copyright 2024 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. + +"""Google Cloud Video Stitcher sample for listing all VOD configs in a location. +Example usage: + python list_vod_configs.py --project_id --location +""" + +# [START videostitcher_list_vod_configs] + +import argparse + +from google.cloud.video.stitcher_v1.services.video_stitcher_service import ( + pagers, + VideoStitcherServiceClient, +) + + +def list_vod_configs(project_id: str, location: str) -> pagers.ListVodConfigsPager: + """Lists all VOD configs in a location. + Args: + project_id: The GCP project ID. + location: The location of the VOD configs. + + Returns: + An iterable object containing VOD config resources. + """ + + client = VideoStitcherServiceClient() + + parent = f"projects/{project_id}/locations/{location}" + response = client.list_vod_configs(parent=parent) + print("VOD configs:") + for vod_config in response.vod_configs: + print({vod_config.name}) + + return response + + +# [END videostitcher_list_vod_configs] + +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 VOD configs.", + required=True, + ) + args = parser.parse_args() + list_vod_configs( + args.project_id, + args.location, + ) diff --git a/video/stitcher/requirements.txt b/video/stitcher/requirements.txt index e3e318614b5..93acfa7b7b8 100644 --- a/video/stitcher/requirements.txt +++ b/video/stitcher/requirements.txt @@ -1,3 +1,3 @@ google-api-python-client==2.131.0 grpcio==1.62.1 -google-cloud-video-stitcher==0.7.9 +google-cloud-video-stitcher==0.7.10 diff --git a/video/stitcher/update_vod_config.py b/video/stitcher/update_vod_config.py new file mode 100644 index 00000000000..5065141c8cd --- /dev/null +++ b/video/stitcher/update_vod_config.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python + +# Copyright 2024 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. + +"""Google Cloud Video Stitcher sample for updating a VOD config. +Example usage: + python update_vod_config.py --project_id --location \ + --vod_config_id --vod_uri +""" + +# [START videostitcher_update_vod_config] + +import argparse + +from google.cloud.video import stitcher_v1 +from google.cloud.video.stitcher_v1.services.video_stitcher_service import ( + VideoStitcherServiceClient, +) +from google.protobuf import field_mask_pb2 as field_mask + + +def update_vod_config( + project_id: str, location: str, vod_config_id: str, vod_uri: str +) -> stitcher_v1.types.VodConfig: + """Updates a VOD config. + Args: + project_id: The GCP project ID. + location: The location of the VOD config. + vod_config_id: The existing VOD config's ID. + vod_uri: Updated URI of the VOD to stitch; this URI must reference + either an MPEG-DASH manifest (.mpd) file or an M3U playlist + manifest (.m3u8) file. + + Returns: + The VOD config resource. + """ + + client = VideoStitcherServiceClient() + + name = f"projects/{project_id}/locations/{location}/vodConfigs/{vod_config_id}" + vod_config = stitcher_v1.types.VodConfig( + name=name, + source_uri=vod_uri, + ) + update_mask = field_mask.FieldMask(paths=["sourceUri"]) + + operation = client.update_vod_config(vod_config=vod_config, update_mask=update_mask) + response = operation.result() + print(f"Updated VOD config: {response.name}") + return response + + +# [END videostitcher_update_vod_config] + +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 VOD config.", + required=True, + ) + parser.add_argument( + "--vod_config_id", + help="The existing VOD config's ID.", + required=True, + ) + parser.add_argument( + "--vod_uri", + help="The updated URI of the VOD stream to stitch (.mpd or .m3u8 file) in double quotes.", + required=True, + ) + args = parser.parse_args() + update_vod_config( + args.project_id, + args.location, + args.vod_config_id, + args.vod_uri, + ) diff --git a/video/stitcher/utils.py b/video/stitcher/utils.py index 28537306262..4f697a5af2a 100644 --- a/video/stitcher/utils.py +++ b/video/stitcher/utils.py @@ -90,3 +90,27 @@ def delete_stale_live_configs(project_id: str, location: str) -> None: continue if (now.seconds - creation_time_sec) > (3 * seconds_per_hour): response = client.delete_live_config(name=live_config.name) + + +def delete_stale_vod_configs(project_id: str, location: str) -> None: + """Lists all outdated VOD configs in a location. + Args: + project_id: The GCP project ID. + location: The location of the VOD configs.""" + + client = VideoStitcherServiceClient() + + parent = f"projects/{project_id}/locations/{location}" + response = client.list_vod_configs(parent=parent) + + now = timestamp_pb2.Timestamp() + now.GetCurrentTime() + + for vod_config in response.vod_configs: + tmp = vod_config.name.split("-") + try: + creation_time_sec = int(tmp.pop()) + except ValueError: + continue + if (now.seconds - creation_time_sec) > (3 * seconds_per_hour): + response = client.delete_vod_config(name=vod_config.name) diff --git a/video/stitcher/vod_config_test.py b/video/stitcher/vod_config_test.py new file mode 100644 index 00000000000..f3480a704f6 --- /dev/null +++ b/video/stitcher/vod_config_test.py @@ -0,0 +1,76 @@ +# Copyright 2024 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. + +import os +import uuid + +from google.protobuf import empty_pb2 as empty +from google.protobuf import timestamp_pb2 +import pytest + +import create_vod_config +import delete_vod_config +import get_vod_config +import list_vod_configs +import update_vod_config +import utils + +project_id = os.environ["GOOGLE_CLOUD_PROJECT"] +location = "us-central1" +now = timestamp_pb2.Timestamp() +now.GetCurrentTime() + +vod_config_id = f"python-test-vod-config-{uuid.uuid4().hex[:5]}-{now.seconds}" + +input_bucket_name = "cloud-samples-data/media/" +input_video_file_name = "hls-vod/manifest.m3u8" +updated_input_video_file_name = "hls-vod/manifest.mpd" +vod_uri = f"https://storage.googleapis.com/{input_bucket_name}{input_video_file_name}" +updated_vod_uri = ( + f"https://storage.googleapis.com/{input_bucket_name}{updated_input_video_file_name}" +) +# VMAP Pre-roll (https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/tags) +ad_tag_uri = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpreonly&ciu_szs=300x250%2C728x90&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&correlator=" + + +def test_vod_config_operations(capsys: pytest.fixture) -> None: + utils.delete_stale_vod_configs(project_id, location) + + # Test setup + + vod_config_name = ( + f"projects/{project_id}/locations/{location}/vodConfigs/{vod_config_id}" + ) + + # Tests + + response = create_vod_config.create_vod_config( + project_id, location, vod_config_id, vod_uri, ad_tag_uri + ) + assert vod_config_name in response.name + + list_vod_configs.list_vod_configs(project_id, location) + out, _ = capsys.readouterr() + assert vod_config_name in out + + response = update_vod_config.update_vod_config( + project_id, location, vod_config_id, updated_vod_uri + ) + assert vod_config_name in response.name + + response = get_vod_config.get_vod_config(project_id, location, vod_config_id) + assert vod_config_name in response.name + + response = delete_vod_config.delete_vod_config(project_id, location, vod_config_id) + assert response == empty.Empty() diff --git a/video/stitcher/vod_session_test.py b/video/stitcher/vod_session_test.py index 553a75713a9..6bbd712e078 100644 --- a/video/stitcher/vod_session_test.py +++ b/video/stitcher/vod_session_test.py @@ -13,10 +13,15 @@ # limitations under the License. import os +import uuid +from google.protobuf import empty_pb2 as empty +from google.protobuf import timestamp_pb2 import pytest +import create_vod_config import create_vod_session +import delete_vod_config import get_vod_ad_tag_detail import get_vod_session import get_vod_stitch_detail @@ -26,6 +31,11 @@ project_id = os.environ["GOOGLE_CLOUD_PROJECT"] project_number = os.environ["GOOGLE_CLOUD_PROJECT_NUMBER"] location = "us-central1" +now = timestamp_pb2.Timestamp() +now.GetCurrentTime() + +vod_config_id = f"python-test-vod-config-{uuid.uuid4().hex[:5]}-{now.seconds}" + input_bucket_name = "cloud-samples-data/media/" input_video_file_name = "hls-vod/manifest.m3u8" vod_uri = f"https://storage.googleapis.com/{input_bucket_name}{input_video_file_name}" @@ -34,9 +44,20 @@ def test_vod_session_operations(capsys: pytest.fixture) -> None: - request = create_vod_session.create_vod_session( - project_id, location, vod_uri, ad_tag_uri + # Test setup + + vod_config_name = ( + f"projects/{project_id}/locations/{location}/vodConfigs/{vod_config_id}" + ) + + response = create_vod_config.create_vod_config( + project_id, location, vod_config_id, vod_uri, ad_tag_uri ) + assert vod_config_name in response.name + + # Tests + + request = create_vod_session.create_vod_session(project_id, location, vod_config_id) session_name_prefix = f"projects/{project_number}/locations/{location}/vodSessions/" assert session_name_prefix in request.name @@ -89,3 +110,8 @@ def test_vod_session_operations(capsys: pytest.fixture) -> None: project_id, location, session_id, stitch_details_id ) assert stitch_details_name in response.name + + # Clean up + + response = delete_vod_config.delete_vod_config(project_id, location, vod_config_id) + assert response == empty.Empty()