Your JWP dashboard lets you manually upload single videos or retrieve up to 100 videos via URLs. However, suppose you need to add more than 100 videos or need to add videos programmatically to your media library. In that case, you can code a solution instructing JWP to fetch videos from URLs.
You can also manually upload videos from your JWP dashboard.
Requirements
CSV file
The .csv file lists videos with relevant video metadata you want to add to your JWP library.
When creating your .csv file, a populated download_url
column is required. We recommend including download_url
, title
, description
, publish_start_date
, and permalink
as the first six column headers. You can include additional header columns to include custom video metadata.

Recommended and custom column headers
The table below defines all possible column headers and the acceptable data format for each.
Special characters should not be HTML-encoded. HTML-encoded characters in the
title
,description
, andtags
columns should be decoded before pushing to the API.
Column | Description | |
---|---|---|
download_url* | (Required) Remote URL of your video files
Be sure that your videos adhere to the guidelines here to ensure our platform can transcode them. |
|
title | Title of the video | |
description | Description of the video | |
tags | Tags for the video
Multiple tags should be comma-separated within quotation marks. |
|
publish_start_date | Video publish date in YYYY-MM-DD format
This specifies the date and time when the video should be available for streaming. |
|
permalink | URL of the web page where this video is published | |
external_id | Unique identifier value in metadata on the media
For example, a foreign ID to a different system.
|
|
thumbnail_download_url | URL of the video thumbnail location
If the URL is valid the upload script attempts to create a thumbnail for the media based on what is served at the URL, and sets that new thumbnail to be the media’s poster. |
|
custom.legacy_id | ID for the media, generated by a non-JW Player platform or program
For example, a foreign ID to a different system.
|
|
custom.param | User-defined parameter
The param part of the column header -- after the period separator (. ) -- specifies the parameter name.A parameter name should adhere to the following naming rules:
|
download_url,title,description,tags,publish_start_date,permalink,external_id,thumbnail_download_url,custom.legacy_id,custom.sport
http://video.my_platform.net/2324731/5288693/16179765?signature=f00d4fae14055dd97c35987c7c1e4aae30865cb8,This is Title One,This is Description One,tag1,2012-08-25,http://www.my_site/page1.html,5288693,http://images.my_platform.net/archive/images/10055001.jpg,legacy id value,football
http://video.my_platform.net/2324731/5288693/16179766?signature=f00d4fae14055dd97c35987c7c1e4aae30865cb9,This is Title Two,This is Description Two,"tag1,tag2",2013-08-19,http://www.my_site/page2.html,5287268,http://images.my_platform.net/archive/images/10055002.jpg,legacy id value,tennis
http://video.my_platform.net/2324731/5288693/16179767?signature=f00d4fae14055dd97c35987c7c1e4aae30865cb0,This is Title Three,This is Description Three,tag2,2014-06-19,http://www.my_site/page3.html,5287267,http://images.my_platform.net/archive/images/10055003.jpg,legacy id value,cricket
http://video.my_platform.net/2324731/5288693/16179768?signature=f00d4fae14055dd97c35987c7c1e4aae30865cb1,This is Title Four,This is Description Four,"tag1,tag3",2014-08-19,http://www.my_site/page4.html,5287266,http://images.my_platform.net/archive/images/10055004.jpg,legacy id value,baseball
Add videos to your library
Use the following steps to upload the videos and associated information in the .csv file programmatically:
- Add the Python client library to your codebase.
- Add the .csv file to your codebase.
- Add the batch upload code to the same directory as the .csv file. Be sure to replace the placeholders:
<site_id>
and<api_secret>
.This Python batch upload code performs the following tasks:
- Imports the necessary code dependencies
- Parses the .csv file you created into an array or list
- Loops over each row to create a video record
- Prints relevant JWP unique identifiers for resources created
import html
import csv
import requests
import time
from functools import wraps
SITE_ID = "<site_id>"
API_SECRET = "<api_secret>"
headers = {"Authorization": f"Bearer {API_SECRET}"}
timeout_wait_time = 2.0
max_retries = 6
def needs_unescaping(key):
return key in ["title", "description", "tags"] or key.startswith("custom.")
def parse_row(row):
media = {}
custom_parameters = {}
for key, value in row.items():
if key.startswith('custom.'):
custom_parameters[key.split('.')[1]] = value
else :
media[key] = html.unescape(value) if needs_unescaping(key) else value
media['custom_params'] = custom_parameters
return media
def retry(max_tries=6):
def decorator_retry(func):
@wraps(func)
def func_retry(*args, **kwargs):
wait = timeout_wait_time
func_response = func(*args, **kwargs)
tries = 1
status_code = func_response.status_code
while status_code == 429 and tries <= max_tries:
print(f"Timeout hit. About to sleep {wait} seconds!")
time.sleep(wait)
func_response = func(*args, **kwargs)
status_code = func_response.status_code
tries += 1
wait *= wait
if (tries > max_tries):
print(f"Failed timeout retries more than {max_tries} times.")
return func_response
return func_retry
return decorator_retry
@retry(max_tries=max_retries)
def create_media(media_fields, content):
url = f"https://api.jwplayer.com/v2/sites/{SITE_ID}/media/"
payload = {
"upload": {
# refer to MAPI v2 documentation for method options: https://docs.jwplayer.com/platform/reference/post_v2-sites-site-id-media
"method": "fetch",
"download_url": content
},
"metadata": media_fields,
}
create_media_response = requests.post(url=url, headers=headers, json=payload)
return create_media_response
@retry(max_tries=max_retries)
def create_thumbnail(media_id, thumbnail_url):
url = f"https://api.jwplayer.com/v2/sites/{SITE_ID}/thumbnails/"
payload = {
# refer to MAPI v2 documentation for thumbnail endpoints: https://docs.jwplayer.com/platform/reference/post_v2-sites-site-id-thumbnails
"relationships": {"media": [{"id": f"{media_id}"}]},
"upload": {
"source_type": "custom_upload",
"method": "fetch",
"thumbnail_type": "static",
"download_url": f"{thumbnail_url}"
}
}
create_thumbnail_response = requests.post(url=url, headers=headers, json=payload)
return create_thumbnail_response
def poll_thumbnail_for_ready_status(thumbnail_id):
url = f"https://api.jwplayer.com/v2/sites/{SITE_ID}/thumbnails/{thumbnail_id}"
status = "processing"
while status != "ready":
get_thumbnail_response = requests.get(url=url, headers=headers)
status = get_thumbnail_response.json().get("status")
print(f"{thumbnail_id} not yet ready. Waiting for one second.")
# Rather than use @retry here, we wait. That's because it doesn't make sense to poll without some regular wait.
time.sleep(1)
@retry(max_tries=max_retries)
def update_thumbnail(thumbnail_id):
url = f"https://api.jwplayer.com/v2/sites/{SITE_ID}/thumbnails/{thumbnail_id}"
payload = {
# refer to MAPI v2 Documentation for updating thumbnails: https://docs.jwplayer.com/platform/reference/patch_v2-sites-site-id-thumbnails-thumbnail-id-
"relationships": {
"media": [{"is_poster": True}]
}
}
update_thumbnail_response = requests.patch(url=url, headers=headers, json=payload)
return update_thumbnail_response
if __name__ == "__main__":
with open("example_to_load.csv") as csvfile:
reader = csv.reader(csvfile)
headings = next(reader)
for row in reader:
row = dict(zip(headings, row))
video = parse_row(row)
media_metadata = {
"title": video.get("title"),
"description":video.get("description"),
"tags":video.get("tags").split(','),
"publish_start_date":video.get("publish_start_date"),
"permalink":video.get("permalink"),
"external_id":video.get('external_id'),
"custom_params":video.get('custom_params')
}
legacy_id = video.get('custom_params',{}).get('legacy_id')
if legacy_id :
print(f"Uploading media with legacy id {legacy_id}:")
elif video.get('external_id'):
print(f"Uploading media with external id {video.get('external_id')}:")
else:
print(f"Uploading media with title {video.get('title')}:")
create_media_response = create_media(media_metadata,video.get("download_url"))
print('create_media_response:')
print(create_media_response.json())
errors = create_media_response.json().get('errors')
media_id = create_media_response.json().get("id")
if errors:
print(f'Errors on Creating Media with title {video.get("title")}:')
print(errors)
print('You will need to retry this upload.')
else:
print(f"Media can be accessed in JW Dashboard. site: {SITE_ID}, media: {media_id}")
create_thumbnail_response = create_thumbnail(
media_id=media_id,
thumbnail_url=video.get("thumbnail_download_url")
)
print('create_thumbnail_response:')
print(create_thumbnail_response.json())
errors = create_thumbnail_response.json().get('errors')
thumbnail_id = create_thumbnail_response.json().get("id")
if errors:
print(f'Errors on Creating thumbnail with url {video.get("thumbnail_download_url")}:')
print(errors)
print('The default thumbnail will be created by JWP.')
else:
poll_thumbnail_for_ready_status(thumbnail_id)
update_thumbnail_reponse = update_thumbnail(thumbnail_id=thumbnail_id)
print('update_thumbnail_reponse:')
print(update_thumbnail_reponse.json())
errors = update_thumbnail_reponse.json().get('errors')
if errors:
print(f'Errors on Updating thumbnail with thumbnail ID {thumbnail_id}:')
print(errors)
print(f'Thumbnail {thumbnail_id} is in JW, but it is not the poster for {media_id}.')