faubiguy

Youtube Download Script 2.5

Jul 29th, 2016
111
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.30 KB | None | 0 0
  1. #!/usr/bin/python3
  2. import os, re, base64, time, sys, argparse, itertools
  3. import urllib.request as request
  4. from mutagen.oggvorbis import OggVorbis, OggVorbisHeaderError
  5. from mutagen.flac import Picture
  6. import youtube_dl
  7. from PIL import Image
  8. from io import BytesIO
  9.  
  10. parser = argparse.ArgumentParser(description='Downloads songs from youtube videos and tags them based on description and metadata')
  11. parser.add_argument('videos', metavar='video', nargs='*', help='A video to download and tag. Use youtube id or url')
  12. #parser.add_argument('-s', '--stdin', action='store_true', help='Read videos from stdin in addition to arguments')
  13. parser.add_argument('-d', '--directory', default='/home/zenith/Music/Library/ytdl/others', help='Directory to download songs to. Defaults to /home/zenith/Music/Library/ytdl/others')
  14. parser.add_argument('-n', '--no-tag', action='store_true', help='Don\'t tag, just download')
  15. parser.add_argument('-o', '--allow-overwrite', action='store_true', help='Allow downloaded file to overwrite existing file')
  16. parser.add_argument('-c', '--description', action='store_true', help='Download description')
  17. args = parser.parse_args()
  18.  
  19. kvPattern = re.compile(r'\b((?:\w+\s+)*\w+)\s*(?::|:)\s*([^\s].*)$', re.UNICODE)
  20. kvPattern2 = re.compile(r'\b\[((?:\w+\s+)*\w+)\]\s*(\w+(?:\s+\w+)*)$', re.UNICODE)
  21. titlePattern = re.compile(r'^(.*?)\s+\-\s+(.*)$', re.UNICODE)
  22.  
  23. tagdict = {
  24.     'bpm'        : 'BPM',
  25.     'artist'     : 'ARTIST',
  26.     'song'       : 'TITLE',
  27.     'title'      : 'TITLE',
  28.     'track'      : 'TITLE',
  29.     'genre'      : 'GENRE',
  30.     'language'   : 'LANGUAGE',
  31.     'album'      : 'ALBUM',
  32.     'disc'       : 'ALBUM',
  33.     'website'    : 'WEBSITE',
  34.     'site'       : 'WEBSITE',
  35.     'from'       : 'ORIGINALALBUM',
  36.     'original'   : 'ORIGINAL',
  37.     'source'     : 'ORIGINAL',
  38.     'key'        : 'KEY',
  39.     'arrangement': 'ARRANGER',
  40.     'arranger'   : 'ARRANGER',
  41.     'arrange'    : 'ARRANGER',
  42.     'lyric'      : 'LYRICIST',
  43.     'lyrics'     : 'LYRICIST',
  44.     'lyricist'   : 'LYRICIST',
  45. }
  46.  
  47. def tag(vorbis, information, log=None):
  48.     title = information['title']
  49.     description = information['description']
  50.     channel = information['uploader']
  51.     video_id = information['id']
  52.     if log is None:
  53.         log = lambda x:None
  54.     lines = description.split('\n')
  55.     tags = {}
  56.     for tag, value in (match.group(1,2) for match in (re.search(kvPattern, line) or re.search(kvPattern2, line) for line in lines) if match):
  57.         tag = tag.lower()
  58.         if tag in tagdict:
  59.             tags[tagdict[tag]] = value
  60.         if tag == 'circle' or tag == 'group':
  61.             if 'artist' not in tags:
  62.                 tags['ARTIST'] = value
  63.             continue
  64.         if tag == 'singer' or tag == 'vocals' or tag == 'vocalist' or tag == 'vocal':
  65.             tags['VOCALIST'] = value
  66.             tags['VOCAL'] = 'Yes'
  67.     log('Added tags from description')
  68.     #print(title)
  69.     titleMatch = re.match(titlePattern, title) or re.match(kvPattern2, title)
  70.     if titleMatch:
  71.         if 'ARTIST' not in tags:
  72.             tags['ARTIST'] = titleMatch.group(1).strip()
  73.         if 'TITLE' not in tags:
  74.             tags['TITLE'] = titleMatch.group(2).strip()
  75.     else:
  76.         if 'TITLE' not in tags:
  77.             tags['TITLE'] = title
  78.         if 'ARTIST' not in tags:
  79.             tags['ARTIST'] = channel
  80.     if 'vocal' in title.lower():
  81.         tags['VOCAL'] = 'Yes'
  82.     tags['UPLOADER'] = channel
  83.     tags['VIDEO_ID'] = video_id
  84.     if 'webpage_url' in information:
  85.         tags['VIDEO_WEBPAGE'] = information['webpage_url']
  86.     log('Added tags from title and channel')
  87.     lyrics=re.search(r'lyrics:(.*?)(?:\n\n|$)', description, re.UNICODE + re.IGNORECASE)
  88.     if lyrics:
  89.         tags['LYRICS'] = lyrics.group(1)
  90.         tags['VOCAL'] = 'Yes'
  91.     if 'thumbnail' in information:
  92.         try:
  93.             picture = Picture()
  94.             picture.type = 3
  95.             with request.urlopen(information['thumbnail']) as thumbhttp:
  96.                 picture.mime = thumbhttp.headers.get_content_type()
  97.                 thumb_data = BytesIO(thumbhttp.read())
  98.             thumb_image = Image.open(thumb_data)
  99.             picture.width = thumb_image.width
  100.             picture.height = thumb_image.height
  101.             picture.data = thumb_data.getvalue()
  102.             tags['METADATA_BLOCK_PICTURE'] = base64.b64encode(picture.write()).decode('ascii')
  103.             log('Added thumbnail image')
  104.         except request.HTTPError:
  105.             pass
  106.     vorbis.clear()
  107.     for tag in vorbis:
  108.         del vorbis[tag]
  109.     log('Removed existing tags')
  110.     for tag, value in tags.items():
  111.         vorbis[tag] = value
  112.     vorbis.pprint()
  113.     vorbis.save()
  114.     log('Saved tags to file')
  115.        
  116. class Tagger(youtube_dl.postprocessor.common.PostProcessor):
  117.    
  118.     def run(self, information):
  119.         log = lambda message:self._downloader.to_screen('[tagger] ' + message)
  120.         filename = information['filepath']
  121.         if not filename.endswith('.ogg'):
  122.             return [], information
  123.         log('Tagging {0}'.format(filename))
  124.         try:
  125.             vorbis = OggVorbis(filename)
  126.         except OggVorbisHeaderError:
  127.             log('Unable to read. Renaming and Skipping.')
  128.             os.rename(filename, '.'+filename)
  129.             information['filepath'] = '.'+filename
  130.         else:
  131.             description = information['description']
  132.             uploader = information['uploader']
  133.             title = information['title']
  134.             video_id = information['id']
  135.             tag(vorbis, information, log)
  136.             log('Successfully tagged')
  137.         return [], information
  138.    
  139. class CollisionError(Exception):
  140.     pass
  141.        
  142. class AntiOverwrite(youtube_dl.postprocessor.common.PostProcessor):
  143.    
  144.     def run(self, information):
  145.         oggfilename = re.sub(r'\..*$', '.ogg', information['filepath'])
  146.         if os.path.exists(oggfilename):
  147.             self._downloader.to_screen('[collision_detector] File exists, skipping: {0}'.format(oggfilename))
  148.             os.remove(information['filepath'])
  149.             raise CollisionError()
  150.         return [], information
  151.    
  152. outtmpl = '%(uploader)s - %(title)s [%(id)s].%(ext)s'
  153.  
  154. if args.directory:
  155.     if not os.path.isdir(args.directory):
  156.         parser.error('Not a directory: {0}'.format(args.directory))
  157.     outtmpl = os.path.join(args.directory, outtmpl)
  158.  
  159. youtube_dl_options = {
  160.     'format': 'bestaudio/best',
  161.     'outtmpl': outtmpl,
  162.     'writedescription': args.description,
  163.     'logtostderr': True,
  164.     'nooverwrites': not args.allow_overwrite
  165. }
  166.  
  167. videos = args.videos or (video.strip() for video in sys.stdin)
  168.  
  169. with youtube_dl.YoutubeDL(youtube_dl_options) as downloader:
  170.     #if not args.allow_overwrite:
  171.         #downloader.add_post_processor(AntiOverwrite())
  172.     downloader.add_post_processor(youtube_dl.postprocessor.ffmpeg.FFmpegExtractAudioPP(downloader, preferredcodec='vorbis', preferredquality='0', nopostoverwrites=not args.allow_overwrite))
  173.     if not args.no_tag:
  174.         downloader.add_post_processor(Tagger())
  175.     for video in videos:
  176.         try:
  177.             downloader.download([video])
  178.         except CollisionError:
  179.             pass
  180.         except Exception as ex:
  181.             print('Error occured downloading video {0}: {1}'.format(video, ex), file=sys.stderr)
  182.         print('Finished video: {0}'.format(video))
Add Comment
Please, Sign In to add comment