Repository: ytdl-org/youtube-dl

Issue:
Entire playlist downloaded for a video page with &list= in the URL
Passing youtube-dl a video page with a `&list=` URL argument downloads the entire playlist instead of just that video, e.g.:

```
$ python -m youtube_dl "https://www.youtube.com/watch?v=QUisTd3jvO4&list=PLpWI1yEIe1nEu2w2UyV0dW6fPj6fnhTzE&index=2"
[youtube] PL PLpWI1yEIe1nEu2w2UyV0dW6fPj6fnhTzE: Downloading page #1
[youtube] PL PLpWI1yEIe1nEu2w2UyV0dW6fPj6fnhTzE: Found 15 videos
...
```

I didn't expect this behavior, though I guess some could disagree.  Note that users can easily reach the actual `/playlist` by following the playlist link above the video.



Discussion:
Heh, I knew this would have been troublesome. See #668. @phihag?

I didn't notice that the link at the top is the link to the playlist, honestly. But your reasoning makes sense. This is a video, the playlist is there. Actually, there's no link to the plain single video in the page. I think we made the wrong decision.

Is there any way available to download just the current video being shown in a playlist (with some specific set of options or something)?

If not then how about the following (backward compatible) solution:

For the `--playlist-start` and `playlist-end` options, have a special value `current` which takes the index of the current video being shown. If there's no explicit video id in the url, then fall back to the value 1. To download just the current video in the playlist:

`youtube-dl --playlist-start current --playlist-end current "https://www.youtube.com/watch?v=QUisTd3jvO4&list=PLpWI1yEIe1nEu2w2UyV0dW6fPj6fnhTzE"`

@danielholmes or you can just run `youtube-dl https://www.youtube.com/watch?v=QUisTd3jvO4` or `youtube-dl QUisTd3jvO4`

@jaimeMF the specific use case we have is that our users provide a url to a youtube video that they want to reference. We want to avoid having to educate them about different types of youtube URLs and what ones will or wont work. Thanks for the suggestions though (I didn't realise that second form works with youtube-dl)

I was unsure about what behavior to take when implementing this. I was
convinced to make ytdl download the playlist when they told me that you can
get the single video link from the big title.

However, I am +1 on a "--no-playlist" flag, I see how it can be
inconvenient, and a explicit flag would not be confusing.
Any opinions? Or can I provide a PR for this?


Yes, as seen in #668, other users expect us to download the playlist, so a flag may be best (bonus points for modifying the status message to `Downloading playlist PLfoobar - add --no-playlist to just download video bazbam`). Feel free to draft up a PR.

I think that we should treat them as videos, we have receive some reports about this, and even if it's really easy to download just the video (copying the id or dropping the list parameter), that's not what a standard user would do. As you have already said, getting the link to the playlist from the video page is easy.

Umh, actually this is the default playlist view (there is no other one,
only this and the non-playing index) and it is easier to get the video link
than the playlist link.

Unsure about this, just like to other time...

## 

Filippo Valsorda

2013/9/18 Jaime Marquínez Ferrándiz notifications@github.com

> I think that we should treat them as videos, we have receive some reports
> about this, and even if it's really easy to download just the video
> (copying the id or dropping the list parameter), that's not what a standard
> user would do. As you have already said, getting the link to the playlist
> from the video page is easy.
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/rg3/youtube-dl/issues/755#issuecomment-24661322
> .

You're right and a lot of users probably just see it (they click `Play all`). It's harder to decide and I'm not as confident as before. Adding the flag would solve it, but I don't like this approach too much.


Resolving patch:
diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py
--- a/youtube_dl/YoutubeDL.py
+++ b/youtube_dl/YoutubeDL.py
@@ -83,6 +83,7 @@ class YoutubeDL(object):
     skip_download:     Skip the actual download of the video file
     cachedir:          Location of the cache files in the filesystem.
                        None to disable filesystem cache.
+    noplaylist:        Download single video instead of a playlist if in doubt.
     
     The following parameters are not used by YoutubeDL itself, they are used by
     the FileDownloader:
diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py
--- a/youtube_dl/__init__.py
+++ b/youtube_dl/__init__.py
@@ -187,6 +187,7 @@ def _hide_login_info(opts):
     selection.add_option('--date', metavar='DATE', dest='date', help='download only videos uploaded in this date', default=None)
     selection.add_option('--datebefore', metavar='DATE', dest='datebefore', help='download only videos uploaded before this date', default=None)
     selection.add_option('--dateafter', metavar='DATE', dest='dateafter', help='download only videos uploaded after this date', default=None)
+    selection.add_option('--no-playlist', action='store_true', dest='noplaylist', help='download only the currently playing video', default=False)
 
 
     authentication.add_option('-u', '--username',
@@ -599,6 +600,7 @@ def _real_main(argv=None):
         'progress_with_newline': opts.progress_with_newline,
         'playliststart': opts.playliststart,
         'playlistend': opts.playlistend,
+        'noplaylist': opts.noplaylist,
         'logtostderr': opts.outtmpl == '-',
         'consoletitle': opts.consoletitle,
         'nopart': opts.nopart,
diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py
--- a/youtube_dl/extractor/youtube.py
+++ b/youtube_dl/extractor/youtube.py
@@ -23,6 +23,7 @@
     compat_urllib_error,
     compat_urllib_parse,
     compat_urllib_request,
+    compat_urlparse,
     compat_str,
 
     clean_html,
@@ -1523,9 +1524,19 @@ def _real_extract(self, url):
         mobj = re.match(self._VALID_URL, url, re.VERBOSE)
         if mobj is None:
             raise ExtractorError(u'Invalid URL: %s' % url)
+        playlist_id = mobj.group(1) or mobj.group(2)
+
+        # Check if it's a video-specific URL
+        query_dict = compat_urlparse.parse_qs(compat_urlparse.urlparse(url).query)
+        if 'v' in query_dict:
+            video_id = query_dict['v'][0]
+            if self._downloader.params.get('noplaylist'):
+                self.to_screen(u'Downloading just video %s because of --no-playlist' % video_id)
+                return self.url_result('https://www.youtube.com/watch?v=' + video_id, 'Youtube')
+            else:
+                self.to_screen(u'Downloading playlist PL%s - add --no-playlist to just download video %s' % (playlist_id, video_id))
 
         # Download playlist videos from API
-        playlist_id = mobj.group(1) or mobj.group(2)
         videos = []
 
         for page_num in itertools.count(1):


Test patch:
diff --git a/test/test_youtube_lists.py b/test/test_youtube_lists.py
--- a/test/test_youtube_lists.py
+++ b/test/test_youtube_lists.py
@@ -27,6 +27,14 @@ def test_youtube_playlist(self):
         ytie_results = [YoutubeIE()._extract_id(url['url']) for url in result['entries']]
         self.assertEqual(ytie_results, [ 'bV9L5Ht9LgY', 'FXxLjLQi3Fg', 'tU3Bgo5qJZE'])
 
+    def test_youtube_playlist_noplaylist(self):
+        dl = FakeYDL()
+        dl.params['noplaylist'] = True
+        ie = YoutubePlaylistIE(dl)
+        result = ie.extract('https://www.youtube.com/watch?v=FXxLjLQi3Fg&list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re')
+        self.assertEqual(result['_type'], 'url')
+        self.assertEqual(YoutubeIE()._extract_id(result['url']), 'FXxLjLQi3Fg')
+
     def test_issue_673(self):
         dl = FakeYDL()
         ie = YoutubePlaylistIE(dl)