Repository: streamlink/streamlink
Pull request #1722 (2018-05-31 23:43:08)

plugins.reshet: support for reshet.tv live and VOD streams
Includes a small enhancement to the brightcove player class.


Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index d9b24173..0c7098b7 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -174,6 +174,7 @@ radionet                - radio.net          Yes   --
                         - radio.pt
                         - radio.se
 raiplay                 raiplay.it           Yes   No    Most streams are geo-restricted to Italy.
+reshet                  reshet.tv            Yes   Yes   Streams may be geo-restricted to Israel.
 rtbf                    - rtbf.be/auvio      Yes   Yes   Streams may be geo-restricted to Belgium or Europe.
                         - rtbfradioplayer.be
 rtlxl                   rtlxl.nl             No    Yes   Streams may be geo-restricted to The Netherlands. Livestreams not supported.
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/brightcove.py b/src/streamlink/plugins/brightcove.py
index 6ed940b1..8b230895 100644
--- a/src/streamlink/plugins/brightcove.py
+++ b/src/streamlink/plugins/brightcove.py
@@ -98,6 +98,7 @@ class BrightcovePlayer(object):
         policy_key = self.policy_key(video_id)
         self.logger.debug("Found policy key: {0}", policy_key)
         data = self.video_info(video_id, policy_key)
+        headers = {"Referer": self.player_url(video_id)}
 
         for source in data.get("sources"):
             # determine quality name
@@ -107,10 +108,9 @@ class BrightcovePlayer(object):
                 q = "{0}k".format(source.get("avg_bitrate") // 1000)
             else:
                 q = "live"
-
             if ((source.get("type") == "application/x-mpegURL" and source.get("src")) or
                     (source.get("src") and ".m3u8" in source.get("src"))):
-                for s in HLSStream.parse_variant_playlist(self.session, source.get("src")).items():
+                for s in HLSStream.parse_variant_playlist(self.session, source.get("src"), headers=headers).items():
                     yield s
             elif source.get("app_name"):
                 s = RTMPStream(self.session,
@@ -118,7 +118,7 @@ class BrightcovePlayer(object):
                                 "playpath": source.get("stream_name")})
                 yield q, s
             elif source.get("src") and source.get("src").endswith(".mp4"):
-                yield q, HTTPStream(self.session, source.get("src"))
+                yield q, HTTPStream(self.session, source.get("src"), headers=headers)
 
     @classmethod
     def from_url(cls, session, url):
diff --git a/src/streamlink/plugins/reshet.py b/src/streamlink/plugins/reshet.py
new file mode 100644
index 00000000..0b5e4de8
--- /dev/null
+++ b/src/streamlink/plugins/reshet.py
@@ -0,0 +1,34 @@
+from __future__ import print_function
+
+import re
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http
+from streamlink.plugins.brightcove import BrightcovePlayer
+
+
+class Reshet(Plugin):
+    url_re = re.compile(r"https?://(?:www\.)?reshet\.tv/(live|item/)")
+    video_id_re = re.compile(r'"videoID"\s*:\s*"(\d+)"')
+    account_id = "1551111274001"
+    live_channel_id = "ref:stream_reshet_live1"
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return cls.url_re.match(url) is not None
+
+    def _get_streams(self):
+        bp = BrightcovePlayer(self.session, self.account_id)
+        m = self.url_re.match(self.url)
+        base = m and m.group(1)
+        if base == "live":
+            return bp.get_streams(self.live_channel_id)
+        else:
+            res = http.get(self.url)
+            m = self.video_id_re.search(res.text)
+            video_id = m and m.group(1)
+            if video_id:
+                return bp.get_streams(video_id)
+
+
+__plugin__ = Reshet
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:


Test patch:
diff --git a/tests/test_plugin_reshet.py b/tests/test_plugin_reshet.py
new file mode 100644
index 00000000..47b53709
--- /dev/null
+++ b/tests/test_plugin_reshet.py
@@ -0,0 +1,26 @@
+import unittest
+
+from streamlink.plugins.reshet import Reshet
+
+
+class TestPluginReshet(unittest.TestCase):
+    def test_can_handle_url(self):
+        should_match = [
+            "https://www.reshet.tv/live",
+            "http://www.reshet.tv/live",
+            "http://reshet.tv/live",
+            "https://reshet.tv/live",
+            "http://reshet.tv/item/foo/bar123",
+            "https://reshet.tv/item/foo/bar123",
+            "https://www.reshet.tv/item/foo/bar123",
+            "http://www.reshet.tv/item/foo/bar123",
+        ]
+        for url in should_match:
+            self.assertTrue(Reshet.can_handle_url(url))
+
+    def test_can_handle_url_negative(self):
+        should_not_match = [
+            "https://www.youtube.com",
+        ]
+        for url in should_not_match:
+            self.assertFalse(Reshet.can_handle_url(url))