Repository: spotdl/spotify-downloader
Issue: Incorrect filename: "Token - YouTube Rapper - Bonus"


#### What is the purpose of your *issue*?
- [ x ] Bug
- [ x ] Feature Request
- [ ] Question
- [ ] Other

When specifying a specific youtube url - it sometimes gets completely the wrong filename. 

https://www.youtube.com/watch?v=WL8haXrw4QA  is a charlotte de witte track - that gets named to a Beyonce track

I suspect this is because it is going to spotify to get the details or it's doing some search. Is it possible that it be specified to just take the the data from the youtube title?

Changed files:
spotdl/patch/youtube.py

diff --git a/spotdl/patch/youtube.py b/spotdl/patch/youtube.py
index 121fb20f..5bea92d2 100644
--- a/spotdl/patch/youtube.py
+++ b/spotdl/patch/youtube.py
@@ -1,12 +1,8 @@
 # This code has been taken from PyTube
 # https://github.com/nficano/pytube
-# and is based on
-# https://github.com/nficano/pytube/pull/643
+# and is based on various patches required to keep PyTube usable.
 
-# This is because the commit required to fix the issue
-# https://github.com/nficano/pytube/issues/641
-# hasn't been released on PyPI yet and is urgently
-# required for spotdl to operate properly.
+# We need to do this since PyTube isn't being maintained.
 
 # The below code is LICENSED under:
 """
@@ -36,14 +32,23 @@ SOFTWARE.
 
 
 import pytube
+from pytube.extract import get_ytplayer_config, apply_signature
 
 def apply_patches():
+    """
+    This methods applies all the needed patches on PyTube.
+    """
+    # Patch 1: https://github.com/nficano/pytube/pull/643
     pytube.__main__.apply_descrambler = apply_descrambler
+    # Patch 2: https://github.com/nficano/pytube/pull/634
+    pytube.__main__.YouTube.extract_title = extract_title
+    pytube.__main__.YouTube.descramble = descramble
 
 
-# The below imports are required by the patch
+# The below imports are required by the patches
 import json
-from urllib.parse import parse_qs, unquote
+from html import unescape
+from urllib.parse import parse_qs, parse_qsl, unquote
 
 def apply_descrambler(stream_data, key):
     """Apply various in-place transforms to YouTube's media stream data.
@@ -106,3 +111,69 @@ def apply_descrambler(stream_data, key):
             for i in stream_data[key].split(",")
         ]
 
+
+def extract_title(self):
+    html_lower = self.watch_html.lower()
+    i_start = html_lower.index('<meta property="og:title" content="') + len(
+        '<meta property="og:title" content="'
+    )
+    curr_i = i_start
+    end_found = False
+    while not end_found:
+        # search for the end of the tag: ">
+        if html_lower[curr_i] == '"' and html_lower[curr_i + 1] == ">":
+            i_end = curr_i
+            end_found = True
+        curr_i += 1
+
+    return self.watch_html[i_start:i_end].strip()
+
+
+def descramble(self) -> None:
+    """Descramble the stream data and build Stream instances.
+    The initialization process takes advantage of Python's
+    "call-by-reference evaluation," which allows dictionary transforms to
+    be applied in-place, instead of holding references to mutations at each
+    interstitial step.
+    :rtype: None
+    """
+    self.vid_info = dict(parse_qsl(self.vid_info_raw))
+    if self.age_restricted:
+        self.player_config_args = self.vid_info
+    else:
+        assert self.watch_html is not None
+        self.player_config_args = get_ytplayer_config(self.watch_html)["args"]
+
+        # Fix for KeyError: 'title' issue #434
+        if "title" not in self.player_config_args:  # type: ignore
+            title = self.extract_title()
+            self.player_config_args["title"] = unescape(title)
+
+    # https://github.com/nficano/pytube/issues/165
+    stream_maps = ["url_encoded_fmt_stream_map"]
+    if "adaptive_fmts" in self.player_config_args:
+        stream_maps.append("adaptive_fmts")
+
+    # unscramble the progressive and adaptive stream manifests.
+    for fmt in stream_maps:
+        if not self.age_restricted and fmt in self.vid_info:
+            apply_descrambler(self.vid_info, fmt)
+        apply_descrambler(self.player_config_args, fmt)
+
+        if not self.js:
+            if not self.embed_html:
+                self.embed_html = request.get(url=self.embed_url)
+            self.js_url = extract.js_url(self.embed_html)
+            self.js = request.get(self.js_url)
+
+        apply_signature(self.player_config_args, fmt, self.js)
+
+        # build instances of :class:`Stream <Stream>`
+        self.initialize_stream_objects(fmt)
+
+    # load the player_response object (contains subtitle information)
+    self.player_response = json.loads(self.player_config_args["player_response"])
+    del self.player_config_args["player_response"]
+    self.stream_monostate.title = self.title
+    self.stream_monostate.duration = self.length
+