Repository: streamlink/streamlink

Issue:
Schoolism plugin not working with videos which have subtitles
<!--
Thanks for reporting a bug!
USE THE TEMPLATE. Otherwise your bug report may be rejected.

First, see the contribution guidelines:
https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink

Also check the list of open and closed bug reports:
https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22bug%22

Please see the text preview to avoid unnecessary formatting errors.
-->


## Bug Report

<!-- Replace [ ] with [x] in order to check the box -->
- [x] This is a bug report and I have read the contribution guidelines.


### Description

<!-- Explain the bug as thoroughly as you can. Don't leave out information which is necessary for us to reproduce and debug this issue. -->

Schoolism plugin works fine with other courses except the two courses below. it'll get error when opening video with subtitles.
- Digital Painting with Craig Mullins 
- Drawing Fundamentals with Thomas Fluharty

I'm using streamlink 1.1.1 in python 2.7. I think I have detected the reason why this bug happens but unfortunately I'm not familiar with python and streamlink itself so I may need some help:/
The javascript variable "allVideos" (which stores video URL link and other information) in HTML code is different when video contains subtitles.
  - Videos without subtitles:
```
var allVideos=[
{sources:[{type:"application/x-mpegurl",src:"https://d8u31iyce9xic.cloudfront.net/14/1/part1.m3u8?Policy=CiAgICAgICAgICAgIHsgCiAgICAgICAgICAgICAgICJTdGF0ZW1lbnQiOiBbCiAgICAgICAgICAgICAgICAgIHsgCiAgICAgICAgICAgICAgICAgICAgICJSZXNvdXJjZSI6Imh0dHBzOi8vZDh1MzFpeWNlOXhpYy5jbG91ZGZyb250Lm5ldC8xNC8xLyoiLAogICAgICAgICAgICAgICAgICAgICAiQ29uZGl0aW9uIjp7CiAgICAgICAgICAgICAgICAgICAgICAgICJEYXRlR3JlYXRlclRoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTU1Njk2Mjc3MH0sCiAgICAgICAgICAgICAgICAgICAgICAgICJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTU1NzA0OTE3MH0KICAgICAgICAgICAgICAgICAgICAgfSAKICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICBdIAogICAgICAgICAgICB9CiAgICAgICAg&Signature=HE0~u~zThmbVd0QMbmSuHe3Pxxcg4-ZZXf6N8Fyczbae5Ov1BSfuJgWOeGPk1Y7zuVVE2vO5TlEWNR2aVIvQIAkVQNuaohOf9xuOdnn96SFq6J-LxTEPZthTvhbb3SJeDLWzdEj9-f93pfptKsTd8gABIAltfM1A-~Wjv~jiMVd19T0GJA9PUMNIeNkY7snBE7bMT85ARGrrHynWR71g31U1O-JvfAcnHLEcql-kc~5ZbV0HiJlvqicYhyJUsLzO3lR7sBihjmdq2FDH3-ckhzX3LJhJ9mpZYqZJ6r5DF1LApM870Ct-0KOe84EqdkCxUO4eaCd2ggJx8XLGbibaGQ__&Key-Pair-Id=APKAJHWRPDT6BGTTH7LA",title:"Gesture Drawing - Lesson 1 - Part 1",playlistTitle:"Part 1",}],},
{sources:[{type:"application/x-mpegurl",src:"https://d8u31iyce9xic.cloudfront.net/14/1/part2.m3u8?Policy=CiAgICAgICAgICAgIHsgCiAgICAgICAgICAgICAgICJTdGF0ZW1lbnQiOiBbCiAgICAgICAgICAgICAgICAgIHsgCiAgICAgICAgICAgICAgICAgICAgICJSZXNvdXJjZSI6Imh0dHBzOi8vZDh1MzFpeWNlOXhpYy5jbG91ZGZyb250Lm5ldC8xNC8xLyoiLAogICAgICAgICAgICAgICAgICAgICAiQ29uZGl0aW9uIjp7CiAgICAgICAgICAgICAgICAgICAgICAgICJEYXRlR3JlYXRlclRoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTU1Njk2Mjc3MH0sCiAgICAgICAgICAgICAgICAgICAgICAgICJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTU1NzA0OTE3MH0KICAgICAgICAgICAgICAgICAgICAgfSAKICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICBdIAogICAgICAgICAgICB9CiAgICAgICAg&Signature=HE0~u~zThmbVd0QMbmSuHe3Pxxcg4-ZZXf6N8Fyczbae5Ov1BSfuJgWOeGPk1Y7zuVVE2vO5TlEWNR2aVIvQIAkVQNuaohOf9xuOdnn96SFq6J-LxTEPZthTvhbb3SJeDLWzdEj9-f93pfptKsTd8gABIAltfM1A-~Wjv~jiMVd19T0GJA9PUMNIeNkY7snBE7bMT85ARGrrHynWR71g31U1O-JvfAcnHLEcql-kc~5ZbV0HiJlvqicYhyJUsLzO3lR7sBihjmdq2FDH3-ckhzX3LJhJ9mpZYqZJ6r5DF1LApM870Ct-0KOe84EqdkCxUO4eaCd2ggJx8XLGbibaGQ__&Key-Pair-Id=APKAJHWRPDT6BGTTH7LA",title:"Gesture Drawing - Lesson 1 - Part 2",playlistTitle:"Part 2",}]}
];

```
  - Videos with subtitles:
```
var allVideos=[
{sources:[{type:"application/x-mpegurl",src:"https://d8u31iyce9xic.cloudfront.net/44/2/part1.m3u8?Policy=CiAgICAgICAgICAgIHsgCiAgICAgICAgICAgICAgICJTdGF0ZW1lbnQiOiBbCiAgICAgICAgICAgICAgICAgIHsgCiAgICAgICAgICAgICAgICAgICAgICJSZXNvdXJjZSI6Imh0dHBzOi8vZDh1MzFpeWNlOXhpYy5jbG91ZGZyb250Lm5ldC80NC8yLyoiLAogICAgICAgICAgICAgICAgICAgICAiQ29uZGl0aW9uIjp7CiAgICAgICAgICAgICAgICAgICAgICAgICJEYXRlR3JlYXRlclRoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTU1Njk2MjkyMH0sCiAgICAgICAgICAgICAgICAgICAgICAgICJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTU1NzA0OTMyMH0KICAgICAgICAgICAgICAgICAgICAgfSAKICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICBdIAogICAgICAgICAgICB9CiAgICAgICAg&Signature=WysPwDFhH0K75hoVq~XPrOjp-Lv5SbNTaM~x0NGpLUJTmzHw83Zz~HAqbpZPaeJiKRaEieC2y7uYJzzLlOdZvPWLyCoy76-5nav1bcihyD2DeJ7lIdCmsNlJarlODdxot3HaO0E7V2rv7WikNol5DmuL-dRsld8Y8JUWm4gJT5SSLVOSBWlCHI~TPrdDyjujMX1k2cLjLc7mHeOuBJOrJGWwwtb3DB48h8rjavZ6R5SXP3YU8uUDow8W74H27nKFd1mhd6ic0CBMVVEucL91ZkyGklNpoRTDqK2nHm6gZk7VWr6it7~XGtYpmHqOJf7BT0p1qaKHLNZnms~S3rKq0A__&Key-Pair-Id=APKAJHWRPDT6BGTTH7LA",title:"Digital Painting - Lesson 2 - Part 1",playlistTitle:"Part 1",}],        subtitles: [{
            "default": true,
            kind: "subtitles", srclang: "en", label: "English",
            src:  "https://s3.amazonaws.com/schoolism-encoded/44/subtitles/2/2-1.vtt",
        }],
        },
{sources:[{type:"application/x-mpegurl",src:"https://d8u31iyce9xic.cloudfront.net/44/2/part2.m3u8?Policy=CiAgICAgICAgICAgIHsgCiAgICAgICAgICAgICAgICJTdGF0ZW1lbnQiOiBbCiAgICAgICAgICAgICAgICAgIHsgCiAgICAgICAgICAgICAgICAgICAgICJSZXNvdXJjZSI6Imh0dHBzOi8vZDh1MzFpeWNlOXhpYy5jbG91ZGZyb250Lm5ldC80NC8yLyoiLAogICAgICAgICAgICAgICAgICAgICAiQ29uZGl0aW9uIjp7CiAgICAgICAgICAgICAgICAgICAgICAgICJEYXRlR3JlYXRlclRoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTU1Njk2MjkyMH0sCiAgICAgICAgICAgICAgICAgICAgICAgICJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTU1NzA0OTMyMH0KICAgICAgICAgICAgICAgICAgICAgfSAKICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICBdIAogICAgICAgICAgICB9CiAgICAgICAg&Signature=WysPwDFhH0K75hoVq~XPrOjp-Lv5SbNTaM~x0NGpLUJTmzHw83Zz~HAqbpZPaeJiKRaEieC2y7uYJzzLlOdZvPWLyCoy76-5nav1bcihyD2DeJ7lIdCmsNlJarlODdxot3HaO0E7V2rv7WikNol5DmuL-dRsld8Y8JUWm4gJT5SSLVOSBWlCHI~TPrdDyjujMX1k2cLjLc7mHeOuBJOrJGWwwtb3DB48h8rjavZ6R5SXP3YU8uUDow8W74H27nKFd1mhd6ic0CBMVVEucL91ZkyGklNpoRTDqK2nHm6gZk7VWr6it7~XGtYpmHqOJf7BT0p1qaKHLNZnms~S3rKq0A__&Key-Pair-Id=APKAJHWRPDT6BGTTH7LA",title:"Digital Painting - Lesson 2 - Part 2",playlistTitle:"Part 2",}],        subtitles: [{
            "default": true,
            kind: "subtitles", srclang: "en", label: "English",
            src:  "https://s3.amazonaws.com/schoolism-encoded/44/subtitles/2/2-2.vtt",
        }]
];
```


### Expected / Actual behavior

<!-- What do you expect to happen, and what is actually happening? -->

The plugin works whether videos have subtitles or not. / It gets error with videos with subtitles.

### Reproduction steps / Explicit stream URLs to test

<!-- How can we reproduce this? Please note the exact steps below using the list format supplied. If you need more steps please add them. -->

1. Login to Schoolism and switch course to "Digital Painting with Craig Mullins"
2. Open lesson 1 part 1 with streamlink, for example. The course page URL is "https://www.schoolism.com/watchLesson.php?type=st&id=506

### Log output

<!--
TEXT LOG OUTPUT IS REQUIRED for a bug report!
Use the `--loglevel debug` parameter and avoid using parameters which suppress log output.
https://streamlink.github.io/cli.html#cmdoption-l

Make sure to **remove usernames and passwords**
You can copy the output to https://gist.github.com/ or paste it below.
-->

```
[cli][debug] OS:         Linux-4.4.0-17134-Microsoft-x86_64-with-Ubuntu-18.04-bionic
[cli][debug] Python:     2.7.15rc1
[cli][debug] Streamlink: 1.1.1
[cli][debug] Requests(2.21.0), Socks(1.6.7), Websocket(0.56.0)
[cli][info] Found matching plugin schoolism for URL https://www.schoolism.com/watchLesson.php?type=st&id=506
[cli][debug] Plugin specific arguments:
[cli][debug]  --schoolism-email=(email omitted)
[cli][debug]  --schoolism-password=(password omitted)
[cli][debug]  --schoolism-part=1 (part)
[plugin.schoolism][debug] Logged in to Schoolism as (email omitted)
error: Unable to parse JSON: Expecting property name enclosed in double quotes: line 5 column 9 (char 1337) (u'[{"sources":[{"type":"application ...)
```


### Additional comments, screenshots, etc.
I have a schoolism account valid till August so I can help testing.
Or if someone can give me some hint fixing this issue, I can make a merge request myself. Thank you!

[Love Streamlink? Please consider supporting our collective. Thanks!](https://opencollective.com/streamlink/donate)



Discussion:

Resolving patch:
diff --git a/src/streamlink/plugins/schoolism.py b/src/streamlink/plugins/schoolism.py
--- a/src/streamlink/plugins/schoolism.py
+++ b/src/streamlink/plugins/schoolism.py
@@ -17,8 +17,9 @@ class Schoolism(Plugin):
     url_re = re.compile(r"https?://(?:www\.)?schoolism\.com/(viewAssignment|watchLesson).php")
     login_url = "https://www.schoolism.com/index.php"
     key_time_url = "https://www.schoolism.com/video-html/key-time.php"
-    playlist_re = re.compile(r"var allVideos\s*=\s*(\[\{.*\}]);", re.DOTALL)
+    playlist_re = re.compile(r"var allVideos\s*=\s*(\[.*\]);", re.DOTALL)
     js_to_json = partial(re.compile(r'(?!<")(\w+):(?!/)').sub, r'"\1":')
+    fix_brackets = partial(re.compile(r',\s*\}').sub, r'}')
     playlist_schema = validate.Schema(
         validate.transform(playlist_re.search),
         validate.any(
@@ -26,7 +27,7 @@ class Schoolism(Plugin):
             validate.all(
                 validate.get(1),
                 validate.transform(js_to_json),
-                validate.transform(lambda x: x.replace(",}", "}")),  # remove invalid ,
+                validate.transform(fix_brackets),  # remove invalid ,
                 validate.transform(parse_json),
                 [{
                     "sources": validate.all([{


Test patch:
diff --git a/tests/plugins/test_schoolism.py b/tests/plugins/test_schoolism.py
--- a/tests/plugins/test_schoolism.py
+++ b/tests/plugins/test_schoolism.py
@@ -17,3 +17,37 @@ def test_can_handle_url_negative(self):
         ]
         for url in should_not_match:
             self.assertFalse(Schoolism.can_handle_url(url))
+
+    def test_playlist_parse_subs(self):
+        with_subs = """var allVideos=[
+            {sources:[{type:"application/x-mpegurl",src:"https://d8u31iyce9xic.cloudfront.net/44/2/part1.m3u8?Policy=TOKEN&Signature=TOKEN&Key-Pair-Id=TOKEN",title:"Digital Painting - Lesson 2 - Part 1",playlistTitle:"Part 1",}],        subtitles: [{
+                    "default": true,
+                    kind: "subtitles", srclang: "en", label: "English",
+                    src:  "https://s3.amazonaws.com/schoolism-encoded/44/subtitles/2/2-1.vtt",
+                }],
+                },
+            {sources:[{type:"application/x-mpegurl",src:"https://d8u31iyce9xic.cloudfront.net/44/2/part2.m3u8?Policy=TOKEN&Signature=TOKEN&Key-Pair-Id=TOKEN",title:"Digital Painting - Lesson 2 - Part 2",playlistTitle:"Part 2",}],        subtitles: [{
+                    "default": true,
+                    kind: "subtitles", srclang: "en", label: "English",
+                    src:  "https://s3.amazonaws.com/schoolism-encoded/44/subtitles/2/2-2.vtt",
+                }]
+            }];
+            """
+
+        data = Schoolism.playlist_schema.validate(with_subs)
+
+        self.assertIsNotNone(data)
+        self.assertEqual(2, len(data))
+
+
+    def test_playlist_parse(self):
+        without_subs = """var allVideos=[
+            {sources:[{type:"application/x-mpegurl",src:"https://d8u31iyce9xic.cloudfront.net/14/1/part1.m3u8?Policy=TOKEN&Signature=TOKEN&Key-Pair-Id=TOKEN",title:"Gesture Drawing - Lesson 1 - Part 1",playlistTitle:"Part 1",}],},
+            {sources:[{type:"application/x-mpegurl",src:"https://d8u31iyce9xic.cloudfront.net/14/1/part2.m3u8?Policy=TOKEN&Signature=TOKEN&Key-Pair-Id=TOKEN",title:"Gesture Drawing - Lesson 1 - Part 2",playlistTitle:"Part 2",}]}
+            ];
+        """
+
+        data = Schoolism.playlist_schema.validate(without_subs)
+
+        self.assertIsNotNone(data)
+        self.assertEqual(2, len(data))