152 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			152 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Python
		
	
	
	
| '''
 | |
|     Wrapper for the youtube-dl library. Used so if there are any library interface
 | |
|     updates we only need to udpate them in one place.
 | |
| '''
 | |
| 
 | |
| 
 | |
| import os
 | |
| from django.conf import settings
 | |
| from copy import copy
 | |
| from common.logger import log
 | |
| import yt_dlp
 | |
| 
 | |
| 
 | |
| _youtubedl_cachedir = getattr(settings, 'YOUTUBE_DL_CACHEDIR', None)
 | |
| _defaults = getattr(settings, 'YOUTUBE_DEFAULTS', {})
 | |
| if _youtubedl_cachedir:
 | |
|     _youtubedl_cachedir = str(_youtubedl_cachedir)
 | |
|     _defaults['cachedir'] = _youtubedl_cachedir
 | |
| 
 | |
| 
 | |
| 
 | |
| class YouTubeError(yt_dlp.utils.DownloadError):
 | |
|     '''
 | |
|         Generic wrapped error for all errors that could be raised by youtube-dl.
 | |
|     '''
 | |
|     pass
 | |
| 
 | |
| 
 | |
| def get_yt_opts():
 | |
|     opts = copy(_defaults)
 | |
|     cookie_file = settings.COOKIES_FILE
 | |
|     if cookie_file.is_file():
 | |
|         cookie_file_path = str(cookie_file.resolve())
 | |
|         log.info(f'[youtube-dl] using cookies.txt from: {cookie_file_path}')
 | |
|         opts.update({'cookiefile': cookie_file_path})
 | |
|     return opts
 | |
| 
 | |
| 
 | |
| def get_media_info(url):
 | |
|     '''
 | |
|         Extracts information from a YouTube URL and returns it as a dict. For a channel
 | |
|         or playlist this returns a dict of all the videos on the channel or playlist
 | |
|         as well as associated metadata.
 | |
|     '''
 | |
|     opts = get_yt_opts()
 | |
|     opts.update({
 | |
|         'skip_download': True,
 | |
|         'forcejson': True,
 | |
|         'simulate': True,
 | |
|         'logger': log,
 | |
|         'extract_flat': True,
 | |
|     })
 | |
|     response = {}
 | |
|     with yt_dlp.YoutubeDL(opts) as y:
 | |
|         try:
 | |
|             response = y.extract_info(url, download=False)
 | |
|         except yt_dlp.utils.DownloadError as e:
 | |
|             raise YouTubeError(f'Failed to extract_info for "{url}": {e}') from e
 | |
|     if not response:
 | |
|         raise YouTubeError(f'Failed to extract_info for "{url}": No metadata was '
 | |
|                            f'returned by youtube-dl, check for error messages in the '
 | |
|                            f'logs above. This task will be retried later with an '
 | |
|                            f'exponential backoff.')
 | |
|     return response
 | |
| 
 | |
| 
 | |
| def download_media(url, media_format, extension, output_file, info_json, 
 | |
|                    sponsor_categories="all", 
 | |
|                    embed_thumbnail=False, embed_metadata=False, skip_sponsors=True, 
 | |
|                    write_subtitles=False, auto_subtitles=False, sub_langs='en'):
 | |
|     '''
 | |
|         Downloads a YouTube URL to a file on disk.
 | |
|     '''
 | |
| 
 | |
|     def hook(event):
 | |
|         filename = os.path.basename(event['filename'])
 | |
|     
 | |
|         if event.get('downloaded_bytes') is None or event.get('total_bytes') is None:
 | |
|             return None
 | |
| 
 | |
|         if event['status'] == 'error':
 | |
|             log.error(f'[youtube-dl] error occured downloading: {filename}')
 | |
|         elif event['status'] == 'downloading':
 | |
|             downloaded_bytes = event.get('downloaded_bytes', 0)
 | |
|             total_bytes = event.get('total_bytes', 0)
 | |
|             eta = event.get('_eta_str', '?').strip()
 | |
|             percent_done = event.get('_percent_str', '?').strip()
 | |
|             speed = event.get('_speed_str', '?').strip()
 | |
|             total = event.get('_total_bytes_str', '?').strip()
 | |
|             if downloaded_bytes > 0 and total_bytes > 0:
 | |
|                 p = round((event['downloaded_bytes'] / event['total_bytes']) * 100)
 | |
|                 if (p % 5 == 0) and p > hook.download_progress:
 | |
|                     hook.download_progress = p
 | |
|                     log.info(f'[youtube-dl] downloading: {filename} - {percent_done} '
 | |
|                              f'of {total} at {speed}, {eta} remaining')
 | |
|             else:
 | |
|                 # No progress to monitor, just spam every 10 download messages instead
 | |
|                 hook.download_progress += 1
 | |
|                 if hook.download_progress % 10 == 0:
 | |
|                     log.info(f'[youtube-dl] downloading: {filename} - {percent_done} '
 | |
|                                 f'of {total} at {speed}, {eta} remaining')
 | |
|         elif event['status'] == 'finished':
 | |
|             total_size_str = event.get('_total_bytes_str', '?').strip()
 | |
|             elapsed_str = event.get('_elapsed_str', '?').strip()
 | |
|             log.info(f'[youtube-dl] finished downloading: {filename} - '
 | |
|                      f'{total_size_str} in {elapsed_str}')
 | |
|         else:
 | |
|             log.warn(f'[youtube-dl] unknown event: {str(event)}')
 | |
|     hook.download_progress = 0
 | |
| 
 | |
|     ytopts = {
 | |
|         'format': media_format,
 | |
|         'merge_output_format': extension,
 | |
|         'outtmpl': output_file,
 | |
|         'quiet': True,
 | |
|         'progress_hooks': [hook],
 | |
|         'writeinfojson': info_json,
 | |
|         'postprocessors': [],
 | |
|         'writesubtitles': write_subtitles,
 | |
|         'writeautomaticsub': auto_subtitles,
 | |
|         'subtitleslangs': sub_langs.split(','),
 | |
|     }
 | |
|     
 | |
|     sbopt = {
 | |
|         'key': 'SponsorBlock',
 | |
|         'categories': [sponsor_categories]
 | |
|     }
 | |
|     ffmdopt = {
 | |
|         'key': 'FFmpegMetadata',
 | |
|         'add_chapters': True,
 | |
|         'add_metadata': True
 | |
|     }
 | |
| 
 | |
|     opts = get_yt_opts()
 | |
|     if embed_thumbnail:
 | |
|         ytopts['postprocessors'].append({'key': 'EmbedThumbnail'})
 | |
|     if embed_metadata:
 | |
|         ffmdopt["add_metadata"] = True
 | |
|     if skip_sponsors:
 | |
|         ytopts['postprocessors'].append(sbopt)
 | |
|     
 | |
|     ytopts['postprocessors'].append(ffmdopt)
 | |
|         
 | |
|     opts.update(ytopts)
 | |
|         
 | |
|     with yt_dlp.YoutubeDL(opts) as y:
 | |
|         try:
 | |
|             return y.download([url])
 | |
|         except yt_dlp.utils.DownloadError as e:
 | |
|             raise YouTubeError(f'Failed to download for "{url}": {e}') from e
 | |
|     return False
 |