1
- from os import getenv
2
-
3
- from googleapiclient .discovery import build
4
- from googleapiclient .http import HttpError
5
- from sentry_sdk import capture_exception
6
-
7
-
8
- from spotify_dl .constants import YOUTUBE_API_SERVICE_NAME
9
- from spotify_dl .constants import YOUTUBE_API_VERSION
10
- from spotify_dl .constants import VIDEO
11
- from spotify_dl .constants import YOUTUBE_VIDEO_URL
12
1
from spotify_dl .scaffold import log
13
- from spotify_dl .cache import check_if_in_cache , save_to_cache
14
- from json import loads
15
- import requests
16
- from lxml import html # skipcq: BAN-B410
17
- import re
18
-
19
-
20
- from click import secho
21
-
22
- # skipcq: PYL-R1710
23
- def fetch_youtube_url (search_term , dev_key = None ):
24
- """
25
- For each song name/artist name combo, fetch the YouTube URL and return the list of URLs.
26
- :param search_term: Search term to be looked up on YouTube
27
- :param dev_key: Youtube API key
28
- """
29
- in_cache , video_id = check_if_in_cache (search_term )
30
- if in_cache :
31
- return YOUTUBE_VIDEO_URL + video_id
32
- if not dev_key :
33
- YOUTUBE_SEARCH_BASE = "https://www.youtube.com/results?search_query="
34
- try :
35
- response = requests .get (YOUTUBE_SEARCH_BASE + search_term ).content
36
- html_response = html .fromstring (response )
37
- video = html_response .xpath ("//a[contains(@class, 'yt-uix-tile-link')]/@href" )
38
- video_id = re .search ("((\?v=)[a-zA-Z0-9_-]{4,15})" , video [0 ]).group (0 )[3 :]
39
- log .debug (f"Found video id { video_id } for search term { search_term } " )
40
- _ = save_to_cache (search_term = search_term , video_id = video_id )
41
- return YOUTUBE_VIDEO_URL + video_id
42
- except AttributeError as e :
43
- log .warning (f"Could not find scrape details for { search_term } " )
44
- capture_exception (e )
45
- return None
46
- except IndexError as e :
47
- log .warning (f"Could not perform scrape search for { search_term } , got a different HTML" )
48
- capture_exception (e )
49
- return None
50
- else :
51
- youtube = build (YOUTUBE_API_SERVICE_NAME , YOUTUBE_API_VERSION ,
52
- developerKey = dev_key ,
53
- cache_discovery = False )
54
- try :
55
- in_cache , video_id = check_if_in_cache (search_term )
2
+ import youtube_dl
56
3
57
- if not in_cache :
58
- search_response = youtube .search ().list (q = search_term ,
59
- part = 'id, snippet' ).execute ()
60
- for v in search_response ['items' ]:
61
- if v ['id' ]['kind' ] == VIDEO :
62
- video_id = v ['id' ]['videoId' ]
63
- log .debug (f"Adding Video id { video_id } " )
64
- _ = save_to_cache (search_term = search_term , video_id = video_id )
65
- return YOUTUBE_VIDEO_URL + video_id
66
- except HttpError as err :
67
- err_details = loads (err .content .decode ('utf-8' )).get ('error' ).get ('errors' )
68
- secho ("Couldn't complete search due to following errors: " , fg = 'red' )
69
- for e in err_details :
70
- error_reason = e .get ('reason' )
71
- error_domain = e .get ('domain' )
72
- error_message = e .get ('message' )
73
4
74
- if error_reason == 'quotaExceeded' or error_reason == 'dailyLimitExceeded' :
75
- secho (f"\t You're over daily allowed quota. Unfortunately, YouTube restricts API keys to a max of 10,000 requests per day which translates to a maximum of 100 searches." , fg = 'red' )
76
- secho (f"\t The quota will be reset at midnight Pacific Time (PT)." ,fg = 'red' )
77
- secho (f"\t You can request for Quota increase from https://console.developers.google.com/apis/api/youtube.googleapis.com/quotas." , fg = 'red' )
78
- else :
79
- secho (f"\t Search failed due to { error_domain } :{ error_reason } , message: { error_message } " )
80
- return None
81
-
82
- def get_youtube_dev_key ():
5
+ def download_songs (songs , download_directory , format_string , skip_mp3 ):
83
6
"""
84
- Fetches the Youtube Developer API key from the environment variable.
85
- :return string containing the developer API key
7
+ Downloads songs from the YouTube URL passed to either current directory or download_directory, is it is passed.
8
+ :param songs: Dictionary of songs and associated artist
9
+ :param download_directory: Location where to save
10
+ :param format_string: format string for the file conversion
11
+ :param skip_mp3: Whether to skip conversion to MP3
86
12
"""
87
- return getenv ('YOUTUBE_DEV_KEY' )
13
+ download_directory = f"{ download_directory } \\ "
14
+ log .debug (f"Downloading to { download_directory } " )
15
+ for song , artist in songs .items ():
16
+ query = f"{ artist } - { song } " .replace (":" , "" ).replace ("\" " , "" )
17
+ download_archive = download_directory + 'downloaded_songs.txt'
18
+ outtmpl = download_directory + '%(title)s.%(ext)s'
19
+ ydl_opts = {
20
+ 'format' : format_string ,
21
+ 'download_archive' : download_archive ,
22
+ 'outtmpl' : outtmpl ,
23
+ 'default_search' : 'ytsearch' ,
24
+ 'noplaylist' : True ,
25
+ 'postprocessor_args' : ['-metadata' , 'title=' + song ,
26
+ '-metadata' , 'artist=' + artist ]
27
+ }
28
+ if not skip_mp3 :
29
+ mp3_postprocess_opts = {
30
+ 'key' : 'FFmpegExtractAudio' ,
31
+ 'preferredcodec' : 'mp3' ,
32
+ 'preferredquality' : '192' ,
33
+ }
34
+ ydl_opts ['postprocessors' ] = [mp3_postprocess_opts .copy ()]
35
+
36
+ with youtube_dl .YoutubeDL (ydl_opts ) as ydl :
37
+ try :
38
+ log .debug (ydl .download ([query ]))
39
+ except Exception as e :
40
+ log .debug (e )
41
+ print ('Failed to download: {}, please ensure YouTubeDL is up-to-date. ' .format (query ))
42
+ continue
0 commit comments