Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

option to delete recordings after they are downloaded #36

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ Required Scopes for your server-to-server zoom app:

- `cloud_recording:read:list_user_recordings:admin`.
- `cloud_recording:read:list_recording_files:admin`.
- (Optional) `cloud_recording:delete:meeting_recording:admin`: if you want to enable `DELETE_AFTER_DOWNLOAD`
- (Optional) `user:read:list_users:admin`: if you want the script to iterate over all users in the account (default behavior).

If you are using classic scopes then these would be:
If you are using classic scopes then these would be:

- `recording:read:admin` to download the recordings.
- `recording:write:admin` to delete recordings.
- `user:read:admin` if you want the script to iterate over all users in the account.

## Instructions

1. Create a server-to-server app as specified above and activate it (no need to publish).
Expand All @@ -28,14 +31,14 @@ If you are using classic scopes then these would be:

1. Install the requirements listed in `requirements.txt` using [pip](https://pip.pypa.io/en/stable/reference/requirement-specifiers/).

```bash
python -m pip install -r requirements.txt
```
```bash
python -m pip install -r requirements.txt
```

1. Run `zoom_batch_downloader.py`.

```bash
python zoom_batch_downloader.py
```
```bash
python zoom_batch_downloader.py
```

Code written by Georg Kasmin, Lane Campbell, Sami Hassan and Aness Zurba.
7 changes: 7 additions & 0 deletions config_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,20 @@
# Note: One "meeting" can have multiple recording instances.
GROUP_BY_RECORDING = False

# If True, recordings will be named with the meeting date at the beginning of the filename;
# otherwise it will be at the end.
NAME_RECORDINGS_DATE_FIRST = False

# If True, participant audio files will be downloaded as well.
# This works when "Record a separate audio file of each participant" is enabled.
INCLUDE_PARTICIPANT_AUDIO = True

# Set to True for more verbose output.
VERBOSE_OUTPUT = False

# Set to True to delete meetings that are successfully downloaded
DELETE_AFTER_DOWNLOAD = False

# Constants used for indicating size in bytes.
B = 1
KB = 1024 * B
Expand Down
43 changes: 35 additions & 8 deletions zoom_batch_downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def main():
CONFIG.END_DAY or monthrange(CONFIG.END_YEAR, CONFIG.END_MONTH)[1],
)

file_count, total_size, skipped_count = download_recordings(
file_count, total_size, skipped_count, deleted_count = download_recordings(
get_users(), from_date, to_date
)

Expand All @@ -74,6 +74,7 @@ def main():
f"{Style.BRIGHT}Downloaded {Fore.GREEN}{file_count}{Fore.RESET} files.",
f"Total size: {Fore.GREEN}{total_size_str}{Fore.RESET}.{Style.RESET_ALL}",
f"Skipped: {skipped_count} files.",
f"Deleted: {deleted_count} meetings.",
)


Expand Down Expand Up @@ -130,7 +131,7 @@ def get_user_name(user_data):


def download_recordings(users, from_date, to_date):
file_count, total_size, skipped_count = 0, 0, 0
file_count, total_size, skipped_count, deleted_count = 0, 0, 0, 0

for user_email, user_name in users:
user_description = get_user_description(user_email, user_name)
Expand All @@ -142,20 +143,26 @@ def download_recordings(users, from_date, to_date):
)

meetings = get_meetings(get_meeting_uuids(user_email, from_date, to_date))
user_file_count, user_total_size, user_skipped_count = (
user_file_count, user_total_size, user_skipped_count, user_downloaded = (
download_recordings_from_meetings(meetings, user_host_folder)
)

utils.print_bright(
"######################################################################"
)

if CONFIG.DELETE_AFTER_DOWNLOAD:
utils.print_bright(f"Deleting {len(user_downloaded)} meetings from {user_description}")
delete_meetings(user_downloaded)
deleted_count += len(user_downloaded)

print()

file_count += user_file_count
total_size += user_total_size
skipped_count += user_skipped_count

return (file_count, total_size, skipped_count)
return (file_count, total_size, skipped_count, deleted_count)


def download_not_ready_files():
Expand Down Expand Up @@ -186,6 +193,15 @@ def get_user_host_folder(user_email):
def date_to_str(date):
return date.strftime("%Y-%m-%d")

def delete_meetings(meetings):
for meeting in meetings:
try:
url = f"https://api.zoom.us/v2/meetings/{meeting["uuid"]}/recordings"
client.delete(url)
except Exception as e:
utils.print_bright(
f"Logging error occurred while deleting recordings for meeting {meeting.get("uuid")}: {e}"
)

def get_meeting_uuids(user_email, start_date, end_date):
meeting_uuids = []
Expand Down Expand Up @@ -246,6 +262,8 @@ def get_meetings(meeting_uuids):
def download_recordings_from_meetings(meetings, host_folder):
file_count, total_size, skipped_count = 0, 0, 0

downloaded = []

for meeting in meetings:
if (
CONFIG.TOPICS
Expand Down Expand Up @@ -277,9 +295,15 @@ def download_recordings_from_meetings(meetings, host_folder):
recording_file.get("file_extension")
or os.path.splitext(recording_file["file_name"])[1]
)
recording_name = utils.slugify(
f'{topic}__{recording_file["recording_start"]}'
)
if CONFIG.NAME_RECORDINGS_DATE_FIRST:
recording_name = utils.slugify(
f'{recording_file["recording_start"]}__{topic}'
)
else:
recording_name = utils.slugify(
f'{topic}__{recording_file["recording_start"]}'
)

file_id = recording_file["id"]
file_name_suffix = (
os.path.splitext(recording_file["file_name"])[0] + "__"
Expand Down Expand Up @@ -308,7 +332,10 @@ def download_recordings_from_meetings(meetings, host_folder):
else:
skipped_count += 1

return file_count, total_size, skipped_count
if skipped_count == 0:
downloaded.append(meeting)

return file_count, total_size, skipped_count, downloaded


def download_recording_file(
Expand Down
16 changes: 11 additions & 5 deletions zoom_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,21 @@ def __init__(self, account_id: str, client_id: str, client_secret: str, PAGE_SIZ
self.cached_token = None

def get(self, url):
return self._get_with_token(lambda t: requests.get(url=url, headers=self.get_headers(t))).json()
return self._req_with_token(lambda t: requests.get(url=url, headers=self.get_headers(t))).json()

def _get_with_token(self, get):
def delete(self, url):
resp = self._req_with_token(lambda t: requests.delete(url = url, headers=self.get_headers(t)))
if resp.status_code == 204:
return None
return resp.json()

def _req_with_token(self, req):
if self.cached_token:
response = get(self.cached_token)
response = req(self.cached_token)

if not self.cached_token or response.status_code == 401:
self.cached_token = self.fetch_token()
response = get(self.cached_token)
response = req(self.cached_token)

if not response.ok:
raise Exception(f'{response.status_code} {response.text}')
Expand Down Expand Up @@ -86,4 +92,4 @@ def do_as_get(token):

return test_response

self._get_with_token(lambda t: do_as_get(t))
self._req_with_token(lambda t: do_as_get(t))