tarina

git clone https://git.tarina.org/tarina
Log | Files | Refs | README | LICENSE

main.py (10917B)


      1 #!/usr/bin/env python
      2 #
      3 # Upload videos to Youtube from the command-line using APIv3.
      4 #
      5 # Author: Arnau Sanchez <pyarnau@gmail.com>
      6 # Project: https://github.com/tokland/youtube-upload
      7 """
      8 Upload a video to Youtube from the command-line.
      9 
     10     $ youtube-upload --title="A.S. Mutter playing" \
     11                      --description="Anne Sophie Mutter plays Beethoven" \
     12                      --category=Music \
     13                      --tags="mutter, beethoven" \
     14                      anne_sophie_mutter.flv
     15     pxzZ-fYjeYs
     16 """
     17 
     18 import os
     19 import sys
     20 import optparse
     21 import collections
     22 import webbrowser
     23 from io import open
     24 
     25 import googleapiclient.errors
     26 import oauth2client
     27 from oauth2client import file
     28 
     29 from . import auth
     30 from . import upload_video
     31 from . import categories
     32 from . import lib
     33 from . import playlists
     34 
     35 # http://code.google.com/p/python-progressbar (>= 2.3)
     36 try:
     37     import progressbar
     38 except ImportError:
     39     progressbar = None
     40 
     41 class InvalidCategory(Exception): pass
     42 class OptionsError(Exception): pass
     43 class AuthenticationError(Exception): pass
     44 class RequestError(Exception): pass
     45 
     46 EXIT_CODES = {
     47     OptionsError: 2,
     48     InvalidCategory: 3,
     49     RequestError: 3,
     50     AuthenticationError: 4,
     51     oauth2client.client.FlowExchangeError: 4,
     52     NotImplementedError: 5,
     53 }
     54 
     55 WATCH_VIDEO_URL = "https://www.youtube.com/watch?v={id}"
     56 
     57 debug = lib.debug
     58 struct = collections.namedtuple
     59 
     60 def open_link(url):
     61     """Opens a URL link in the client's browser."""
     62     webbrowser.open(url)
     63 
     64 def get_progress_info():
     65     """Return a function callback to update the progressbar."""
     66     progressinfo = struct("ProgressInfo", ["callback", "finish"])
     67 
     68     if progressbar:
     69         bar = progressbar.ProgressBar(widgets=[
     70             progressbar.Percentage(),
     71             ' ', progressbar.Bar(),
     72             ' ', progressbar.FileTransferSpeed(),
     73             ' ', progressbar.DataSize(), '/', progressbar.DataSize('max_value'),
     74             ' ', progressbar.Timer(),
     75             ' ', progressbar.AdaptiveETA(),
     76         ])
     77         def _callback(total_size, completed):
     78             if not hasattr(bar, "next_update"):
     79                 if hasattr(bar, "maxval"):
     80                     bar.maxval = total_size
     81                 else:
     82                     bar.max_value = total_size
     83                 bar.start()
     84             bar.update(completed)
     85         def _finish():
     86             if hasattr(bar, "next_update"):
     87                 return bar.finish()
     88         return progressinfo(callback=_callback, finish=_finish)
     89     else:
     90         return progressinfo(callback=None, finish=lambda: True)
     91 
     92 def get_category_id(category):
     93     """Return category ID from its name."""
     94     if category:
     95         if category in categories.IDS:
     96             ncategory = categories.IDS[category]
     97             debug("Using category: {0} (id={1})".format(category, ncategory))
     98             return str(categories.IDS[category])
     99         else:
    100             msg = "{0} is not a valid category".format(category)
    101             raise InvalidCategory(msg)
    102 
    103 def upload_youtube_video(youtube, options, video_path, total_videos, index):
    104     """Upload video with index (for split videos)."""
    105     u = lib.to_utf8
    106     title = u(options.title)
    107     if hasattr(u('string'), 'decode'):
    108         description = u(options.description or "").decode("string-escape")
    109     else:
    110         description = options.description
    111     if options.publish_at:
    112       debug("Your video will remain private until specified date.")
    113 
    114     tags = [u(s.strip()) for s in (options.tags or "").split(",")]
    115     ns = dict(title=title, n=index+1, total=total_videos)
    116     title_template = u(options.title_template)
    117     complete_title = (title_template.format(**ns) if total_videos > 1 else title)
    118     progress = get_progress_info()
    119     category_id = get_category_id(options.category)
    120     request_body = {
    121         "snippet": {
    122             "title": complete_title,
    123             "description": description,
    124             "categoryId": category_id,
    125             "tags": tags,
    126             "defaultLanguage": options.default_language,
    127             "defaultAudioLanguage": options.default_audio_language,
    128 
    129         },
    130         "status": {
    131             "embeddable": options.embeddable,
    132             "privacyStatus": ("private" if options.publish_at else options.privacy),
    133             "publishAt": options.publish_at,
    134             "license": options.license,
    135 
    136         },
    137         "recordingDetails": {
    138             "location": lib.string_to_dict(options.location),
    139             "recordingDate": options.recording_date,
    140         },
    141     }
    142 
    143     debug("Start upload: {0}".format(video_path))
    144     try:
    145         video_id = upload_video.upload(youtube, video_path,
    146             request_body, progress_callback=progress.callback,
    147             chunksize=options.chunksize)
    148     finally:
    149         progress.finish()
    150     return video_id
    151 
    152 def get_youtube_handler(options):
    153     """Return the API Youtube object."""
    154     home = os.path.expanduser("~")
    155     default_credentials = os.path.join(home, ".youtube-upload-credentials.json")
    156     client_secrets = options.client_secrets or os.path.join(home, ".client_secrets.json")
    157     credentials = options.credentials_file or default_credentials
    158     debug("Using client secrets: {0}".format(client_secrets))
    159     debug("Using credentials file: {0}".format(credentials))
    160     get_code_callback = (auth.browser.get_code
    161         if options.auth_browser else auth.console.get_code)
    162     return auth.get_resource(client_secrets, credentials,
    163         get_code_callback=get_code_callback)
    164 
    165 def parse_options_error(parser, options):
    166     """Check errors in options."""
    167     required_options = ["title"]
    168     missing = [opt for opt in required_options if not getattr(options, opt)]
    169     if missing:
    170         parser.print_usage()
    171         msg = "Some required option are missing: {0}".format(", ".join(missing))
    172         raise OptionsError(msg)
    173 
    174 def run_main(parser, options, args, output=sys.stdout):
    175     """Run the main scripts from the parsed options/args."""
    176     parse_options_error(parser, options)
    177     youtube = get_youtube_handler(options)
    178 
    179     if youtube:
    180         for index, video_path in enumerate(args):
    181             video_id = upload_youtube_video(youtube, options, video_path, len(args), index)
    182             video_url = WATCH_VIDEO_URL.format(id=video_id)
    183             debug("Video URL: {0}".format(video_url))
    184             if options.open_link:
    185                 open_link(video_url) #Opens the Youtube Video's link in a webbrowser
    186 
    187             if options.thumb:
    188                 youtube.thumbnails().set(videoId=video_id, media_body=options.thumb).execute()
    189             if options.playlist:
    190                 playlists.add_video_to_playlist(youtube, video_id,
    191                     title=lib.to_utf8(options.playlist), privacy=options.privacy)
    192             output.write(video_id + "\n")
    193     else:
    194         raise AuthenticationError("Cannot get youtube resource")
    195 
    196 def main(arguments):
    197     """Upload videos to Youtube."""
    198     usage = """Usage: %prog [OPTIONS] VIDEO [VIDEO2 ...]
    199 
    200     Upload videos to Youtube."""
    201     parser = optparse.OptionParser(usage)
    202 
    203     # Video metadata
    204     parser.add_option('-t', '--title', dest='title', type="string",
    205         help='Video title')
    206     parser.add_option('-c', '--category', dest='category', type="string",
    207         help='Video category')
    208     parser.add_option('-d', '--description', dest='description', type="string",
    209         help='Video description')
    210     parser.add_option('', '--description-file', dest='description_file', type="string",
    211         help='Video description file', default=None)
    212     parser.add_option('', '--tags', dest='tags', type="string",
    213         help='Video tags (separated by commas: "tag1, tag2,...")')
    214     parser.add_option('', '--privacy', dest='privacy', metavar="STRING",
    215         default="public", help='Privacy status (public | unlisted | private)')
    216     parser.add_option('', '--publish-at', dest='publish_at', metavar="datetime",
    217        default=None, help='Publish date (ISO 8601): YYYY-MM-DDThh:mm:ss.sZ')
    218     parser.add_option('', '--license', dest='license', metavar="string",
    219        choices=('youtube', 'creativeCommon'), default='youtube',
    220        help='License for the video, either "youtube" (the default) or "creativeCommon"')
    221     parser.add_option('', '--location', dest='location', type="string",
    222         default=None, metavar="latitude=VAL,longitude=VAL[,altitude=VAL]",
    223         help='Video location"')
    224     parser.add_option('', '--recording-date', dest='recording_date', metavar="datetime",
    225         default=None, help="Recording date (ISO 8601): YYYY-MM-DDThh:mm:ss.sZ")
    226     parser.add_option('', '--default-language', dest='default_language', type="string",
    227         default=None, metavar="string",
    228         help="Default language (ISO 639-1: en | fr | de | ...)")
    229     parser.add_option('', '--default-audio-language', dest='default_audio_language', type="string",
    230         default=None, metavar="string",
    231         help="Default audio language (ISO 639-1: en | fr | de | ...)")
    232     parser.add_option('', '--thumbnail', dest='thumb', type="string", metavar="FILE",
    233         help='Image file to use as video thumbnail (JPEG or PNG)')
    234     parser.add_option('', '--playlist', dest='playlist', type="string",
    235         help='Playlist title (if it does not exist, it will be created)')
    236     parser.add_option('', '--title-template', dest='title_template',
    237         type="string", default="{title} [{n}/{total}]", metavar="string",
    238         help='Template for multiple videos (default: {title} [{n}/{total}])')
    239     parser.add_option('', '--embeddable', dest='embeddable', default=True,
    240         help='Video is embeddable')
    241 
    242     # Authentication
    243     parser.add_option('', '--client-secrets', dest='client_secrets',
    244         type="string", help='Client secrets JSON file')
    245     parser.add_option('', '--credentials-file', dest='credentials_file',
    246         type="string", help='Credentials JSON file')
    247     parser.add_option('', '--auth-browser', dest='auth_browser', action='store_true',
    248         help='Open a GUI browser to authenticate if required')
    249 
    250     #Additional options
    251     parser.add_option('', '--chunksize', dest='chunksize', type="int",
    252         default = 1024*1024*8, help='Update file chunksize')
    253     parser.add_option('', '--open-link', dest='open_link', action='store_true',
    254         help='Opens a url in a web browser to display the uploaded video')
    255 
    256     options, args = parser.parse_args(arguments)
    257 
    258     if options.description_file is not None and os.path.exists(options.description_file):
    259         with open(options.description_file, encoding="utf-8") as file:
    260             options.description = file.read()
    261 
    262     try:
    263         run_main(parser, options, args)
    264     except googleapiclient.errors.HttpError as error:
    265         response = bytes.decode(error.content, encoding=lib.get_encoding()).strip()
    266         raise RequestError(u"Server response: {0}".format(response))
    267 
    268 def run():
    269     sys.exit(lib.catch_exceptions(EXIT_CODES, main, sys.argv[1:]))
    270 
    271 if __name__ == '__main__':
    272     run()