diff --git a/.gitignore b/.gitignore index c295659..bbc372d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ # PyCharm .idea/ +build +dist + +twitter_creds.json \ No newline at end of file diff --git a/README.md b/README.md index abf9c2e..6067084 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,19 @@ # PictureToBurn A python package for gif-making. + +## Setup and Installation +### Set up Twitter creds +* Generate access token. Full instructions at https://developer.twitter.com/en/docs/basics/authentication/guides/access-tokens +* Copy `twitter_creds.dist.json` to `twitter_creds.json` +* Add token details to `twitter_creds.json` + +### Install +``` +python setup.py install +``` + +## Running +``` +create_gif [local_file | twitter_url] +``` \ No newline at end of file diff --git a/bin/create_gif b/bin/create_gif old mode 100644 new mode 100755 index 90c4b15..fb4794b --- a/bin/create_gif +++ b/bin/create_gif @@ -2,32 +2,48 @@ import os import warnings from fire import Fire +import logging -from picture_to_burn import mp4_to_gif, find_mp4 +from picture_to_burn import mp4_to_gif, find_video +log = logging.getLogger('create_gif') +log.setLevel(logging.DEBUG) +ch = logging.StreamHandler() +log.addHandler(ch) + +max_size = 2 #MB def convert(some_mp4: str, width: int = 320): """ Convert something to a gif. Currently supports local .mp4 files and tweets. """ - if os.path.isfile(some_mp4) or _is_ffmpegable_url(some_mp4): + log.info(f"Running with {some_mp4}") + if os.path.isfile(some_mp4): pass elif 'twitter.com' in some_mp4: - some_mp4 = find_mp4(some_mp4) - else: + log.info("Getting the video from Twitter") + some_mp4 = find_video(some_mp4, log) + + if not _is_ffmpegable_url(some_mp4): raise NotImplementedError( f"Can't find a way to convert {some_mp4} into a gif") + gif = mp4_to_gif(some_mp4, width) - size_mb = os.path.getsize(gif) / 1000000 - if size_mb > 2: + size_mb = _get_size(gif) + while size_mb > max_size: + log.info(f"Width {width}") warnings.warn(f"Gif created is {size_mb:.02f} MB, " f"and might not display in Slack. Making a smaller one.") os.remove(gif) - gif = mp4_to_gif(some_mp4, width // 2) + width = width // 2 + gif = mp4_to_gif(some_mp4, width) + size_mb = _get_size(gif) - print(f"Created gif: {gif}") + log.info(f"Created gif {gif} with size {size_mb} MB") +def _get_size(gif: str): + return os.path.getsize(gif) / 1000000 # At least, I've tried it with these FFMPEGABLE = ['ts', 'mp4', 'm3u8'] diff --git a/picture_to_burn/__init__.py b/picture_to_burn/__init__.py index f421674..a604b6b 100644 --- a/picture_to_burn/__init__.py +++ b/picture_to_burn/__init__.py @@ -1,2 +1,2 @@ from .ffmpeg import mp4_to_gif -from .twitter import find_mp4 +from .twitter_helper import find_video diff --git a/picture_to_burn/twitter.py b/picture_to_burn/twitter.py deleted file mode 100644 index 2615fa3..0000000 --- a/picture_to_burn/twitter.py +++ /dev/null @@ -1,20 +0,0 @@ -import requests -from lxml.html import fromstring - - -def find_mp4(tweet_url: str) -> str: - r""" - Scrape the .mp4 URL out of a tweet link. - - :param tweet_url: URL for a tweet with a video. - :return: URL of the mp4 itself. - """ - # TODO: error nicely if the tweet isn't found - tweet_html = requests.get(tweet_url) - - # TODO: error nicely if there's some kind of parsing error - tree = fromstring(tweet_html.content) - video_div = tree.cssselect('.PlayableMedia-player')[0] - player_style = video_div.attrib['style'].split('/')[-1] - media_id = player_style.split('.')[0] - return f'https://video.twimg.com/tweet_video/{media_id}.mp4' diff --git a/picture_to_burn/twitter_helper.py b/picture_to_burn/twitter_helper.py new file mode 100644 index 0000000..63a0554 --- /dev/null +++ b/picture_to_burn/twitter_helper.py @@ -0,0 +1,53 @@ +import twitter +import json +import sys +import os +from logging import Logger +import pkg_resources + +def _get_creds(): + with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'data', 'twitter_creds.json'), 'r') as f: + creds = json.load(f) + return creds + + +def find_video(tweet_url: str, log: Logger) -> str: + r""" + Gets the video URL from the Tweet + + :param tweet_url: URL for a tweet with a video. + :return: URL of the video itself. + """ + try: + creds = _get_creds() + api = twitter.Api( + consumer_key=creds['consumer_key'], + access_token_key=creds['access_token_key'], + consumer_secret=creds['consumer_secret'], + access_token_secret=creds['access_token_secret'] + ) + except Exception as e: + log.error(e) + raise Exception("There was a problem with your twitter creds") + tweet_id = tweet_url.strip("/").split("/")[-1] + + status = api.GetStatus(tweet_id) + media = status.media + + try: + video = media[0].AsDict() + + saved_variant = None + # Default to m3u8, then highest quality mp4 + for variant in video['video_info']['variants']: + if variant['content_type'] == 'application/x-mpegURL': + saved_variant = variant + break + elif saved_variant == None: + saved_variant = variant + elif variant['content_type'] == "video/mp4" and variant['bitrate'] > saved_variant['bitrate']: + saved_variant = variant + + return saved_variant['url'].split("?")[0] #Remove query string + except Exception as e: + raise Exception("There's no video on this Twitter post") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2039768..91a8edc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,3 @@ -cssselect==1.0.3 docopt==0.6.2 fire==0.1.3 -lxml==4.1.1 -requests==2.21.0 +python-twitter==3.5 \ No newline at end of file diff --git a/setup.py b/setup.py index b4fe6ee..03f7f1d 100644 --- a/setup.py +++ b/setup.py @@ -5,11 +5,10 @@ name='picture-to-burn', packages=find_packages(), install_requires=[ - 'cssselect', + 'python-twitter', 'docopt', - 'lxml', - 'requests', ], scripts=['bin/create_gif'], + data_files=[('data', ['twitter_creds.json'])], version='0.0.1', ) diff --git a/twitter_creds.dist.json b/twitter_creds.dist.json new file mode 100644 index 0000000..dfd24a7 --- /dev/null +++ b/twitter_creds.dist.json @@ -0,0 +1,6 @@ +{ + "consumer_key": "xxxxx", + "access_token_key": "xxxxx", + "consumer_secret": "xxxxx", + "access_token_secret": "xxxxx" +} \ No newline at end of file