Repository: spaam/svtplay-dl

Issue:
-A --all-last=4  funkar i 4.69 men EJ i nya 4.72
svtplay-dl -A --all-last=4 -S --all-subtitles https://www.svtplay.se/video/e4dakqE/ett-enklare-liv-1/4-ensam-vid-polcirkeln

Med 4.72 börjar den hämta från allra första början, dvs från första i S01, men med 4.69 funkar det som det ska, dvs den hämtar från S06E04 och resten, alltså totalt de 4 sista avsnitten.

Många säsonger e're som man kan se på 

https://www.svtplay.se/video/jLJmbRd/ett-enklare-liv

Så alla dem vill man inte ha ännu en gång...

Discussion:

Solution patch:
diff --git a/lib/svtplay_dl/fetcher/__init__.py b/lib/svtplay_dl/fetcher/__init__.py
index 80320113..6c59649e 100644
--- a/lib/svtplay_dl/fetcher/__init__.py
+++ b/lib/svtplay_dl/fetcher/__init__.py
@@ -20,11 +20,12 @@ class VideoRetriever:
         codec = kwargs.pop("codec", "h264")
         self.resolution = kwargs.pop("resolution", "")
         self.language = kwargs.pop("language", "")
-        self.audio_role = kwargs.pop("role", "main")
+        self.audio_role = kwargs.pop("role", "main")  # remove..
+        self.video_role = kwargs.pop("video_role", "main")
         self.format = f"{codec}-{channels}" if channels else codec
 
     def __repr__(self):
-        return f"<Video(fetcher={self.__class__.__name__}, bitrate={self.bitrate} format={self.format}, language={self.language}, audio_role={self.audio_role}, resolution={self.resolution}>"
+        return f"<Video(fetcher={self.__class__.__name__}, bitrate={self.bitrate} format={self.format}, language={self.language}, role={self.video_role}, resolution={self.resolution}>"
 
     @property
     def name(self):
diff --git a/lib/svtplay_dl/fetcher/dash.py b/lib/svtplay_dl/fetcher/dash.py
index dcc5b74b..af4933d0 100644
--- a/lib/svtplay_dl/fetcher/dash.py
+++ b/lib/svtplay_dl/fetcher/dash.py
@@ -134,6 +134,7 @@ def adaptionset(attributes, elements, url, baseurl=None):
         template = element.find("{urn:mpeg:dash:schema:mpd:2011}SegmentTemplate")
         represtation = element.findall(".//{urn:mpeg:dash:schema:mpd:2011}Representation")
         role_elemets = element.findall(".//{urn:mpeg:dash:schema:mpd:2011}Role")
+        accessibility = element.findall(".//{urn:mpeg:dash:schema:mpd:2011}Accessibility[@schemeIdUri='urn:se:svt:accessibility']")
 
         codecs = None
         if "codecs" in element.attrib:
@@ -143,6 +144,8 @@ def adaptionset(attributes, elements, url, baseurl=None):
             lang = element.attrib["lang"]
         if role_elemets:
             role = role_elemets[0].attrib["value"]
+            if accessibility:
+                role = f'{role}-{accessibility[0].get("value")}'
 
         resolution = ""
         if "maxWidth" in element.attrib and "maxHeight" in element.attrib:
@@ -230,6 +233,7 @@ def _dashparse(config, text, url, output, cookies, **kwargs):
     loutput = copy.copy(output)
     loutput["ext"] = "mp4"
     attributes = DASHattibutes()
+    subtitles = []
 
     text = re.sub("&(?!amp;)", "&amp;", text)
     xml = ET.XML(text)
@@ -286,10 +290,11 @@ def _dashparse(config, text, url, output, cookies, **kwargs):
                 channels=audio["channels"],
                 resolution=video["resolution"],
                 language=audio["lang"],
-                role=audio["role"],
+                video_role=video["role"],
                 **kwargs,
             )
     for sub in subtitles:
+        continue
         if len(subtitles) > 1:
             if sub["role"] and sub["role"] != "main" and sub["role"] != "subtitle":
                 sub["lang"] = f'{sub["lang"]}-{sub["role"]}'
diff --git a/lib/svtplay_dl/fetcher/hls.py b/lib/svtplay_dl/fetcher/hls.py
index a9c01ec1..beaea2c1 100644
--- a/lib/svtplay_dl/fetcher/hls.py
+++ b/lib/svtplay_dl/fetcher/hls.py
@@ -62,6 +62,7 @@ def _hlsparse(config, text, url, output, **kwargs):
     segments = None
 
     if m3u8.master_playlist:
+        video_group = None
         for i in m3u8.master_playlist:
             audio_url = None
             vcodec = None
@@ -71,23 +72,33 @@ def _hlsparse(config, text, url, output, **kwargs):
             resolution = ""
             if i["TAG"] == "EXT-X-MEDIA":
                 if i["TYPE"] and i["TYPE"] != "SUBTITLES":
+                    uri = None
+                    if i["GROUP-ID"] not in media:
+                        media[i["GROUP-ID"]] = []
+                    if i["TYPE"] == "VIDEO":
+                        video_group = i["GROUP-ID"]
+
                     if "URI" in i:
                         if segments is None:
                             segments = True
-                        if i["GROUP-ID"] not in media:
-                            media[i["GROUP-ID"]] = []
-                        if "CHANNELS" in i:
-                            if i["CHANNELS"] == "6":
-                                chans = "51"
-                        if "LANGUAGE" in i:
-                            language = i["LANGUAGE"]
-                        if "AUTOSELECT" in i and i["AUTOSELECT"].upper() == "YES":
-                            role = "main"
-                        else:
-                            role = "alt"
-                        media[i["GROUP-ID"]].append([i["URI"], chans, language, role])
+                        uri = i["URI"]
                     else:
                         segments = False
+
+                    if "CHANNELS" in i:
+                        if i["CHANNELS"] == "6":
+                            chans = "51"
+                    if "LANGUAGE" in i:
+                        language = i["LANGUAGE"]
+                    if "AUTOSELECT" in i and i["AUTOSELECT"].upper() == "YES" and "DEFAULT" in i and i["DEFAULT"].upper() == "YES":
+                        role = "main"
+                    else:
+                        role = "alternate"
+                        if "CHARACTERISTICS" in i:
+                            role = f'{role}-{i["CHARACTERISTICS"].replace("se.svt.accessibility.", "")}'
+
+                    media[i["GROUP-ID"]].append([uri, chans, language, role])
+
                 if i["TYPE"] == "SUBTITLES":
                     if "URI" in i:
                         caption = None
@@ -121,35 +132,68 @@ def _hlsparse(config, text, url, output, **kwargs):
                 if "AUDIO" in i:
                     audio_group = i["AUDIO"]
                 urls = get_full_url(i["URI"], url)
-                videos[bit_rate] = [urls, resolution, vcodec, audio_group]
+                videos[bit_rate] = [urls, resolution, vcodec, audio_group, video_group]
             else:
                 continue  # Needs to be changed to utilise other tags.
 
         for bit_rate in list(videos.keys()):
-            urls, resolution, vcodec, audio_group = videos[bit_rate]
+            urls, resolution, vcodec, audio_group, video_group = videos[bit_rate]
             if audio_group and media:
-                for group in media[audio_group]:
-                    audio_url = get_full_url(group[0], url)
-                    chans = group[1] if audio_url else channels
-                    codec = vcodec if vcodec else codec
-
-                    yield HLS(
-                        copy.copy(config),
-                        urls,
-                        bit_rate,
-                        cookies=cookies,
-                        keycookie=keycookie,
-                        authorization=authorization,
-                        audio=audio_url,
-                        output=loutput,
-                        segments=bool(segments),
-                        channels=chans,
-                        codec=codec,
-                        resolution=resolution,
-                        language=group[2],
-                        role=group[3],
-                        **kwargs,
-                    )
+                if video_group and video_group in media:
+                    for vgroup in media[video_group]:
+                        vurl = urls
+                        video_role = vgroup[3]
+                        if vgroup[0]:
+                            vurl = vgroup[0]
+                        for group in media[audio_group]:
+                            audio_url = get_full_url(group[0], url)
+                            chans = group[1] if audio_url else channels
+                            codec = vcodec if vcodec else codec
+
+                            yield HLS(
+                                copy.copy(config),
+                                vurl,
+                                bit_rate,
+                                cookies=cookies,
+                                keycookie=keycookie,
+                                authorization=authorization,
+                                audio=audio_url,
+                                video_role=video_role,
+                                output=loutput,
+                                segments=bool(segments),
+                                channels=chans,
+                                codec=codec,
+                                resolution=resolution,
+                                language=group[2],
+                                **kwargs,
+                            )
+                else:
+                    vurl = urls
+                    video_role = "main"
+                    for group in media[audio_group]:
+                        if group[0]:
+                            audio_url = get_full_url(group[0], url)
+                        chans = group[1] if audio_url else channels
+                        codec = vcodec if vcodec else codec
+
+                        yield HLS(
+                            copy.copy(config),
+                            vurl,
+                            bit_rate,
+                            cookies=cookies,
+                            keycookie=keycookie,
+                            authorization=authorization,
+                            audio=audio_url,
+                            video_role=video_role,
+                            output=loutput,
+                            segments=bool(segments),
+                            channels=chans,
+                            codec=codec,
+                            resolution=resolution,
+                            language=group[2],
+                            role=group[3],
+                            **kwargs,
+                        )
             else:
                 chans = channels
                 codec = vcodec if vcodec else codec
diff --git a/lib/svtplay_dl/service/svtplay.py b/lib/svtplay_dl/service/svtplay.py
index de62b8bd..35164e40 100644
--- a/lib/svtplay_dl/service/svtplay.py
+++ b/lib/svtplay_dl/service/svtplay.py
@@ -290,6 +290,9 @@ class Svtplay(Service, MetadataThumbMixin):
         if not episodes:
             logging.error("Can't find any videos.")
         else:
+            if not self.config.get("reverse_list"):
+                episodes = episodes[::-1]
+
             if config.get("all_last") > 0:
                 return episodes[: config.get("all_last")]
         return episodes
@@ -404,10 +407,11 @@ class Svtplay(Service, MetadataThumbMixin):
 
             validfrom = episode["item"]["validFrom"]
             if "+" in validfrom:
+                timeformat = "%Y-%m-%dT%H:%M:%S.%f%z" if "." in episode["item"]["validFrom"] else "%Y-%m-%dT%H:%M:%S%z"
                 date = time.mktime(
                     datetime.datetime.strptime(
                         _fix_broken_timezone_implementation(episode["item"]["validFrom"].replace("Z", "")),
-                        "%Y-%m-%dT%H:%M:%S%z",
+                        timeformat,
                     ).timetuple(),
                 )
             else:
diff --git a/lib/svtplay_dl/service/tv4play.py b/lib/svtplay_dl/service/tv4play.py
index c3e9096e..0b8f598b 100644
--- a/lib/svtplay_dl/service/tv4play.py
+++ b/lib/svtplay_dl/service/tv4play.py
@@ -137,11 +137,13 @@ class Tv4play(Service, OpenGraphThumbMixin):
         for item in items:
             episodes.append(f"https://www.tv4play.se/video/{item}")
 
-        episodes = episodes[::-1]
-        if config.get("all_last") > 0:
-            return episodes[: config.get("all_last")]
         if not episodes:
             logging.warning("Can't find any videos")
+        else:
+            if not self.config.get("reverse_list"):
+                episodes = episodes[::-1]
+            if config.get("all_last") > 0:
+                return episodes[: config.get("all_last")]
         return episodes
 
     def _get_seriesid(self, data, jansson):
diff --git a/lib/svtplay_dl/utils/parser.py b/lib/svtplay_dl/utils/parser.py
index 0eaa09fe..b1fb49c7 100644
--- a/lib/svtplay_dl/utils/parser.py
+++ b/lib/svtplay_dl/utils/parser.py
@@ -71,7 +71,7 @@ def gen_parser(version="unknown"):
     )
     general.add_argument("--config", dest="configfile", metavar="configfile", default=CONFIGFILE, help="Specify configuration file")
     general.add_argument("-f", "--force", action="store_true", dest="force", default=False, help="overwrite if file exists already")
-    general.add_argument("-r", "--resume", action="store_true", dest="resume", default=False, help="resume a download (RTMP obsolete)")
+    # general.add_argument("-r", "--resume", action="store_true", dest="resume", default=False, help="resume a download (RTMP obsolete)")
     general.add_argument("-l", "--live", action="store_true", dest="live", default=False, help="enable for live streams (RTMP based ones)")
     general.add_argument("-c", "--capture_time", default=-1, type=int, metavar="capture_time", help="define capture time in minutes of a live stream")
     general.add_argument("-s", "--silent", action="store_true", dest="silent", default=False, help="be less verbose")
@@ -203,10 +203,10 @@ def gen_parser(version="unknown"):
         help="Choose the language of the audio (it can override the default one), --list-quality to show which one to choose from",
     )
     quality.add_argument(
-        "--audio-role",
-        dest="audio_role",
-        default=None,
-        help="Choose the role of the audio (it can override the default one), --list-quality to show which one to choose from",
+        "--video-role",
+        dest="video_role",
+        default="main",
+        help="Choose the role of the video (it can override the default one), --list-quality to show which one to choose from",
     )
     quality.add_argument(
         "--resolution",
@@ -272,6 +272,7 @@ def gen_parser(version="unknown"):
     alleps.add_argument("-A", "--all-episodes", action="store_true", dest="all_episodes", default=False, help="try to download all episodes")
     alleps.add_argument("--all-last", dest="all_last", default=-1, type=int, metavar="NN", help="get last NN episodes instead of all episodes")
     alleps.add_argument("--include-clips", dest="include_clips", default=False, action="store_true", help="include clips from websites when using -A")
+    alleps.add_argument("-R", "--reverse", action="store_true", dest="reverse_list", default=False, help="Reverse download order")
 
     cmorep = parser.add_argument_group("C More")
     cmorep.add_argument("--cmore-operatorlist", dest="cmoreoperatorlist", default=False, action="store_true", help="show operatorlist for cmore")
@@ -318,7 +319,7 @@ def setup_defaults():
     options.set("output", None)
     options.set("subfolder", False)
     options.set("configfile", CONFIGFILE)
-    options.set("resume", False)
+    # options.set("resume", False)
     options.set("live", False)
     options.set("capture_time", -1)
     options.set("silent", False)
@@ -354,7 +355,7 @@ def setup_defaults():
     options.set("http_headers", None)
     options.set("format_preferred", None)
     options.set("audio_language", None)
-    options.set("audio_role", None)
+    options.set("video_role", "main")
     options.set("resolution", None)
     options.set("stream_prio", None)
     options.set("no_remux", False)
@@ -371,6 +372,7 @@ def setup_defaults():
     options.set("output_format", "mp4")
     options.set("get_all_subtitles", False)
     options.set("token", None)
+    options.set("reverse_list", False)
     return _special_settings(options)
 
 
@@ -379,7 +381,7 @@ def parsertoconfig(config, parser):
     config.set("filename", parser.filename)
     config.set("subfolder", parser.subfolder)
     config.set("configfile", parser.configfile)
-    config.set("resume", parser.resume)
+    # config.set("resume", parser.resume)
     config.set("live", parser.live)
     config.set("capture_time", parser.capture_time)
     config.set("silent", parser.silent)
@@ -409,7 +411,7 @@ def parsertoconfig(config, parser):
     config.set("http_headers", parser.http_headers)
     config.set("cookies", parser.cookies)
     config.set("format_preferred", parser.format_preferred)
-    config.set("audio_role", parser.audio_role)
+    config.set("video_role", parser.video_role)
     config.set("resolution", parser.resolution)
     config.set("audio_language", parser.audio_language)
     config.set("stream_prio", parser.stream_prio)
@@ -428,6 +430,7 @@ def parsertoconfig(config, parser):
     config.set("only_video", parser.only_video)
     config.set("output_format", parser.output_format)
     config.set("token", parser.token)
+    config.set("reverse_list", parser.reverse_list)
     return _special_settings(config)
 
 
diff --git a/lib/svtplay_dl/utils/stream.py b/lib/svtplay_dl/utils/stream.py
index 5dcf3c67..c2d61cd9 100644
--- a/lib/svtplay_dl/utils/stream.py
+++ b/lib/svtplay_dl/utils/stream.py
@@ -24,7 +24,7 @@ def sort_quality(data) -> List:
     data = sorted(data, key=lambda x: (x.bitrate, x.name), reverse=True)
     datas = []
     for i in data:
-        datas.append([i.bitrate, i.name, i.format, i.resolution, i.language, i.audio_role])
+        datas.append([i.bitrate, i.name, i.format, i.resolution, i.language, i.video_role])
     return datas
 
 
@@ -69,15 +69,13 @@ def language_prio(config, streams) -> List:
     return prioritized
 
 
-def audio_role(config, streams) -> List:
-    if config.get("audio_role"):
-        role = config.get("audio_role")
-    elif config.get("audio_role") is None and config.get("audio_language"):
-        return streams
+def video_role(config, streams) -> List:
+    if config.get("video_role"):
+        role = config.get("video_role")
     else:
-        role = "main"
+        return streams
 
-    prioritized = [s for s in streams if s.audio_role == role]
+    prioritized = [s for s in streams if s.video_role == role]
     return prioritized
 
 
@@ -172,9 +170,9 @@ def select_quality(config, streams):
         form_prio = DEFAULT_FORMAT_PRIO
     streams = format_prio(streams, form_prio)
 
-    streams = audio_role(config, streams)
+    streams = video_role(config, streams)
     if not streams:
-        raise error.UIException(f"Can't find any streams with that audio role {config.get('audio_role')}")
+        raise error.UIException(f"Can't find any streams with that video role {config.get('video_role')}")
 
     streams = language_prio(config, streams)
     if not streams:


Test patch:
diff --git a/lib/svtplay_dl/tests/test_stream.py b/lib/svtplay_dl/tests/test_stream.py
index acb28d09..d68f535f 100644
--- a/lib/svtplay_dl/tests/test_stream.py
+++ b/lib/svtplay_dl/tests/test_stream.py
@@ -5,11 +5,11 @@ from svtplay_dl.fetcher.hls import HLS
 from svtplay_dl.fetcher.http import HTTP
 from svtplay_dl.subtitle import subtitle
 from svtplay_dl.utils.parser import setup_defaults
-from svtplay_dl.utils.stream import audio_role
 from svtplay_dl.utils.stream import format_prio
 from svtplay_dl.utils.stream import language_prio
 from svtplay_dl.utils.stream import sort_quality
 from svtplay_dl.utils.stream import subtitle_filter
+from svtplay_dl.utils.stream import video_role
 
 
 class streamTest_sort(unittest.TestCase):
@@ -94,52 +94,52 @@ class streamTestRole(unittest.TestCase):
             DASH(setup_defaults(), "http://example.com", 3001, None),
             DASH(setup_defaults(), "http://example.com", 3002, None),
         ]
-        streams = audio_role(setup_defaults(), test_streams)
+        streams = video_role(setup_defaults(), test_streams)
         assert len(streams) == 3
 
     def test_language_prio2(self):
         test_streams = [
             DASH(setup_defaults(), "http://example.com", 3000, None),
-            DASH(setup_defaults(), "http://example.com", 3001, None, role="x-sv"),
+            DASH(setup_defaults(), "http://example.com", 3001, None, video_role="x-sv"),
             DASH(setup_defaults(), "http://example.com", 3002, None),
         ]
         config = setup_defaults()
-        config.set("audio_role", "x-sv")
-        streams = audio_role(config, test_streams)
+        config.set("video_role", "x-sv")
+        streams = video_role(config, test_streams)
         assert len(streams) == 1
 
     def test_language_prio3(self):
         test_streams = [
             DASH(setup_defaults(), "http://example.com", 3000, None),
-            DASH(setup_defaults(), "http://example.com", 3001, None, role="x-sv"),
+            DASH(setup_defaults(), "http://example.com", 3001, None, video_role="x-sv"),
             DASH(setup_defaults(), "http://example.com", 3002, None),
         ]
         config = setup_defaults()
-        config.set("audio_role", "sv")
-        streams = audio_role(config, test_streams)
+        config.set("video_role", "sv")
+        streams = video_role(config, test_streams)
         assert len(streams) == 0
 
     def test_language_prio4(self):
         test_streams = [
             DASH(setup_defaults(), "http://example.com", 3000, None),
-            DASH(setup_defaults(), "http://example.com", 3001, None, role="x-sv"),
+            DASH(setup_defaults(), "http://example.com", 3001, None, video_role="x-sv"),
             DASH(setup_defaults(), "http://example.com", 3002, None),
         ]
         config = setup_defaults()
         config.set("audio_language", "sv")
-        streams = audio_role(config, test_streams)
-        assert len(streams) == 3
+        streams = video_role(config, test_streams)
+        assert len(streams) == 2
 
     def test_language_prio5(self):
         test_streams = [
             DASH(setup_defaults(), "http://example.com", 3000, None),
-            DASH(setup_defaults(), "http://example.com", 3001, None, role="x-sv"),
+            DASH(setup_defaults(), "http://example.com", 3001, None, video_role="x-sv"),
             DASH(setup_defaults(), "http://example.com", 3002, None),
         ]
         config = setup_defaults()
-        config.set("audio_role", "isii")
+        config.set("video_role", "isii")
         config.set("audio_language", "sv")
-        streams = audio_role(config, test_streams)
+        streams = video_role(config, test_streams)
         assert len(streams) == 0