Repository: streamlink/streamlink (python)

Issue:
Error on nightly when using twitch
### Checklist

- [x] This is a bug report.
- [ ] This is a feature request.
- [ ] This is a plugin (improvement) request.
- [ ] I have read the contribution guidelines.

### Description

Getting a error when trying to connect to a twitch stream with the latest nightly

### Expected / Actual behavior

```
[cli][info] Found matching plugin twitch for URL twitch.tv/crream
[plugin.twitch][info] Attempting to authenticate using OAuth token
[plugin.twitch][info] Successfully logged in as Thinkpad4
error: No playable streams found on this URL: twitch.tv/crream
```

That is what it should return ^

### Reproduction steps / Explicit stream URLs to test

```
[cli][info] Found matching plugin twitch for URL twitch.tv/crream
[plugin.twitch][info] Attempting to authenticate using OAuth token
[plugin.twitch][info] Successfully logged in as Thinkpad4
Traceback (most recent call last):
  File "d:\Program Files (x86)\Streamlink\bin\streamlink-script.py", line 15, in
 <module>
    main()
  File "d:\Program Files (x86)\Streamlink\pkgs\streamlink_cli\main.py", line 103
8, in main
    handle_url()
  File "d:\Program Files (x86)\Streamlink\pkgs\streamlink_cli\main.py", line 482
, in handle_url
    streams = fetch_streams(plugin)
  File "d:\Program Files (x86)\Streamlink\pkgs\streamlink_cli\main.py", line 394
, in fetch_streams
    sorting_excludes=args.stream_sorting_excludes)
  File "d:\Program Files (x86)\Streamlink\pkgs\streamlink\plugin\plugin.py", lin
e 328, in get_streams
    return self.streams(*args, **kwargs)
  File "d:\Program Files (x86)\Streamlink\pkgs\streamlink\plugin\plugin.py", lin
e 236, in streams
    ostreams = self._get_streams()
  File "d:\Program Files (x86)\Streamlink\pkgs\streamlink\plugins\twitch.py", li
ne 617, in _get_streams
    return self._get_hls_streams("live")
  File "d:\Program Files (x86)\Streamlink\pkgs\streamlink\plugins\twitch.py", li
ne 581, in _get_hls_streams
    force_restart=not stream_type == "live")
  File "d:\Program Files (x86)\Streamlink\pkgs\streamlink\stream\hls.py", line 2
88, in parse_variant_playlist
    locale = session_.localization
  File "d:\Program Files (x86)\Streamlink\pkgs\streamlink\session.py", line 474,
 in localization
    return Localization(self.get_option("locale"))
  File "d:\Program Files (x86)\Streamlink\pkgs\streamlink\utils\l10n.py", line 1
15, in __init__
    self.language_code = language_code
  File "d:\Program Files (x86)\Streamlink\pkgs\streamlink\utils\l10n.py", line 1
40, in language_code
    self.language, self.country = self._parse_locale_code(language_code)
  File "d:\Program Files (x86)\Streamlink\pkgs\streamlink\utils\l10n.py", line 1
25, in _parse_locale_code
    return self.get_language(parts[0]), self.get_country(parts[1])
  File "d:\Program Files (x86)\Streamlink\pkgs\streamlink\utils\l10n.py", line 1
68, in get_language
    return Language.get(language)
  File "d:\Program Files (x86)\Streamlink\pkgs\streamlink\utils\l10n.py", line 7
5, in get
    l = languages.get(alpha2=language)
  File "d:\Program Files (x86)\Streamlink\pkgs\iso639\iso639.py", line 265, in g
et
    return getattr(self, key)[value]
  File "d:\Program Files (x86)\Streamlink\pkgs\iso639\iso639.py", line 132, in _
_getattr__
    'alpha2': self.part1,
  File "d:\Program Files (x86)\Streamlink\pkgs\iso639\iso639.py", line 105, in _
_get__
    val = self.f(instance)
  File "d:\Program Files (x86)\Streamlink\pkgs\iso639\iso639.py", line 198, in p
art1
    return dict((x.part1, x) for x in self.languages if x.part1)
  File "d:\Program Files (x86)\Streamlink\pkgs\iso639\iso639.py", line 105, in _
_get__
    val = self.f(instance)
  File "d:\Program Files (x86)\Streamlink\pkgs\iso639\iso639.py", line 174, in l
anguages
    l, i, m, p5, p2, p1 = _fabtabular()
  File "d:\Program Files (x86)\Streamlink\pkgs\iso639\iso639.py", line 20, in _f
abtabular
    from pkg_resources import resource_filename
  File "d:\Program Files (x86)\Streamlink\pkgs\pkg_resources\__init__.py", line
48, in <module>
    import six
ImportError: No module named 'six'
```

That is what it is returning with the latest nightly

### Environment details

Windows 7 64bit Latest Nightly

Pull request description:
win32: fix missing modules when using windows installer
`pkg_resources` (`setuptool`) and `requests` have changed the way their packages are structured so that they no longer vendor packages, this means those previously vendored packages have to be included by the installer builder.

This should fix #945, plus an unreported issue with `requests`. 

Patch:
diff --git a/docs/cli.rst b/docs/cli.rst
index 9dd3d3e6..46c5f0ed 100644
--- a/docs/cli.rst
+++ b/docs/cli.rst
@@ -136,7 +136,7 @@ or for a option without value::
 
 .. note::
     Any quotes used will be part of the value, so only use when the value needs them,
-    e.g. specifying a player with a path containing spaces.
+    e.g. specifiying a player with a path containing spaces.
 
 Example
 ^^^^^^^
@@ -219,7 +219,7 @@ You can login like this:
 Once logged in, the plugin makes sure to save the session credentials to avoid
 asking your username and password again.
 
-Nevertheless, these credentials are valid for a limited amount of time, so it
+Neverthless, these credentials are valid for a limited amount of time, so it
 might be a good idea to save your username and password in your
 :ref:`configuration file <cli-streamlinkrc>` anyway.
 
diff --git a/docs/ext_argparse.py b/docs/ext_argparse.py
index e95b9ab4..6d86a3df 100644
--- a/docs/ext_argparse.py
+++ b/docs/ext_argparse.py
@@ -28,7 +28,7 @@ _Argument = namedtuple("Argument", ["args", "options"])
 
 _block_re = re.compile(r":\n{2}\s{2}")
 _default_re = re.compile(r"Default is (.+)\.\n")
-_note_re = re.compile(r"Note: (.*)(?:\n\n|\n*$)", re.DOTALL)
+_note_re = re.compile(r"Note: (.*)\n\n", re.DOTALL)
 _option_re = re.compile(r"(?m)^((?!\s{2}).*)(--[\w-]+)")
 
 
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index 3498ac02..a524287f 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -146,13 +146,13 @@ openrectv           openrec.tv           Yes   Yes
 orf_tvthek          tvthek.orf.at        Yes   Yes
 ovvatv              ovva.tv              Yes   No
 pandatv             panda.tv             Yes   ?
+pcyourfreetv        pc-yourfreetv.com    Yes   --    Requires a login.
 periscope           periscope.tv         Yes   Yes   Replay/VOD is supported.
 picarto             picarto.tv           Yes   --
 playtv              playtv.fr            Yes   --    Streams may be geo-restricted to France.
 pluzz               - france.tv          Yes   Yes   Streams may be geo-restricted to France, Andorra and Monaco.
                     - ludo.fr
                     - zouzous.fr
-		    - france3-reg.. [8]_
 powerapp            powerapp.com.tr      Yes   No
 raiplay             raiplay.it           Yes   No    Most streams are geo-restricted to Italy.
 rtlxl               rtlxl.nl             No    Yes   Streams may be geo-restricted to The Netherlands. Livestreams not supported.
@@ -257,4 +257,3 @@ zhanqitv            zhanqi.tv            Yes   No
 .. [5] mediathek.daserste.de
 .. [6] players.brightcove.net
 .. [7] player.theplatform.com
-.. [8] france3-regions.francetvinfo.fr
diff --git a/script/makeinstaller.sh b/script/makeinstaller.sh
index 259cce34..df5bbb60 100755
--- a/script/makeinstaller.sh
+++ b/script/makeinstaller.sh
@@ -45,6 +45,14 @@ packages=requests
          iso639
          iso3166
          pkg_resources
+         six
+         appdirs
+         packaging
+         pyparsing
+         urllib3
+         idna
+         chardet
+         certifi
 pypi_wheels=pycryptodome==3.4.3
 
 files=../win32/LICENSE.txt > \$INSTDIR
diff --git a/src/streamlink/plugins/app17.py b/src/streamlink/plugins/app17.py
index b4cecb73..d0a17598 100644
--- a/src/streamlink/plugins/app17.py
+++ b/src/streamlink/plugins/app17.py
@@ -31,7 +31,6 @@ class App17(Plugin):
     def _get_streams(self):
         match = _url_re.match(self.url)
         page = match.group("page")
-        channel = match.group("channel")
 
         http.headers.update({'User-Agent': useragents.CHROME})
 
@@ -45,16 +44,6 @@ class App17(Plugin):
                 self.logger.info("Stream currently unavailable.")
                 return
 
-            url = ROOM_URL.format(rid)
-            res = http.get(url)
-        elif page == 'profile':
-            api = http.post(API_URL, data={"targetUserID": channel})
-            data = http.json(api, schema=_user_api_schema)
-            rid = data["liveStreamID"]
-            if rid == 0:
-                self.logger.info("Stream currently unavailable.")
-                return
-
             url = ROOM_URL.format(rid)
             res = http.get(url)
         else:
@@ -78,16 +67,13 @@ class App17(Plugin):
         else:
             yield "live", HTTPStream(self.session, url)
 
-        if '17app.co' in url:
-            prefix = url.replace("rtmp:", "http:").replace(".flv", "/playlist.m3u8")
-            if '/playlist.m3u8' not in prefix:
-                url = prefix + "/playlist.m3u8"
-            else:
-                url = prefix
+        prefix = url.replace("rtmp:", "http:").replace(".flv", ".m3u8")
+        if '.m3u8' not in prefix:
+            url = prefix + "/playlist.m3u8"
             for stream in HLSStream.parse_variant_playlist(self.session, url).items():
                 yield stream
         else:
-            url = url.replace(".flv", ".m3u8")
+            url = prefix
             yield "live", HLSStream(self.session, url)
 
 
diff --git a/src/streamlink/plugins/pcyourfreetv.py b/src/streamlink/plugins/pcyourfreetv.py
new file mode 100644
index 00000000..cf253c2c
--- /dev/null
+++ b/src/streamlink/plugins/pcyourfreetv.py
@@ -0,0 +1,79 @@
+import re
+
+from streamlink.compat import unquote
+from streamlink.plugin import Plugin, PluginOptions
+from streamlink.plugin.api import http
+from streamlink.stream import HLSStream
+
+
+class PCYourFreeTV(Plugin):
+    _login_url = 'http://pc-yourfreetv.com/home.php'
+    _url_re = re.compile(r'http://pc-yourfreetv\.com/index_player\.php\?channel=.+?&page_id=\d+')
+    _iframe_re = re.compile(r"<iframe .*?\bsrc='(?P<iframe>.+?)'.*?</iframe>")
+    _player_re = re.compile(r"<script language=JavaScript>m='(?P<player>.+?)'", re.DOTALL)
+    _video_url_re = re.compile(r'new [a-zA-Z0-9]+.Player\({source: "(?P<video_url>[^"]+?)".+?}.*\);', re.DOTALL)
+
+    options = PluginOptions({
+        'username': None,
+        'password': None
+    })
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return PCYourFreeTV._url_re.match(url)
+
+    def login(self, username, password):
+        res = http.post(
+            self._login_url,
+            data={
+                'user_name': username,
+                'user_pass': password,
+                'login': 'Login'
+            }
+        )
+
+        return username in res.text
+
+    def _get_streams(self):
+        username = self.get_option('username')
+        password = self.get_option('password')
+
+        if username is None or password is None:
+            self.logger.error("PC-YourFreeTV requires authentication, use --pcyourfreetv-username "
+                              "and --pcyourfreetv-password to set your username/password combination")
+            return
+
+        if self.login(username, password):
+            self.logger.info("Successfully logged in as {0}", username)
+
+        # Retrieve URL iframe
+        res = http.get(self.url)
+        match = self._iframe_re.search(res.text)
+        if match is None:
+            return
+
+        res = http.get(match.group('iframe'))
+        players = self._player_re.findall(res.text)
+        if len(players) == 0 is None:
+            return
+
+        while len(players) > 0:
+            player = unquote(players[-1])
+            players = self._player_re.findall(player)
+
+        match = self._video_url_re.search(player)
+        if match is None:
+            return
+
+        video_url = match.group('video_url')
+        if '.m3u8' in video_url:
+            streams = HLSStream.parse_variant_playlist(self.session, video_url)
+            if len(streams) != 0:
+                for stream in streams.items():
+                    yield stream
+            else:
+                # Not a HLS playlist
+                yield 'live', HLSStream(self.session, video_url)
+
+
+__plugin__ = PCYourFreeTV
diff --git a/src/streamlink/plugins/pluzz.py b/src/streamlink/plugins/pluzz.py
index 59555a26..0e9377c0 100644
--- a/src/streamlink/plugins/pluzz.py
+++ b/src/streamlink/plugins/pluzz.py
@@ -15,10 +15,9 @@ class Pluzz(Plugin):
     PLAYER_GENERATOR_URL = 'https://sivideo.webservices.francetelevisions.fr/assets/staticmd5/getUrl?id=jquery.player.7.js'
     TOKEN_URL = 'http://hdfauthftv-a.akamaihd.net/esi/TA?url={0}'
 
-    _url_re = re.compile(r'https?://((?:www)\.france\.tv/.+\.html|www\.(ludo|zouzous)\.fr/heros/[\w-]+|france3-regions\.francetvinfo\.fr/.+?/tv/direct)')
+    _url_re = re.compile(r'https?://((?:www)\.france\.tv/.+\.html|www\.(ludo|zouzous)\.fr/heros/[\w-]+)')
     _pluzz_video_id_re = re.compile(r'data-main-video="(?P<video_id>.+?)"')
-    _jeunesse_video_id_re = re.compile(r'playlist: \[{.*?,"identity":"(?P<video_id>.+?)@(?P<catalogue>Ludo|Zouzous)"')
-    _f3_regions_video_id_re = re.compile(r'"http://videos\.francetv\.fr/video/(?P<video_id>.+)@Regions"')
+    _other_video_id_re = re.compile(r'playlist: \[{.*?,"identity":"(?P<video_id>.+?)@(?P<catalogue>Ludo|Zouzous)"')
     _player_re = re.compile(r'src="(?P<player>//staticftv-a\.akamaihd\.net/player/jquery\.player.+?-[0-9a-f]+?\.js)"></script>')
     _swf_re = re.compile(r'//staticftv-a\.akamaihd\.net/player/bower_components/player_flash/dist/FranceTVNVPVFlashPlayer\.akamai-[0-9a-f]+\.swf')
     _hds_pv_data_re = re.compile(r"~data=.+?!")
@@ -90,12 +89,7 @@ class Pluzz(Plugin):
 
         # Retrieve URL page and search for video ID
         res = http.get(self.url)
-        if 'france.tv' in self.url:
-            match = self._pluzz_video_id_re.search(res.text)
-        elif 'ludo.fr' in self.url or 'zouzous.fr' in self.url:
-            match = self._jeunesse_video_id_re.search(res.text)
-        elif 'france3-regions.francetvinfo.fr' in self.url:
-            match = self._f3_regions_video_id_re.search(res.text)
+        match = self._pluzz_video_id_re.search(res.text) or self._other_video_id_re.search(res.text)
         if match is None:
             return
         video_id = match.group('video_id')
diff --git a/src/streamlink_cli/argparser.py b/src/streamlink_cli/argparser.py
index 40442d48..10c99a57 100644
--- a/src/streamlink_cli/argparser.py
+++ b/src/streamlink_cli/argparser.py
@@ -163,10 +163,8 @@ positional.add_argument(
     help="""
     A URL to attempt to extract streams from.
 
-    Usually, the protocol of http(s) URLs can be omitted ("https://"),
-    depending on the implementation of the plugin being used.
-
-    Alternatively, the URL can also be specified by using the --url option.
+    If it's a HTTP URL then "http://" can be omitted.
+    The URL can also be specified using the --url option.
     """
 )
 positional.add_argument(
@@ -177,13 +175,13 @@ positional.add_argument(
     help="""
     Stream to play.
 
-    Use "best" or "worst" for selecting the highest or lowest available quality.
+    Use "best" or "worst" for highest or lowest quality available.
 
     Fallback streams can be specified by using a comma-separated list:
 
       "720p,480p,best"
 
-    If no stream is specified and --default-stream is not used, then a
+    If no stream is specified and --default-stream is not used then a
     list of available streams will be printed.
     """
 )
@@ -226,8 +224,7 @@ general.add_argument(
     "--can-handle-url-no-redirect",
     metavar="URL",
     help="""
-    Same as --can-handle-url but without following redirects
-    when looking up the URL.
+    Same as --can-handle-url but without following redirects when looking up the URL.
     """
 )
 general.add_argument(
@@ -278,7 +275,7 @@ general.add_argument(
     help="""
     Enable or disable the automatic check for a new version of Streamlink.
 
-    Default is "no".
+    Default is no
     """
 )
 general.add_argument(
@@ -297,7 +294,7 @@ general.add_argument(
     subtitle and audio language.
 
     The locale is formatted as [language_code]_[country_code],
-    eg. en_US or es_ES.
+     eg. eg. en_US or es_ES
 
     Default is system locale.
     """
@@ -333,7 +330,7 @@ player.add_argument(
       streamlink --player "vlc --file-caching=5000" <url> <quality>
 
     As an alternative to this, see the --player-args parameter,
-    which does not log any custom player arguments.
+    which does not log the customer player arguments.
     """
 )
 player.add_argument(
@@ -343,8 +340,7 @@ player.add_argument(
     help="""
     This option allows you to customize the default arguments which
     are put together with the value of --player to create a command
-    to execute. Unlike the --player parameter, custom player
-    arguments will not be logged.
+    to execute.
 
     This value can contain formatting variables surrounded by curly
     braces, {{ and }}. If you need to include a brace character, it
@@ -361,11 +357,6 @@ player.add_argument(
     need to add arguments after the filename.
 
     Default is "{0}".
-
-    Example:
-
-      streamlink -p vlc -a "--play-and-exit {{filename}}" <url> <quality>
-
     """.format(DEFAULT_PLAYER_ARGUMENTS)
 )
 player.add_argument(
@@ -402,6 +393,7 @@ player.add_argument(
     This makes it possible to handle stream disconnects if your player
     is capable of reconnecting to a HTTP stream. This is usually
     done by setting your player to a "repeat mode".
+
     """
 )
 player.add_argument(
@@ -493,11 +485,9 @@ stream.add_argument(
     help="""
     A URL to attempt to extract streams from.
 
-    Usually, the protocol of http(s) URLs can be omitted (https://),
-    depending on the implementation of the plugin being used.
+    If it's a HTTP URL then "http://" can be omitted.
 
-    This is an alternative to setting the URL using a positional argument
-    and can be useful if set in a config file.
+    This is an alternative to setting the URL using a positional argument.
     """
 )
 stream.add_argument(
@@ -505,16 +495,7 @@ stream.add_argument(
     type=comma_list,
     metavar="STREAM",
     help="""
-    Stream to play.
-
-    Use "best" or "worst" for selecting the highest or lowest available quality.
-
-    Fallback streams can be specified by using a comma-separated list:
-
-      "720p,480p,best"
-
-    This is an alternative to setting the stream using a positional argument
-    and can be useful if set in a config file.
+    Open this stream when no stream argument is specified, e.g. "best".
     """
 )
 stream.add_argument(
@@ -547,8 +528,7 @@ stream.add_argument(
     The order will be used to separate streams when there are multiple
     streams with the same name but different stream types. Any stream type
     not listed will be omitted from the available streams list.  A ``*``
-    can be used as a wildcard to match any other type of stream,
-    eg. muxed-stream.
+    can be used as a wildcard to match any other type of stream, eg. muxed-stream.
 
     Default is "rtmp,hls,hds,http,akamaihd,*".
     """
@@ -703,11 +683,9 @@ transport.add_argument(
     type=str,
     metavar="CODE",
     help="""
-    Selects a specific audio source by language code
-    when multiple audio sources are available.
+    Selects a specific audio source, by language code, when multiple audio sources are available.
 
-    Note: This is only useful in special circumstances
-    where the regular locale option fails.
+    Note: This is only useful in special circumstances where the regular locale option fails.
     """)
 transport.add_argument(
     "--http-stream-timeout",
@@ -823,7 +801,6 @@ transport.add_argument(
 
     This is generic option used by streams not covered by other options,
     such as stream protocols specific to plugins, e.g. UStream.
-
     Default is 60.0.
     """)
 transport.add_argument(
@@ -837,7 +814,7 @@ transport.add_argument(
     "--subprocess-cmdline", "--cmdline", "-c",
     action="store_true",
     help="""
-    Print the command-line used internally to play the stream.
+    Print command-line used internally to play stream.
 
     This is only available on RTMP streams.
     """
@@ -867,7 +844,7 @@ transport.add_argument(
     "--ffmpeg-ffmpeg",
     metavar="FILENAME",
     help="""
-    FFMPEG is used to access or mux separate video and audio streams.
+    FFMPEG is used to access mux separate video and audio streams.
     You can specify the location of the ffmpeg executable if it is
     not in your PATH.
 
@@ -878,7 +855,7 @@ transport.add_argument(
     "--ffmpeg-verbose",
     action="store_true",
     help="""
-    Write the console output from ffmpeg to the console.
+    Write the console output from ffmpeg to the console
     """
 )
 transport.add_argument(
@@ -886,16 +863,14 @@ transport.add_argument(
     type=str,
     metavar="PATH",
     help="""
-    Path to write the output from the ffmpeg console.
+    Path to write the output from the ffmpeg console
     """
 )
 transport.add_argument(
     "--ffmpeg-video-transcode",
     metavar="CODEC",
     help="""
-    When muxing streams transcode the video to this CODEC.
-
-    Default is "copy".
+    When muxing streams transcode the video to this CODEC, defaults to copy (no transcode)
 
     Example: "h264"
     """
@@ -904,9 +879,7 @@ transport.add_argument(
     "--ffmpeg-audio-transcode",
     metavar="CODEC",
     help="""
-    When muxing streams transcode the audio to this CODEC.
-
-    Default is "copy".
+    When muxing streams transcode the audio to this CODEC, defaults to copy (no transcode)
 
     Example: "aac"
     """
@@ -919,7 +892,7 @@ http.add_argument(
     help="""
     A HTTP proxy to use for all HTTP requests.
 
-    Example: "http://hostname:port/"
+    Example: http://hostname:port/
     """
 )
 http.add_argument(
@@ -928,7 +901,7 @@ http.add_argument(
     help="""
     A HTTPS capable proxy to use for all HTTPS requests.
 
-    Example: "https://hostname:port/"
+    Example: http://hostname:port/
     """
 )
 http.add_argument(
@@ -1030,7 +1003,8 @@ plugin.add_argument(
     help="""
     Attempts to load plugins from these directories.
 
-    Multiple directories can be used by separating them with a semicolon.
+    Multiple directories can be used by separating them with a
+    semi-colon.
     """
 )
 plugin.add_argument(
@@ -1063,6 +1037,7 @@ plugin.add_argument(
     Note: This method is the old and clunky way of authenticating with
     Twitch, using --twitch-oauth-authenticate is the recommended and
     simpler way of doing it now.
+
     """
 )
 plugin.add_argument(
@@ -1111,7 +1086,7 @@ plugin.add_argument(
     metavar="SESSION_ID",
     help="""
     Set a specific session ID for crunchyroll, can be used to bypass
-    region restrictions.
+    region restrictions
     """
 )
 plugin.add_argument(
@@ -1132,8 +1107,7 @@ plugin.add_argument(
     "--schoolism-email",
     metavar="EMAIL",
     help="""
-    The email associated with your Schoolism account,
-    required to access any Schoolism stream.
+    The email associated with your Schoolism account, required to access any Schoolism stream.
     """
 )
 plugin.add_argument(
@@ -1149,23 +1123,23 @@ plugin.add_argument(
     default=1,
     metavar="PART",
     help="""
-    Play part number PART of the lesson.
+    Play part number PART of the lesson
 
-    Defaults is 1.
+    Defaults is 1
     """
 )
 plugin.add_argument(
     "--daisuki-mux-subtitles",
     action="store_true",
     help="""
-    Automatically mux available subtitles in to the output stream.
+    Automatically mux available subtitles in to the output stream
     """
 )
 plugin.add_argument(
     "--rtve-mux-subtitles",
     action="store_true",
     help="""
-    Automatically mux available subtitles in to the output stream.
+    Automatically mux available subtitles in to the output stream
     """
 )
 plugin.add_argument(
@@ -1174,16 +1148,16 @@ plugin.add_argument(
     choices=["en", "ja", "english", "japanese"],
     default="english",
     help="""
-    The audio language to use for Funimation streams; japanese or english.
+    The audio language to use for Funimation streams; japanese or english
 
-    Default is "english".
+    Default is english
     """
 )
 plugin.add_argument(
     "--funimation-mux-subtitles",
     action="store_true",
     help="""
-    Enable automatically including available subtitles in to the output stream.
+    Enable automatically including available subtitles in to the output stream
     """
 )
 plugin.add_argument(
@@ -1204,15 +1178,14 @@ plugin.add_argument(
     "--pluzz-mux-subtitles",
     action="store_true",
     help="""
-    Automatically mux available subtitles in to the output stream.
+    Automatically mux available subtitles in to the output stream
     """
 )
 plugin.add_argument(
     "--wwenetwork-email",
     metavar="EMAIL",
     help="""
-    The email associated with your WWE Network account,
-    required to access any WWE Network stream.
+    The email associated with your WWE Network account, required to access any WWE Network stream.
     """
 )
 plugin.add_argument(
@@ -1240,7 +1213,7 @@ plugin.add_argument(
     "--npo-subtitles",
     action="store_true",
     help="""
-    Include subtitles for the deaf or hard of hearing, if available.
+    Include subtitles for the deaf or hard of hearing, if available
     """
 )
 plugin.add_argument(
@@ -1257,6 +1230,20 @@ plugin.add_argument(
     A LiveEdu account password to use with --liveedu-email.
     """
 )
+plugin.add_argument(
+    "--pcyourfreetv-username",
+    metavar="USERNAME",
+    help="""
+    The username used to register with pc-yourfreetv.com.
+    """
+)
+plugin.add_argument(
+    "--pcyourfreetv-password",
+    metavar="PASSWORD",
+    help="""
+    A pc-yourfreetv.com account password to use with --pcyourfreetv-username.
+    """
+)
 plugin.add_argument(
     "--bbciplayer-username",
     metavar="USERNAME",
diff --git a/src/streamlink_cli/main.py b/src/streamlink_cli/main.py
index fd70b14d..e111b530 100644
--- a/src/streamlink_cli/main.py
+++ b/src/streamlink_cli/main.py
@@ -919,6 +919,17 @@ def setup_plugin_options():
     if liveedu_password:
         streamlink.set_plugin_option("liveedu", "password", liveedu_password)
 
+    if args.pcyourfreetv_username:
+        streamlink.set_plugin_option("pcyourfreetv", "username", args.pcyourfreetv_username)
+
+    if args.pcyourfreetv_username and not args.pcyourfreetv_password:
+        pcyourfreetv_password = console.askpass("Enter pc-yourfreetv.com password: ")
+    else:
+        pcyourfreetv_password = args.pcyourfreetv_password
+
+    if pcyourfreetv_password:
+        streamlink.set_plugin_option("pcyourfreetv", "password", pcyourfreetv_password)
+
     if args.bbciplayer_username:
         streamlink.set_plugin_option("bbciplayer", "username", args.bbciplayer_username)
 


Test patch:
diff --git a/tests/test_plugin_pcyourfreetv.py b/tests/test_plugin_pcyourfreetv.py
new file mode 100644
index 00000000..d1ee96dc
--- /dev/null
+++ b/tests/test_plugin_pcyourfreetv.py
@@ -0,0 +1,18 @@
+import unittest
+
+from streamlink.plugins.pcyourfreetv import PCYourFreeTV
+
+
+class TestPluginPCYourFreeTV(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/index_player.php?channel=das%20erste&page_id=41"))
+        self.assertTrue(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/index_player.php?channel=srf%20eins&page_id=41"))
+        self.assertTrue(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/index_player.php?channel=bbc%20one&page_id=41"))
+        self.assertTrue(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/index_player.php?channel=tf1&page_id=41"))
+
+        # shouldn't match
+        self.assertFalse(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/home.php"))
+        self.assertFalse(PCYourFreeTV.can_handle_url("http://pc-yourfreetv.com/index_livetv.php?page_id=1"))
+        self.assertFalse(PCYourFreeTV.can_handle_url("http://tvcatchup.com/"))
+        self.assertFalse(PCYourFreeTV.can_handle_url("http://youtube.com/"))
diff --git a/tests/test_plugin_pluzz.py b/tests/test_plugin_pluzz.py
index 9528479d..cac35e71 100644
--- a/tests/test_plugin_pluzz.py
+++ b/tests/test_plugin_pluzz.py
@@ -19,7 +19,6 @@ class TestPluginPluzz(unittest.TestCase):
         self.assertTrue(Pluzz.can_handle_url("http://www.ludo.fr/heros/il-etait-une-fois-la-vie"))
         self.assertTrue(Pluzz.can_handle_url("http://www.zouzous.fr/heros/oui-oui"))
         self.assertTrue(Pluzz.can_handle_url("http://www.zouzous.fr/heros/marsupilami-1"))
-        self.assertTrue(Pluzz.can_handle_url("http://france3-regions.francetvinfo.fr/bourgogne-franche-comte/tv/direct/franche-comte"))
 
         # shouldn't match
         self.assertFalse(Pluzz.can_handle_url("http://www.france.tv/"))
@@ -27,6 +26,5 @@ class TestPluginPluzz(unittest.TestCase):
         self.assertFalse(Pluzz.can_handle_url("http://www.ludo.fr/"))
         self.assertFalse(Pluzz.can_handle_url("http://www.ludo.fr/jeux"))
         self.assertFalse(Pluzz.can_handle_url("http://www.zouzous.fr/"))
-        self.assertFalse(Pluzz.can_handle_url("http://france3-regions.francetvinfo.fr/"))
         self.assertFalse(Pluzz.can_handle_url("http://www.tvcatchup.com/"))
         self.assertFalse(Pluzz.can_handle_url("http://www.youtube.com/"))