Repository: streamlink/streamlink

Issue:
Audio Spanish
I don't hear this video in Spanish, why?,  Is there any way I can keep it from being heard in English?
```
https://www.atresplayer.com/antena3/series/el-cuento-de-la-criada/temporada-1/capitulo-6-el-lugar-de-la-mujer_5b364bee7ed1a8dd360b5b7b/
```



### Logs

```
"C:\Program Files (x86)\Streamlink\bin\streamlink.exe" h
ttps://www.atresplayer.com/antena3/series/el-cuento-de-la-criada/temporada-1/cap
itulo-6-el-lugar-de-la-mujer_5b364bee7ed1a8dd360b5b7b/ best --player ffplay -l d
ebug
[cli][debug] OS:         Windows 7
[cli][debug] Python:     3.5.2
[cli][debug] Streamlink: 0.14.2
[cli][debug] Requests(2.19.1), Socks(1.6.7), Websocket(0.48.0)
[cli][info] Found matching plugin atresplayer for URL https://www.atresplayer.co
m/antena3/series/el-cuento-de-la-criada/temporada-1/capitulo-6-el-lugar-de-la-mu
jer_5b364bee7ed1a8dd360b5b7b/
[plugin.atresplayer][debug] API URL: https://api.atresplayer.com/client/v1/playe
r/episode/5b364bee7ed1a8dd360b5b7b
[plugin.atresplayer][debug] Stream source: https://geodeswowa3player.akamaized.n
et/vcg/_definst_/assets4/2018/06/29/7875368D-BF45-4012-9EBC-C4F78984B672/hls.smi
l/playlist.m3u8?pulse=assets4%2F2018%2F06%2F29%2F7875368D-BF45-4012-9EBC-C4F7898
4B672%2F%7C1531195580%7Cb6e79f7291966a25070a79138cd19cb9 (application/vnd.apple.
mpegurl)
[stream.hls][debug] Using external audio tracks for stream 1080p (language=es, n
ame=Spanish)
[stream.hls][debug] Using external audio tracks for stream 720p (language=es, na
me=Spanish)
[stream.hls][debug] Using external audio tracks for stream 480p (language=es, na
me=Spanish)
[stream.hls][debug] Using external audio tracks for stream 360p (language=es, na
me=Spanish)
[stream.hls][debug] Using external audio tracks for stream 240p (language=es, na
me=Spanish)
[plugin.atresplayer][debug] Stream source: https://geodeswowa3player.akamaized.n
et/vcg/_definst_/assets4/2018/06/29/7875368D-BF45-4012-9EBC-C4F78984B672/dash.sm
il/manifest_mvlist.mpd (application/dash+xml)
[cli][info] Available streams: 240p (worst), 240p+a128k, 360p, 360p+a128k, 480p,
 480p+a128k, 720p, 720p+a128k, 1080p, 1080p+a128k (best)
[cli][info] Starting player: ffplay
....................
....................
....................
```


Discussion:
You are using the `dash stream (.mpd)`, I think streamlink does not support audio channels there.

You could use the `hls stream (.m3u8)`, it should work there

`streamlink URL 1080p`

but you might encounter Audio Issues, because there streams are a bit broken

https://github.com/streamlink/streamlink/issues/1898
Does not work with m3u8
@Toni195 try adding the `--locale es_ES` option.
@beardypig
It doesn't work.

MPD: English.
M3U8: Without audio.
Link atresplayer.com: English.
F4M: Spanish.
> M3U8

```
[stream.hls][debug] Download of segment 17 complete
[stream.hls][debug] Adding segment 39 to queue
[stream.hls][debug] Download of segment 18 complete
[stream.hls][debug] Adding segment 40 to queue
[stream.hls][debug] Cutting off 12 bytes of garbage before decrypting
Exception in thread Thread-HLSStreamWriter:
Traceback (most recent call last):
  File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "src/streamlink/stream/segmented.py", line 167, in run
    self.write(segment, result)
  File "src/streamlink/stream/hls.py", line 135, in write
    self.reader.buffer.write(pkcs7_decode(decrypted_chunk))
  File "src/streamlink/stream/hls.py", line 31, in pkcs7_decode
    raise StreamError("Input is not padded or padding is corrupt, got padding size of {0}".format(val))
streamlink.exceptions.StreamError: Input is not padded or padding is corrupt, got padding size of 188
```

that may be because of my geographical location,
could you post a log of your Issue with the m3u8 stream.

but they are probably not fixable https://github.com/streamlink/streamlink/issues/1898

---

> MPD

it is english because the first stream gets overwritten by the 2nd with the same name.

https://github.com/streamlink/streamlink/blob/577617a2383cc75c6c94160b36aeccd7bd3b4bcf/src/streamlink/stream/dash.py#L207

this could be easily added, there are some solutions
- **ugly** , `_alt` for the 2nd stream
- **better**, `--locale` only show the first stream or your selected language

---

you could also use the mpd url directly in VLC, it works there.
We should consider the locale when picking the audio stream :)
@Toni195 can you try the `m3u8` version and add the `--ffmpeg-verbose` option? 

Resolving patch:
diff --git a/src/streamlink/stream/dash.py b/src/streamlink/stream/dash.py
--- a/src/streamlink/stream/dash.py
+++ b/src/streamlink/stream/dash.py
@@ -11,6 +11,7 @@
 from streamlink.stream.dash_manifest import MPD, sleeper, sleep_until, utc, freeze_timeline
 from streamlink.stream.ffmpegmux import FFMPEGMuxer
 from streamlink.stream.segmented import SegmentedStreamReader, SegmentedStreamWorker, SegmentedStreamWriter
+from streamlink.utils.l10n import Language
 
 log = logging.getLogger(__name__)
 
@@ -196,6 +197,31 @@ def parse_manifest(cls, session, url, **args):
         if not audio:
             audio = [None]
 
+        locale = session.localization
+        locale_lang = locale.language
+        lang = None
+        available_languages = set()
+
+        # if the locale is explicitly set, prefer that language over others
+        for aud in audio:
+            if aud and aud.lang:
+                available_languages.add(aud.lang)
+                try:
+                    if locale.explicit and aud.lang and Language.get(aud.lang) == locale_lang:
+                        lang = aud.lang
+                except LookupError:
+                    continue
+
+        if not lang:
+            # filter by the first language that appears
+            lang = audio[0] and audio[0].lang
+
+        log.debug("Available languages for DASH audio streams: {0} (using: {1})".format(", ".join(available_languages) or "NONE", lang or "n/a"))
+
+        # if the language is given by the stream, filter out other languages that do not match
+        if len(available_languages) > 1:
+            audio = list(filter(lambda a: a.lang is None or a.lang == lang, audio))
+
         for vid, aud in itertools.product(video, audio):
             stream = DASHStream(session, mpd, vid, aud, **args)
             stream_name = []


Test patch:
diff --git a/tests/streams/test_dash.py b/tests/streams/test_dash.py
--- a/tests/streams/test_dash.py
+++ b/tests/streams/test_dash.py
@@ -1,7 +1,4 @@
 import unittest
-import unittest
-from streamlink.stream.dash import DASHStreamWorker
-from tests.mock import MagicMock, patch, ANY, Mock, call
 
 from streamlink import PluginError
 from streamlink.stream import *
@@ -41,8 +38,8 @@ def test_parse_manifest_audio_only(self, mpdClass):
             Mock(adaptationSets=[
                 Mock(contentProtection=None,
                      representations=[
-                         Mock(id=1, mimeType="audio/mp4", bandwidth=128.0),
-                         Mock(id=2, mimeType="audio/mp4", bandwidth=256.0)
+                         Mock(id=1, mimeType="audio/mp4", bandwidth=128.0, lang='en'),
+                         Mock(id=2, mimeType="audio/mp4", bandwidth=256.0, lang='en')
                      ])
             ])
         ])
@@ -63,7 +60,7 @@ def test_parse_manifest_audio_single(self, mpdClass):
                      representations=[
                          Mock(id=1, mimeType="video/mp4", height=720),
                          Mock(id=2, mimeType="video/mp4", height=1080),
-                         Mock(id=3, mimeType="audio/aac", bandwidth=128.0)
+                         Mock(id=3, mimeType="audio/aac", bandwidth=128.0, lang='en')
                      ])
             ])
         ])
@@ -84,8 +81,8 @@ def test_parse_manifest_audio_multi(self, mpdClass):
                      representations=[
                          Mock(id=1, mimeType="video/mp4", height=720),
                          Mock(id=2, mimeType="video/mp4", height=1080),
-                         Mock(id=3, mimeType="audio/aac", bandwidth=128.0),
-                         Mock(id=4, mimeType="audio/aac", bandwidth=256.0)
+                         Mock(id=3, mimeType="audio/aac", bandwidth=128.0, lang='en'),
+                         Mock(id=4, mimeType="audio/aac", bandwidth=256.0, lang='en')
                      ])
             ])
         ])
@@ -98,6 +95,108 @@ def test_parse_manifest_audio_multi(self, mpdClass):
             sorted(["720p+a128k", "1080p+a128k", "720p+a256k", "1080p+a256k"])
         )
 
+    @patch('streamlink.stream.dash.MPD')
+    def test_parse_manifest_audio_multi_lang(self, mpdClass):
+        mpd = mpdClass.return_value = Mock(periods=[
+            Mock(adaptationSets=[
+                Mock(contentProtection=None,
+                     representations=[
+                         Mock(id=1, mimeType="video/mp4", height=720),
+                         Mock(id=2, mimeType="video/mp4", height=1080),
+                         Mock(id=3, mimeType="audio/aac", bandwidth=128.0, lang='en'),
+                         Mock(id=4, mimeType="audio/aac", bandwidth=128.0, lang='es')
+                     ])
+            ])
+        ])
+
+        streams = DASHStream.parse_manifest(self.session, self.test_url)
+        mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
+
+        self.assertSequenceEqual(
+            sorted(list(streams.keys())),
+            sorted(["720p", "1080p"])
+        )
+
+        self.assertEqual(streams["720p"].audio_representation.lang, "en")
+        self.assertEqual(streams["1080p"].audio_representation.lang, "en")
+
+    @patch('streamlink.stream.dash.MPD')
+    def test_parse_manifest_audio_multi_lang_alpha3(self, mpdClass):
+        mpd = mpdClass.return_value = Mock(periods=[
+            Mock(adaptationSets=[
+                Mock(contentProtection=None,
+                     representations=[
+                         Mock(id=1, mimeType="video/mp4", height=720),
+                         Mock(id=2, mimeType="video/mp4", height=1080),
+                         Mock(id=3, mimeType="audio/aac", bandwidth=128.0, lang='eng'),
+                         Mock(id=4, mimeType="audio/aac", bandwidth=128.0, lang='spa')
+                     ])
+            ])
+        ])
+
+        streams = DASHStream.parse_manifest(self.session, self.test_url)
+        mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
+
+        self.assertSequenceEqual(
+            sorted(list(streams.keys())),
+            sorted(["720p", "1080p"])
+        )
+
+        self.assertEqual(streams["720p"].audio_representation.lang, "eng")
+        self.assertEqual(streams["1080p"].audio_representation.lang, "eng")
+
+    @patch('streamlink.stream.dash.MPD')
+    def test_parse_manifest_audio_invalid_lang(self, mpdClass):
+        mpd = mpdClass.return_value = Mock(periods=[
+            Mock(adaptationSets=[
+                Mock(contentProtection=None,
+                     representations=[
+                         Mock(id=1, mimeType="video/mp4", height=720),
+                         Mock(id=2, mimeType="video/mp4", height=1080),
+                         Mock(id=3, mimeType="audio/aac", bandwidth=128.0, lang='en_no_voice'),
+                     ])
+            ])
+        ])
+
+        streams = DASHStream.parse_manifest(self.session, self.test_url)
+        mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
+
+        self.assertSequenceEqual(
+            sorted(list(streams.keys())),
+            sorted(["720p", "1080p"])
+        )
+
+        self.assertEqual(streams["720p"].audio_representation.lang, "en_no_voice")
+        self.assertEqual(streams["1080p"].audio_representation.lang, "en_no_voice")
+
+    @patch('streamlink.stream.dash.MPD')
+    def test_parse_manifest_audio_multi_lang_locale(self, mpdClass):
+        self.session.localization.language.alpha2 = "es"
+        self.session.localization.explicit = True
+
+        mpd = mpdClass.return_value = Mock(periods=[
+            Mock(adaptationSets=[
+                Mock(contentProtection=None,
+                     representations=[
+                         Mock(id=1, mimeType="video/mp4", height=720),
+                         Mock(id=2, mimeType="video/mp4", height=1080),
+                         Mock(id=3, mimeType="audio/aac", bandwidth=128.0, lang='en'),
+                         Mock(id=4, mimeType="audio/aac", bandwidth=128.0, lang='es')
+                     ])
+            ])
+        ])
+
+        streams = DASHStream.parse_manifest(self.session, self.test_url)
+        mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
+
+        self.assertSequenceEqual(
+            sorted(list(streams.keys())),
+            sorted(["720p", "1080p"])
+        )
+
+        self.assertEqual(streams["720p"].audio_representation.lang, "es")
+        self.assertEqual(streams["1080p"].audio_representation.lang, "es")
+
     @patch('streamlink.stream.dash.MPD')
     def test_parse_manifest_drm(self, mpdClass):
         mpd = mpdClass.return_value = Mock(periods=[Mock(adaptationSets=[Mock(contentProtection="DRM")])])
@@ -122,7 +221,7 @@ def test_stream_open_video_only(self, muxer, reader):
     @patch('streamlink.stream.dash.DASHStreamReader')
     @patch('streamlink.stream.dash.FFMPEGMuxer')
     def test_stream_open_video_audio(self, muxer, reader):
-        stream = DASHStream(self.session, Mock(), Mock(id=1, mimeType="video/mp4"), Mock(id=2, mimeType="audio/mp3"))
+        stream = DASHStream(self.session, Mock(), Mock(id=1, mimeType="video/mp4"), Mock(id=2, mimeType="audio/mp3", lang='en'))
         open_reader = reader.return_value = Mock()
 
         stream.open()
@@ -202,7 +301,7 @@ def test_static(self, sleep):
     @patch("streamlink.stream.dash_manifest.time.sleep")
     def test_duplicate_rep_id(self, sleep):
         representation_vid = Mock(id=1, mimeType="video/mp4", height=720)
-        representation_aud = Mock(id=1, mimeType="audio/aac")
+        representation_aud = Mock(id=1, mimeType="audio/aac", lang='en')
 
         mpd = Mock(dynamic=False,
                    publishTime=1,