mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Follow Twitter guidelines for media upload by conforming to the "STATUS" phase, when required, and by providing "media_category" information. These will, for example, allow users to upload videos that exceed the basic 30 second limit. (#9261)
See: - https://twittercommunity.com/t/media-category-values/64781/7 - https://twittercommunity.com/t/duration-too-long-maximim-30000/68760 - https://dev.twitter.com/rest/reference/get/media/upload-status.html
This commit is contained in:
parent
9ade8002ac
commit
e7a5f7bcdf
@ -8,6 +8,8 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
|
from datetime import timedelta, datetime
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -15,6 +17,7 @@ import homeassistant.helpers.config_validation as cv
|
|||||||
from homeassistant.components.notify import (
|
from homeassistant.components.notify import (
|
||||||
ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService)
|
ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService)
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_USERNAME
|
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_USERNAME
|
||||||
|
from homeassistant.helpers.event import async_track_point_in_time
|
||||||
|
|
||||||
REQUIREMENTS = ['TwitterAPI==2.4.6']
|
REQUIREMENTS = ['TwitterAPI==2.4.6']
|
||||||
|
|
||||||
@ -68,49 +71,67 @@ class TwitterNotificationService(BaseNotificationService):
|
|||||||
_LOGGER.warning("'%s' is not a whitelisted directory", media)
|
_LOGGER.warning("'%s' is not a whitelisted directory", media)
|
||||||
return
|
return
|
||||||
|
|
||||||
media_id = self.upload_media(media)
|
callback = partial(self.send_message_callback, message)
|
||||||
|
|
||||||
|
self.upload_media_then_callback(callback, media)
|
||||||
|
|
||||||
|
def send_message_callback(self, message, media_id):
|
||||||
|
"""Tweet a message, optionally with media."""
|
||||||
if self.user:
|
if self.user:
|
||||||
resp = self.api.request('direct_messages/new',
|
resp = self.api.request('direct_messages/new',
|
||||||
{'text': message, 'user': self.user,
|
{'user': self.user,
|
||||||
|
'text': message,
|
||||||
'media_ids': media_id})
|
'media_ids': media_id})
|
||||||
else:
|
else:
|
||||||
resp = self.api.request('statuses/update',
|
resp = self.api.request('statuses/update',
|
||||||
{'status': message, 'media_ids': media_id})
|
{'status': message,
|
||||||
|
'media_ids': media_id})
|
||||||
|
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
self.log_error_resp(resp)
|
self.log_error_resp(resp)
|
||||||
|
else:
|
||||||
|
_LOGGER.debug("Message posted: %s", resp.json())
|
||||||
|
|
||||||
def upload_media(self, media_path=None):
|
def upload_media_then_callback(self, callback, media_path=None):
|
||||||
"""Upload media."""
|
"""Upload media."""
|
||||||
if not media_path:
|
if not media_path:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
with open(media_path, 'rb') as file:
|
||||||
|
total_bytes = os.path.getsize(media_path)
|
||||||
|
(media_category, media_type) = self.media_info(media_path)
|
||||||
|
resp = self.upload_media_init(
|
||||||
|
media_type, media_category, total_bytes
|
||||||
|
)
|
||||||
|
|
||||||
|
if 199 > resp.status_code < 300:
|
||||||
|
self.log_error_resp(resp)
|
||||||
|
return None
|
||||||
|
|
||||||
|
media_id = resp.json()['media_id']
|
||||||
|
media_id = self.upload_media_chunked(file, total_bytes, media_id)
|
||||||
|
|
||||||
|
resp = self.upload_media_finalize(media_id)
|
||||||
|
if 199 > resp.status_code < 300:
|
||||||
|
self.log_error_resp(resp)
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.check_status_until_done(media_id, callback)
|
||||||
|
|
||||||
|
def media_info(self, media_path):
|
||||||
|
"""Determine mime type and Twitter media category for given media."""
|
||||||
(media_type, _) = mimetypes.guess_type(media_path)
|
(media_type, _) = mimetypes.guess_type(media_path)
|
||||||
total_bytes = os.path.getsize(media_path)
|
media_category = self.media_category_for_type(media_type)
|
||||||
|
_LOGGER.debug("media %s is mime type %s and translates to %s",
|
||||||
|
media_path, media_type, media_category)
|
||||||
|
return media_category, media_type
|
||||||
|
|
||||||
file = open(media_path, 'rb')
|
def upload_media_init(self, media_type, media_category, total_bytes):
|
||||||
resp = self.upload_media_init(media_type, total_bytes)
|
|
||||||
|
|
||||||
if 199 > resp.status_code < 300:
|
|
||||||
self.log_error_resp(resp)
|
|
||||||
return None
|
|
||||||
|
|
||||||
media_id = resp.json()['media_id']
|
|
||||||
media_id = self.upload_media_chunked(file, total_bytes, media_id)
|
|
||||||
|
|
||||||
resp = self.upload_media_finalize(media_id)
|
|
||||||
if 199 > resp.status_code < 300:
|
|
||||||
self.log_error_resp(resp)
|
|
||||||
|
|
||||||
return media_id
|
|
||||||
|
|
||||||
def upload_media_init(self, media_type, total_bytes):
|
|
||||||
"""Upload media, INIT phase."""
|
"""Upload media, INIT phase."""
|
||||||
resp = self.api.request('media/upload',
|
return self.api.request('media/upload',
|
||||||
{'command': 'INIT', 'media_type': media_type,
|
{'command': 'INIT', 'media_type': media_type,
|
||||||
|
'media_category': media_category,
|
||||||
'total_bytes': total_bytes})
|
'total_bytes': total_bytes})
|
||||||
return resp
|
|
||||||
|
|
||||||
def upload_media_chunked(self, file, total_bytes, media_id):
|
def upload_media_chunked(self, file, total_bytes, media_id):
|
||||||
"""Upload media, chunked append."""
|
"""Upload media, chunked append."""
|
||||||
@ -128,17 +149,55 @@ class TwitterNotificationService(BaseNotificationService):
|
|||||||
return media_id
|
return media_id
|
||||||
|
|
||||||
def upload_media_append(self, chunk, media_id, segment_id):
|
def upload_media_append(self, chunk, media_id, segment_id):
|
||||||
"""Upload media, append phase."""
|
"""Upload media, APPEND phase."""
|
||||||
return self.api.request('media/upload',
|
return self.api.request('media/upload',
|
||||||
{'command': 'APPEND', 'media_id': media_id,
|
{'command': 'APPEND', 'media_id': media_id,
|
||||||
'segment_index': segment_id},
|
'segment_index': segment_id},
|
||||||
{'media': chunk})
|
{'media': chunk})
|
||||||
|
|
||||||
def upload_media_finalize(self, media_id):
|
def upload_media_finalize(self, media_id):
|
||||||
"""Upload media, finalize phase."""
|
"""Upload media, FINALIZE phase."""
|
||||||
return self.api.request('media/upload',
|
return self.api.request('media/upload',
|
||||||
{'command': 'FINALIZE', 'media_id': media_id})
|
{'command': 'FINALIZE', 'media_id': media_id})
|
||||||
|
|
||||||
|
def check_status_until_done(self, media_id, callback, *args):
|
||||||
|
"""Upload media, STATUS phase."""
|
||||||
|
resp = self.api.request('media/upload',
|
||||||
|
{'command': 'STATUS', 'media_id': media_id},
|
||||||
|
method_override='GET')
|
||||||
|
if resp.status_code != 200:
|
||||||
|
_LOGGER.error("media processing error: %s", resp.json())
|
||||||
|
processing_info = resp.json()['processing_info']
|
||||||
|
|
||||||
|
_LOGGER.debug("media processing %s status: %s", media_id,
|
||||||
|
processing_info)
|
||||||
|
|
||||||
|
if processing_info['state'] in {u'succeeded', u'failed'}:
|
||||||
|
return callback(media_id)
|
||||||
|
|
||||||
|
check_after_secs = processing_info['check_after_secs']
|
||||||
|
_LOGGER.debug("media processing waiting %s seconds to check status",
|
||||||
|
str(check_after_secs))
|
||||||
|
|
||||||
|
when = datetime.now() + timedelta(seconds=check_after_secs)
|
||||||
|
myself = partial(self.check_status_until_done, media_id, callback)
|
||||||
|
async_track_point_in_time(self.hass, myself, when)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def media_category_for_type(media_type):
|
||||||
|
"""Determine Twitter media category by mime type."""
|
||||||
|
if media_type is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if media_type.startswith('image/gif'):
|
||||||
|
return 'tweet_gif'
|
||||||
|
elif media_type.startswith('video/'):
|
||||||
|
return 'tweet_video'
|
||||||
|
elif media_type.startswith('image/'):
|
||||||
|
return 'tweet_image'
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def log_bytes_sent(bytes_sent, total_bytes):
|
def log_bytes_sent(bytes_sent, total_bytes):
|
||||||
"""Log upload progress."""
|
"""Log upload progress."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user