Skip to content
This repository was archived by the owner on Aug 7, 2024. It is now read-only.

Commit a1fe9c8

Browse files
authored
Merge pull request #645 from danielthepope/feature/subtitles
Add ability to upload subtitles and link to media
2 parents 491fd37 + c94744b commit a1fe9c8

File tree

5 files changed

+180
-5
lines changed

5 files changed

+180
-5
lines changed

testdata/subs.srt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
1
2+
00:00:00,000 --> 00:00:03,251
3+
Lorem ipsum dolor sit amet,
4+
consectetur adipiscing elit.
5+
6+
2
7+
00:00:03,251 --> 00:00:06,502
8+
Maecenas rutrum suscipit accumsan.

tests/test_api_30.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,6 +1548,87 @@ def testPostUploadMediaChunkedFinalize(self):
15481548
self.assertEqual(len(responses.calls), 1)
15491549
self.assertTrue(resp)
15501550

1551+
@responses.activate
1552+
def testPostMediaSubtitlesCreateSuccess(self):
1553+
responses.add(
1554+
POST,
1555+
'https://upload.twitter.com/1.1/media/subtitles/create.json',
1556+
body=b'',
1557+
status=200)
1558+
expected_body = {
1559+
'media_id': '1234',
1560+
'media_category': 'TweetVideo',
1561+
'subtitle_info': {
1562+
'subtitles': [{
1563+
'media_id': '5678',
1564+
'language_code': 'en',
1565+
'display_name': 'English'
1566+
}]
1567+
}
1568+
}
1569+
resp = self.api.PostMediaSubtitlesCreate(video_media_id=1234,
1570+
subtitle_media_id=5678,
1571+
language_code='en',
1572+
display_name='English')
1573+
1574+
self.assertEqual(len(responses.calls), 1)
1575+
self.assertEqual(responses.calls[0].request.url,
1576+
'https://upload.twitter.com/1.1/media/subtitles/create.json')
1577+
request_body = json.loads(responses.calls[0].request.body.decode('utf-8'))
1578+
self.assertTrue(resp)
1579+
self.assertDictEqual(expected_body, request_body)
1580+
1581+
@responses.activate
1582+
def testPostMediaSubtitlesCreateFailure(self):
1583+
responses.add(
1584+
POST,
1585+
'https://upload.twitter.com/1.1/media/subtitles/create.json',
1586+
body=b'{"error":"Some error happened"}',
1587+
status=400)
1588+
self.assertRaises(
1589+
twitter.TwitterError,
1590+
lambda: self.api.PostMediaSubtitlesCreate(video_media_id=1234,
1591+
subtitle_media_id=5678,
1592+
language_code='en',
1593+
display_name='English'))
1594+
1595+
@responses.activate
1596+
def testPostMediaSubtitlesDeleteSuccess(self):
1597+
responses.add(
1598+
POST,
1599+
'https://upload.twitter.com/1.1/media/subtitles/delete.json',
1600+
body=b'',
1601+
status=200)
1602+
expected_body = {
1603+
'media_id': '1234',
1604+
'media_category': 'TweetVideo',
1605+
'subtitle_info': {
1606+
'subtitles': [{
1607+
'language_code': 'en'
1608+
}]
1609+
}
1610+
}
1611+
resp = self.api.PostMediaSubtitlesDelete(video_media_id=1234, language_code='en')
1612+
1613+
self.assertEqual(len(responses.calls), 1)
1614+
self.assertEqual(responses.calls[0].request.url,
1615+
'https://upload.twitter.com/1.1/media/subtitles/delete.json')
1616+
request_body = json.loads(responses.calls[0].request.body.decode('utf-8'))
1617+
self.assertTrue(resp)
1618+
self.assertDictEqual(expected_body, request_body)
1619+
1620+
@responses.activate
1621+
def testPostMediaSubtitlesDeleteFailure(self):
1622+
responses.add(
1623+
POST,
1624+
'https://upload.twitter.com/1.1/media/subtitles/delete.json',
1625+
body=b'{"error":"Some error happened"}',
1626+
status=400)
1627+
self.assertRaises(
1628+
twitter.TwitterError,
1629+
lambda: self.api.PostMediaSubtitlesDelete(video_media_id=1234,
1630+
language_code='en'))
1631+
15511632
@responses.activate
15521633
def testGetUserSuggestionCategories(self):
15531634
with open('testdata/get_user_suggestion_categories.json') as f:

tests/test_twitter_utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ def test_parse_media_file_fileobj(self):
7575
self.assertEqual(file_size, 44772)
7676
self.assertEqual(media_type, 'image/jpeg')
7777

78+
def test_parse_media_file_subtitle(self):
79+
data_file, filename, file_size, media_type = parse_media_file('testdata/subs.srt')
80+
self.assertTrue(hasattr(data_file, 'read'))
81+
self.assertEqual(filename, 'subs.srt')
82+
self.assertEqual(file_size, 157)
83+
self.assertEqual(media_type, 'text/srt')
84+
7885
def test_utils_error_checking(self):
7986
with open('testdata/168NQ.jpg', 'r') as f:
8087
self.assertRaises(

twitter/api.py

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,15 +1028,15 @@ def PostUpdate(self,
10281028
attachment_url=None):
10291029
"""Post a twitter status message from the authenticated user.
10301030
1031-
https://dev.twitter.com/docs/api/1.1/post/statuses/update
1031+
https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update
10321032
10331033
Args:
10341034
status (str):
10351035
The message text to be posted. Must be less than or equal to
10361036
CHARACTER_LIMIT characters.
10371037
media (int, str, fp, optional):
1038-
A URL, a local file, or a file-like object (something with a
1039-
read() method), or a list of any combination of the above.
1038+
A media ID, URL, local file, or file-like object (something with
1039+
a read() method), or a list of any combination of the above.
10401040
media_additional_owners (list, optional):
10411041
A list of user ids representing Twitter users that should be able
10421042
to use the uploaded media in their tweets. If you pass a list of
@@ -1398,7 +1398,7 @@ def UploadMediaChunked(self,
13981398
number of additional owners is capped at 100 by Twitter.
13991399
media_category:
14001400
Category with which to identify media upload. Only use with Ads
1401-
API & video files.
1401+
API, video files and subtitles.
14021402
14031403
Returns:
14041404
media_id:
@@ -1424,6 +1424,81 @@ def UploadMediaChunked(self,
14241424
except KeyError:
14251425
raise TwitterError('Media could not be uploaded.')
14261426

1427+
def PostMediaSubtitlesCreate(self,
1428+
video_media_id,
1429+
subtitle_media_id,
1430+
language_code,
1431+
display_name):
1432+
"""Associate uploaded subtitles to an uploaded video. You can associate
1433+
subtitles to a video before or after Tweeting.
1434+
1435+
Args:
1436+
video_media_id (int):
1437+
Media ID of the uploaded video to add the subtitles to. The
1438+
video must have been uploaded using the category 'TweetVideo'.
1439+
subtitle_media_id (int):
1440+
Media ID of the uploaded subtitle file. The subtitles myst have
1441+
been uploaded using the category 'Subtitles'.
1442+
language_code (str):
1443+
The language code that the subtitles are written in. The
1444+
language code should be a BCP47 code (e.g. 'en', 'sp')
1445+
display_name (str):
1446+
Language name (e.g. 'English', 'Spanish')
1447+
1448+
Returns:
1449+
True if successful. Raises otherwise.
1450+
"""
1451+
url = '%s/media/subtitles/create.json' % self.upload_url
1452+
1453+
subtitle = {}
1454+
subtitle['media_id'] = str(subtitle_media_id)
1455+
subtitle['language_code'] = language_code
1456+
subtitle['display_name'] = display_name
1457+
parameters = {}
1458+
parameters['media_id'] = str(video_media_id)
1459+
parameters['media_category'] = 'TweetVideo'
1460+
parameters['subtitle_info'] = {}
1461+
parameters['subtitle_info']['subtitles'] = [subtitle]
1462+
1463+
resp = self._RequestUrl(url, 'POST', json=parameters)
1464+
# Response body should be blank, so only do error checking if the response is not blank.
1465+
if resp.content.decode('utf-8'):
1466+
self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
1467+
1468+
return True
1469+
1470+
def PostMediaSubtitlesDelete(self,
1471+
video_media_id,
1472+
language_code):
1473+
"""Remove subtitles from an uploaded video.
1474+
1475+
Args:
1476+
video_media_id (int):
1477+
Media ID of the video for which the subtitles will be removed.
1478+
language_code (str):
1479+
The language code of the subtitle file that should be deleted.
1480+
The language code should be a BCP47 code (e.g. 'en', 'sp')
1481+
1482+
Returns:
1483+
True if successful. Raises otherwise.
1484+
"""
1485+
url = '%s/media/subtitles/delete.json' % self.upload_url
1486+
1487+
subtitle = {}
1488+
subtitle['language_code'] = language_code
1489+
parameters = {}
1490+
parameters['media_id'] = str(video_media_id)
1491+
parameters['media_category'] = 'TweetVideo'
1492+
parameters['subtitle_info'] = {}
1493+
parameters['subtitle_info']['subtitles'] = [subtitle]
1494+
1495+
resp = self._RequestUrl(url, 'POST', json=parameters)
1496+
# Response body should be blank, so only do error checking if the response is not blank.
1497+
if resp.content.decode('utf-8'):
1498+
self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
1499+
1500+
return True
1501+
14271502
def _TweetTextWrap(self,
14281503
status,
14291504
char_lim=CHARACTER_LIMIT):

twitter/twitter_utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ def parse_media_file(passed_media, async_upload=False):
243243
long_img_formats = [
244244
'image/gif'
245245
]
246+
subtitle_formats = ['text/srt']
246247
video_formats = ['video/mp4',
247248
'video/quicktime']
248249

@@ -274,6 +275,9 @@ def parse_media_file(passed_media, async_upload=False):
274275
pass
275276

276277
media_type = mimetypes.guess_type(os.path.basename(filename))[0]
278+
# The .srt extension is not recognised by the mimetypes module.
279+
if os.path.basename(filename).endswith('.srt'):
280+
media_type = 'text/srt'
277281
if media_type is not None:
278282
if media_type in img_formats and file_size > 5 * 1048576:
279283
raise TwitterError({'message': 'Images must be less than 5MB.'})
@@ -283,7 +287,7 @@ def parse_media_file(passed_media, async_upload=False):
283287
raise TwitterError({'message': 'Videos must be less than 15MB.'})
284288
elif media_type in video_formats and async_upload and file_size > 512 * 1048576:
285289
raise TwitterError({'message': 'Videos must be less than 512MB.'})
286-
elif media_type not in img_formats and media_type not in video_formats and media_type not in long_img_formats:
290+
elif media_type not in img_formats + long_img_formats + subtitle_formats + video_formats:
287291
raise TwitterError({'message': 'Media type could not be determined.'})
288292

289293
return data_file, filename, file_size, media_type

0 commit comments

Comments
 (0)