Repository: streamlink/streamlink
Pull request #1714 (2018-05-30 20:54:50)

plugins.onetv: fix issues with ctc channels and add DASH support
The "ctc" channels weren't working, and now DASH streams are supported. 


Patch:
diff --git a/script/makeinstaller.sh b/script/makeinstaller.sh
index 68b2f385..4c284d05 100755
--- a/script/makeinstaller.sh
+++ b/script/makeinstaller.sh
@@ -52,7 +52,6 @@ format=bundled
 ;           - urllib3
 ;           - socks / sockshandler
 ;       - websocket-client
-;       - isodate
 packages=pkg_resources
          six
          iso639
@@ -65,7 +64,6 @@ packages=pkg_resources
          websocket
          socks
          sockshandler
-         isodate
 pypi_wheels=pycryptodome==3.4.3
 
 files=../win32/LICENSE.txt > \$INSTDIR
diff --git a/script/release.sh b/script/release.sh
index fc9bf771..f64292c2 100755
--- a/script/release.sh
+++ b/script/release.sh
@@ -1,184 +1,263 @@
-#!/usr/bin/env bash
-
-script=$(basename -- "$0")
-temp_dir=$(mktemp -d) && trap "rm -rf ${temp_dir}" EXIT || exit 255
+#!/bin/bash
+UPSTREAM_REPO="streamlink"
+CLI="streamlink"
 
 usage() {
-    echo "usage: $script [OPTIONS]" >&2
+  echo "This will prepare $CLI for release!"
+  echo ""
+  echo "Requirements:"
+  echo " git"
+  echo " gpg - with a valid GPG key already generated"
+  echo " hub"
+  echo " github-release"
+  echo " GITHUB_TOKEN in your env variable"
+  echo " "
+  echo "Not only that, but you must have permission for:"
+  echo " Tagging releases within Github"
+  echo ""
 }
 
-help() {
-    usage
-    cat <<EOF >&2
-
-Helper script for performing a release of Streamlink.
-Options can be specified on the command line, or the user will
-be prompted to enter the required options.
+requirements() {
+  if [ ! -f /usr/bin/git ] && [ ! -f /usr/local/bin/git ]; then
+    echo "No git. What's wrong with you?"
+    return 1
+  fi
 
-General options:
-  -h, --help
-    Show this help message and exit.
+  if [ ! -f /usr/bin/gpg ] && [ ! -f /usr/local/bin/gpg ]; then
+    echo "No gpg. What's wrong with you?"
+    return 1
+  fi
 
-  -u, --upstream REPO
-    Set the upstream repo (streamlink/streamlink)
+  if [ ! -f $GOPATH/bin/github-release ]; then
+    echo "No $GOPATH/bin/github-release. Please run 'go get -v github.com/aktau/github-release'"
+    return 1
+  fi
 
-  -o, --origin REPO
-    Set the users fork of the upstream repo (gituser/streamlink)
+  if [ ! -f /usr/bin/hub ]; then
+    echo "No hub. Please run install hub @ github.com/github/hub"
+    return 1
+  fi
 
-  -v, --version VERSION
-    Set the new version number.
-EOF
+  if [[ -z "$GITHUB_TOKEN" ]]; then
+    echo "export GITHUB_TOKEN=yourtoken needed for using github-release"
+  fi
 }
 
-test_getopt() {
-    getopt --test > /dev/null
-    if [[ $? -ne 4 ]]; then
-        echo "An updated version of getopt is required (gnu-getopt)." >&2
-        return 1
-    fi
+# Clone and then change to user's upstream repo for pushing to master / opening PR's :)
+clone() {
+  git clone ssh://git@github.com/$UPSTREAM_REPO/$CLI.git
+  if [ $? -eq 0 ]; then
+        echo OK
+  else
+        echo FAIL
+        exit
+  fi
+  cd $CLI
+  git remote remove origin
+  git remote add origin git@github.com:$ORIGIN_REPO/$CLI.git
+  git checkout -b release-$1
+  cd ..
 }
 
-test_available() {
-    which $1 > /dev/null
-    return $?
+changelog() {
+  cd $CLI
+  echo "Getting commit changes. Writing to ../changes.txt"
+  LOG=$(git shortlog --email --no-merges --pretty=%s ${1}..)
+  echo "
+!!!WRITE YOUR RELEASE NOTES HERE!!!
+
+\`\`\`text
+${LOG}
+\`\`\`" > ../changes.txt
+  echo "Changelog has been written to changes.txt"
+  echo "!!PLEASE REVIEW BEFORE CONTINUING!!"
+  echo "Open changes.txt and add the release information"
+  echo "to the beginning of the file before the git shortlog"
+  cd ..
 }
 
-columns() {
-  cols=$(tput cols)
-  echo $((cols > 80 ? 80 : cols))
+changelog_md() {
+  echo "Generating CHANGELOG.md"
+  CHANGES=$(cat changes.txt)
+  cd $CLI
+  DATE=$(date +"%Y-%m-%d")
+  CHANGELOG=$(cat CHANGELOG.md)
+  HEADER="$CLI $1 ($DATE)"
+  echo -e "# $HEADER\n$CHANGES\n\n$CHANGELOG" >CHANGELOG.md
+  echo "Changes have been written to CHANGELOG.md"
+  cd ..
 }
 
-error() {
-  cols=$(columns)
-  tput hpa $((cols - 6))
-  echo -e "\e[39m[\e[31mFAIL\e[39m]"
-}
+git_commit() {
+  cd $CLI 
 
-success() {
-  cols=$(columns)
-  tput hpa $((cols - 4))
-  echo -e "\e[39m[\e[32mOK\e[39m]"
-}
-
-changelog() {
-  temp_changes=$(mktemp) && trap "rm -rf ${temp_changes}" EXIT || exit 255
-  date=$(date +"%Y-%m-%d")
-  shortlog=$(git shortlog --email --no-merges --pretty=%s ${1}..)
+  BRANCH=`git symbolic-ref --short HEAD`
+  if [ -z "$BRANCH" ]; then
+    echo "Unable to get branch name, is this even a git repo?"
+    return 1
+  fi
+  echo "Branch: " $BRANCH
 
-  echo -e "\n# streamlink $2 ($date)\n\n!! WRITE RELEASE NOTES HERE !!\n\n\`\`\`text\n${shortlog}\n\`\`\`\n" > "${temp_changes}"
+  git add .
+  git commit -m "$1 Release"
+  git push origin $BRANCH
+  hub pull-request -b $UPSTREAM_REPO/$CLI:master -h $ORIGIN_REPO/$CLI:$BRANCH
 
-  sed -i "/# Changelog/ r ${temp_changes}" "CHANGELOG.md"
-  return $?
+  cd ..
+  echo ""
+  echo "PR opened against master"
+  echo ""
 }
 
-check_changelog() {
-  grep "WRITE RELEASE NOTES HERE" "CHANGELOG.md" >/dev/null
-  if [[ "$?" == "0" ]]; then
-    echo "fatal: CHANGELOG.md contains the template text" >&2
-    return 1
+sign() {
+  # Tarball it!
+  cd $CLI
+  git tag $1
+  python setup.py sdist
+  mv dist/$CLI-$1.tar.gz ..
+  cd ..
+
+  # Sign it!
+  echo -e "SIGN THE TARBALL!\n"
+  gpg --detach-sign --armor $CLI-$1.tar.gz
+  if [ $? -eq 0 ]; then
+        echo SIGN OK
+  else
+        echo SIGN FAIL
+        exit
   fi
-  return 0
+
+  echo ""
+  echo "The tar.gz. is now located at $CLI-$1.tar.gz"
+  echo "and the signed one at $CLI-$1.tar.gz.asc"
+  echo ""
 }
 
-action() {
-  local msg temp_log
-  msg=$1
-  temp_log=$(mktemp) && trap "rm -rf ${temp_log}" EXIT || exit 255
+push() {
+  CHANGES=$(cat changes.txt)
+  # Release it!
+  github-release release \
+      --user $UPSTREAM_REPO \
+      --repo $CLI \
+      --tag $1 \
+      --name "$1" \
+      --description "$CHANGES"
+  if [ $? -eq 0 ]; then
+        echo RELEASE UPLOAD OK 
+  else 
+        echo RELEASE UPLOAD FAIL
+        exit
+  fi
+
+  github-release upload \
+      --user $UPSTREAM_REPO \
+      --repo $CLI \
+      --tag $1 \
+      --name "$CLI-$1.tar.gz" \
+      --file $CLI-$1.tar.gz
+  if [ $? -eq 0 ]; then
+        echo TARBALL UPLOAD OK 
+  else 
+        echo TARBALL UPLOAD FAIL
+        exit
+  fi
 
-  echo -n "$msg "
-  shift
+  github-release upload \
+      --user $UPSTREAM_REPO \
+      --repo $CLI\
+      --tag $1 \
+      --name "$CLI-$1.tar.gz.asc" \
+      --file $CLI-$1.tar.gz.asc
+  if [ $? -eq 0 ]; then
+        echo SIGNED TARBALL UPLOAD OK 
+  else 
+        echo SIGNED TARBALL UPLOAD FAIL
+        exit
+  fi
 
-  "$@" 2> "${temp_log}" 1>&2 && success || (error; cat "${temp_log}"; exit 1)
+  echo "DONE"
+  echo "DOUBLE CHECK IT:"
+  echo "!!!"
+  echo "https://github.com/$UPSTREAM_REPO/$CLI/releases/edit/$1"
+  echo "!!!"
+}
+
+
+clean() {
+  rm -rf $CLI $CLI-$1 $CLI-$1.tar.gz $CLI-$1.tar.gz.asc $CLI-$1.exe changes.txt
+}
 
-  return $?
+main() {
+  local cmd=$1
+  usage
+
+  echo "What is your Github username? (location of your $CLI fork)"
+  read ORIGIN_REPO 
+  echo "You entered: $ORIGIN_REPO"
+  echo ""
+  
+  echo ""
+  echo "First, please enter the version of the NEW release: "
+  read VERSION
+  echo "You entered: $VERSION"
+  echo ""
+
+  echo ""
+  echo "Second, please enter the version of the LAST release: "
+  read PREV_VERSION
+  echo "You entered: $PREV_VERSION"
+  echo ""
+
+  clear
+
+  echo "Now! It's time to go through each step of releasing $CLI!"
+  echo "If one of these steps fails / does not work, simply re-run ./release.sh"
+  echo "Re-enter the information at the beginning and continue on the failed step"
+  echo ""
+
+  PS3='Please enter your choice: '
+  options=(
+  "Git clone master"
+  "Generate changelog"
+  "Generate changelog for release"
+  "Create PR"
+  "Tarball and sign - requires gpg key"
+  "Upload the tarball and source code to GitHub release page"
+  "Clean"
+  "Quit")
+  select opt in "${options[@]}"
+  do
+      echo ""
+      case $opt in
+          "Git clone master")
+              clone $VERSION
+              ;;
+          "Generate changelog")
+              changelog $PREV_VERSION $VERSION
+              ;;
+          "Generate changelog for release")
+              changelog_md $VERSION
+              ;;
+          "Create PR")
+              git_commit $VERSION
+              ;;
+          "Tarball and sign - requires gpg key")
+              sign $VERSION
+              ;;
+          "Upload the tarball and source code to GitHub release page")
+              push $VERSION
+              ;;
+          "Clean")
+              clean $VERSION
+              ;;
+          "Quit")
+              clear
+              break
+              ;;
+          *) echo invalid option;;
+      esac
+      echo ""
+  done
 }
 
-test_getopt || exit 255
-
-# setup getopts
-OPTIONS=hu:o:v:
-LONGOPTIONS=help,upstream:,origin:,version:
-PARSED=$(getopt --options $OPTIONS --longoptions=$LONGOPTIONS --name "$0" -- "$@")
-if [[ $? -ne 0 ]]; then
-    usage
-    exit 2
-fi
-
-# read getopt’s output this way to handle the quoting right:
-eval set -- "$PARSED"
-
-# now enjoy the options in order and nicely split until we see --
-while true; do
-    case "$1" in
-        -h|--help)
-            help
-            exit 0
-            ;;
-        -u|--upstream)
-            upstream="$2"
-            shift 2
-            ;;
-        -o|--origin)
-            origin="$2"
-            shift 2
-            ;;
-        -v|--version)
-            version="$2"
-            shift 2
-            ;;
-        --)
-            shift
-            break
-            ;;
-        *)
-            echo "Programming error"
-            exit 3
-            ;;
-    esac
-done
-
-echo -e "Streamlink Release Script\n"
-action "User has git installed" test_available git
-
-if [ -z "$upstream" ]; then
-    echo -n "Upstream repo [streamlink/streamlink]: "
-    read upstream
-    if [ -z "$upstream" ]; then
-        upstream="streamlink/streamlink"
-    fi
-fi
-
-if [ -z "$origin" ]; then
-    echo -n "Fork repo [$USER/streamlink]: "
-    read origin
-    if [ -z "$origin" ]; then
-        origin="$USER/streamlink"
-    fi
-fi
-
-while [ -z "$version" ]; do
-    echo -n "New version number: "
-    read version
-done
-
-action "Cloning ${upstream}..." git clone -q "ssh://git@github.com/${upstream}.git" "${temp_dir}"
-
-pushd "${temp_dir}" >/dev/null
-
-dirty_version=$(python setup.py --version)
-current_version="${dirty_version%%+*}"
-
-action "Adding ${origin} as origin" git remote set-url origin "git@github.com:${origin}.git" || exit 1
-action "Adding release-${version} branch" git checkout -b "release-${version}" || exit 1
-
-action "Updating CHANGELOG.md (${current_version}..HEAD}" changelog "${current_version}" "${version}" || exit 1
-
-# launch editor to edit the file
-"${VISUAL:-"${EDITOR:-vi}"}" "CHANGELOG.md"
-
-action "Check CHANGELOG.md was updated" check_changelog || exit 1
-
-action "Commit CHANGELOG.md to release-${version} branch" git commit CHANGELOG.md -m "Release ${version}" || exit 1
-action "Push release-${version} branch to ${origin}" git push origin "release-${version}" || exit 1
-
-popd >/dev/null
+main "$@"
diff --git a/src/streamlink/plugins/bbciplayer.py b/src/streamlink/plugins/bbciplayer.py
index 77131e28..ef52fdda 100644
--- a/src/streamlink/plugins/bbciplayer.py
+++ b/src/streamlink/plugins/bbciplayer.py
@@ -12,7 +12,6 @@ from streamlink.plugin.api import http
 from streamlink.plugin.api import validate
 from streamlink.stream import HDSStream
 from streamlink.stream import HLSStream
-from streamlink.stream.dash import DASHStream
 from streamlink.utils import parse_json
 
 
@@ -159,9 +158,6 @@ class BBCiPlayer(Plugin):
                 if stream_type == "hls":
                     for s in HLSStream.parse_variant_playlist(self.session, url).items():
                         yield s
-                if connection.get("transferFormat") == "dash":
-                    for s in DASHStream.parse_manifest(self.session, connection["href"]).items():
-                        yield s
 
     def login(self, ptrt_url):
         """
diff --git a/src/streamlink/plugins/dailymotion.py b/src/streamlink/plugins/dailymotion.py
index 1557b98d..4f5623b7 100644
--- a/src/streamlink/plugins/dailymotion.py
+++ b/src/streamlink/plugins/dailymotion.py
@@ -23,17 +23,16 @@ _url_re = re.compile(r"""
     )
 """, re.VERBOSE)
 
-_media_schema = validate.Schema(validate.any(
-    {"error": {"title": validate.text}},
-    # "stream_chromecast_url": validate.url(),
+_media_schema = validate.Schema({
     # Chromecast URL is already available in qualities subdict
-    {"qualities": validate.any({
+    # "stream_chromecast_url": validate.url(),
+    "qualities": validate.any({
         validate.text: validate.all([{
             "type": validate.text,
             "url": validate.url()
         }])
     })
-    }))
+})
 _live_id_schema = validate.Schema(
     {
         "total": int,
@@ -54,10 +53,6 @@ class DailyMotion(Plugin):
         res = http.get(STREAM_INFO_URL.format(media_id), cookies=COOKIES)
         media = http.json(res, schema=_media_schema)
 
-        if media.get("error"):
-            self.logger.error("Failed to get stream: {0}".format(media["error"]["title"]))
-            return
-
         for quality, streams in media['qualities'].items():
             for stream in streams:
                 if stream['type'] == 'application/x-mpegURL':
diff --git a/src/streamlink/plugins/onetv.py b/src/streamlink/plugins/onetv.py
index ad479d8f..a4c06fd9 100644
--- a/src/streamlink/plugins/onetv.py
+++ b/src/streamlink/plugins/onetv.py
@@ -5,7 +5,7 @@ from streamlink.compat import urlencode, unquote, urljoin
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import http
 from streamlink.plugin.plugin import stream_weight
-from streamlink.stream import HLSStream, HTTPStream
+from streamlink.stream import HLSStream, HTTPStream, DASHStream
 from streamlink.utils import update_scheme
 
 
@@ -17,6 +17,9 @@ class OneTV(Plugin):
     _1tv_api = "//stream.1tv.ru/api/playlist/1tvch_as_array.json"
     _ctc_api = "//media.1tv.ru/api/v1/ctc/playlist/{channel}_as_array.json"
     _session_api = "//stream.1tv.ru/get_hls_session"
+    _channel_remap = {"chetv": "ctc-che",
+                      "ctclove": "ctc-love",
+                      "domashny": "ctc-dom"}
 
     @classmethod
     def can_handle_url(cls, url):
@@ -29,7 +32,8 @@ class OneTV(Plugin):
     @property
     def channel(self):
         match = self._url_re.match(self.url)
-        return match and match.group("channel")
+        c = match and match.group("channel")
+        return self._channel_remap.get(c, c)
 
     @property
     def live_api_url(self):
@@ -73,10 +77,20 @@ class OneTV(Plugin):
             res = http.get(self.live_api_url, data={"r": random.randint(1, 100000)})
             live_data = http.json(res)
 
-            for url in live_data.get("hls", [])[:1]:  # only take the first, they are all the same streams
+            # all the streams are equal for each type, so pick a random one
+            hls_streams = live_data.get("hls")
+            if hls_streams:
+                url = random.choice(hls_streams)
                 url = url + '&' + urlencode(self.hls_session())  # TODO: use update_qsd
                 for s in HLSStream.parse_variant_playlist(self.session, url, name_fmt="{pixels}_{bitrate}").items():
                     yield s
+
+            mpd_streams = live_data.get("mpd")
+            if mpd_streams:
+                url = random.choice(mpd_streams)
+                for s in DASHStream.parse_manifest(self.session, url).items():
+                    yield s
+
         elif self.channel == "1tv":
             self.logger.debug("Attempting to find VOD stream...", self.channel)
             vod_data = self.vod_data()
diff --git a/src/streamlink/stream/dash.py b/src/streamlink/stream/dash.py
index 49456404..6ed96a5d 100644
--- a/src/streamlink/stream/dash.py
+++ b/src/streamlink/stream/dash.py
@@ -6,6 +6,7 @@ import os.path
 from streamlink import StreamError, PluginError
 from streamlink.compat import urlparse, urlunparse
 from streamlink.stream.stream import Stream
+from streamlink.stream.wrappers import StreamIOIterWrapper
 from streamlink.stream.dash_manifest import MPD, sleeper, sleep_until, utc
 from streamlink.stream.ffmpegmux import FFMPEGMuxer
 from streamlink.stream.segmented import SegmentedStreamReader, SegmentedStreamWorker, SegmentedStreamWriter
@@ -31,8 +32,8 @@ class DASHStreamWriter(SegmentedStreamWriter):
                 time_to_wait = (segment.available_at - now).total_seconds()
                 fname = os.path.basename(urlparse(segment.url).path)
                 log.debug("Waiting for segment: {fname} ({wait:.01f}s)".format(fname=fname, wait=time_to_wait))
-                sleep_until(segment.available_at)
 
+                sleep_until(segment.available_at)
             return self.session.http.get(segment.url,
                                          timeout=self.timeout,
                                          exception=StreamError)
@@ -41,7 +42,7 @@ class DASHStreamWriter(SegmentedStreamWriter):
             return self.fetch(segment, retries - 1)
 
     def write(self, segment, res, chunk_size=8192):
-        for chunk in res.iter_content(chunk_size):
+        for chunk in StreamIOIterWrapper(res.iter_content(chunk_size)):
             if not self.closed:
                 self.reader.buffer.write(chunk)
             else:
diff --git a/src/streamlink_cli/main.py b/src/streamlink_cli/main.py
index f8ce4b92..2970df86 100644
--- a/src/streamlink_cli/main.py
+++ b/src/streamlink_cli/main.py
@@ -962,8 +962,6 @@ def main():
     # call setup args again once the plugin specific args have been added
     setup_args(parser)
     setup_config_args(parser)
-    # update the logging level if changed by a plugin specific config
-    logger.root.setLevel(args.loglevel)
     setup_console(console_out)
     setup_http_session()
     check_root()


Test patch:
diff --git a/tests/test_plugin_onetv.py b/tests/test_plugin_onetv.py
index 8c74d096..f322a18a 100644
--- a/tests/test_plugin_onetv.py
+++ b/tests/test_plugin_onetv.py
@@ -31,16 +31,16 @@ class TestPluginOneTV(unittest.TestCase):
         self.assertEqual(OneTV("http://1tv.ru/live").channel,
                          "1tv")
         self.assertEqual(OneTV("http://www.ctclove.ru/online").channel,
-                         "ctclove")
+                         "ctc-love")
         self.assertEqual(OneTV("http://domashny.ru/online").channel,
-                         "domashny")
+                         "ctc-dom")
 
     def test_live_api_url(self):
         self.assertEqual(OneTV("http://1tv.ru/live").live_api_url,
                          "http://stream.1tv.ru/api/playlist/1tvch_as_array.json")
 
         self.assertEqual(OneTV("https://www.ctclove.ru/online").live_api_url,
-                         "https://media.1tv.ru/api/v1/ctc/playlist/ctclove_as_array.json")
+                         "https://media.1tv.ru/api/v1/ctc/playlist/ctc-love_as_array.json")
 
         self.assertEqual(OneTV("http://domashny.ru/online").live_api_url,
-                         "http://media.1tv.ru/api/v1/ctc/playlist/domashny_as_array.json")
+                         "http://media.1tv.ru/api/v1/ctc/playlist/ctc-dom_as_array.json")