mpv-clip 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. #!/bin/bash
  2. shopt -s extglob
  3. #~ echo "$@" > /tmp/mpv-clip.log # ytfzf url_handler_opts='assdadasdasda' # doesn't work
  4. usage() {
  5. if [[ "$@" != "" ]]; then
  6. notify "$@" "Help text in terminal: $x11name -h"
  7. fi
  8. echo "
  9. Play URLs with mpv/youtube-dl/yt-dlp:
  10. mpv-clip [options] URL
  11. + copy URL back to clipboard or primary selection, formatted with media title.
  12. Options:
  13. -a Play only audio, but open a small controllable window with elapsed/total
  14. time, and don't disable the screensaver.
  15. -p Advance in playlist - does not seem to work with youtube currently.
  16. Does work with e.g. bandcamp.
  17. -Y str Options to add to mpv --ytdl-raw-options, after a comma. No quality
  18. control! See 'man mpv' for details.
  19. -y str Alternate domain that replaces youtube, e.g. \"invidious.snopyta.org\".
  20. Testing the domain requires 'ping'.
  21. -s str Override list of subtitle languages to embed. The default is to use
  22. your locale + English (2 letter ISO 639-1 codes): \"${slang}\". Pass empty
  23. string \"\" for ALL subtitles, which can be a lot for e.g. youtube.
  24. -c str How to copy URL+title back to the clipboard - this string must contain
  25. exactly two letters:
  26. 1. c|p|b for clipboard, primary selection, or both
  27. 2. b|m|t for BBCode, markdown or plain text
  28. Default: 'cb'.
  29. -t int Network timeout in seconds. With '-y', this is applied twice
  30. Default: $timeout
  31. -n Send desktop notifications.
  32. -i str Path to icon for notifications.
  33. Default: ${icon/$HOME/\~}
  34. -E str Comma separated list of variables and their values. This option exists
  35. to read ytfzf's values passed to URL handlers, namely \$video_pref,
  36. \$is_detach and \$is_audio_only
  37. -h This text.
  38. "
  39. exit 1
  40. }
  41. notify() {
  42. ((notify>0)) && notify-send -i $icon -- "$@" || \
  43. echo "$@"
  44. }
  45. clipboard() {
  46. [[ "$clipboard" == "" ]] && return
  47. # variables URL and title must be defined
  48. case $markup in
  49. b) out="[url=${URL}]${title}[/url]"
  50. ;;
  51. m) out="[${title}](${URL})"
  52. ;;
  53. t) out="$title
  54. $URL"
  55. ;;
  56. esac
  57. case $clipboard in
  58. c) echo -n "$out" | xsel -b
  59. ;;
  60. p) echo -n "$out" | xsel -p
  61. ;;
  62. b) echo -n "$out" | xsel -b
  63. echo -n "$out" | xsel -p
  64. ;;
  65. esac
  66. }
  67. x11name="mpv-clip" # xprop class name to be used by xdotool and optionally window manager
  68. icon="/usr/share/icons/Papirus/48x48/apps/multimedia-video-player.svg"
  69. bbcode=0 # fill clipboard with BBcode instead of plain text
  70. notify=0
  71. ytmirror=""
  72. timeout=5 # network timeout in seconds
  73. ytdlrawopts='write-info-json='
  74. clipboard='c'
  75. cbtext="clipboard"
  76. markup='t'
  77. for dep in mpv; do
  78. type "$dep" >/dev/null || usage
  79. done
  80. slang="${LANG%%_*}"
  81. slang="${slang,,}";
  82. [[ "$slang" == en ]] || slang="${slang},en"
  83. # Just to initialize the array...
  84. #~ opts=( --force-window=yes --ytdl=yes --ytdl-format=ytdl --script-opts=ytdl_hook-try_ytdl_first=yes --script-opts=ytdl_hook-all_formats=yes --script-opts=ytdl_hook-thumbnails=best --script-opts=ytdl_hook-use_manifests=yes ) # mpv options for all use cases
  85. opts=( --force-window=yes --ytdl=yes --ytdl-format=ytdl --script-opts=ytdl_hook-try_ytdl_first=yes ) # mpv options for all use cases
  86. while getopts apY:y:s:c:t:i:hnE: opt; do
  87. case $opt in
  88. a) geometry="33%x30+0+0"
  89. opts=( ${opts[@]} --vid=no --sub=no
  90. --osd-scale-by-window=no --no-osd-bar --no-osc
  91. --osd-msg1=\${time-pos}/\${duration}
  92. --osd-font-size=28 --osd-font=monospace --background=.1/.1/.1
  93. --osd-color=.9/.9/.9 --osd-border-size=0
  94. --osd-margin-y=0 --osd-margin-x=5 --no-stop-screensaver )
  95. x11name="${x11name}-audioonly"
  96. ;;
  97. p) opts=( ${opts[@]} --ytdl-raw-options=ignore-errors=,yes-playlist= ) # as of 2020-10-19, this does not seem to work at all with youtube (or invidious mirrors)
  98. x11name="${x11name}-playlist"
  99. ;;
  100. Y)
  101. ytdlrawopts=",$OPTARG"
  102. ;;
  103. y) ytmirror="$OPTARG" # will use "$ytmirror" instead of "www.youtube.com" or "youtu.be"
  104. for dep in host ping; do type $dep >/dev/null || usage; done
  105. ;;
  106. s) slang="$OPTARG" # comma separated list of subtitle languages - pass empty string '' or "" to get ALL subtitles
  107. ;;
  108. c) (( "${#OPTARG}" != 2 )) && usage "Invalid argument -$opt $OPTARG"
  109. clipboard="${OPTARG:0:1}"
  110. markup="${OPTARG:1:1}"
  111. [[ "$clipboard" =~ ^(c|p|b)$ ]] && [[ "$markup" =~ ^(b|m|t)$ ]] || usage "Invalid argument -$opt $OPTARG"
  112. type xsel >/dev/null || usage
  113. case $clipboard in
  114. c) cbtext="clipboard" ;;
  115. p) cbtext="primary selection" ;;
  116. b) cbtext="clipboard and primary selection" ;;
  117. esac
  118. ;;
  119. t) timeout="$OPTARG"
  120. [[ "$OPTARG" =~ ^[1-9][0-9]*$ ]] || usage "-$opt: Invalid number: $OPTARG (must be a positive integer)"
  121. ;;
  122. i) icon="$OPTARG"
  123. [ -r "$OPTARG" ] || usage "-$opt: Cannot read $OPTARG"
  124. ;;
  125. n) notify=1
  126. ;;
  127. E)
  128. echo "$OPTARG" >> /tmp/mpv-clip.log
  129. ;;
  130. h|*) usage
  131. ;;
  132. esac
  133. done
  134. shift $((OPTIND-1))
  135. cbtext="(copied URL+title to $cbtext)"
  136. # first things first...
  137. URL="$1"
  138. # remove leading/trailing whitespace:
  139. URL="${URL#[[:space:]]*}"
  140. URL="${URL%[[:space:]]*}"
  141. # replace the rest with %20 (urlencode for dummies)
  142. URL="${URL//[[:space:]]/%20}"
  143. # check if it's actually an http URL
  144. [[ "$URL" =~ ^(https?|ftp|file)://* ]] || URL="https://$URL" # try to fix it
  145. [[ "$URL" == ?*\.?* ]] || usage "$URL is not a valid URL" # must contain at least one dot
  146. # silently drop notifications if this is not available
  147. type notify-send >/dev/null 2>&1 || [[ "$notify" == 0 ]]
  148. if [[ "$ytmirror" != "" ]]; then
  149. ytdomains=( www.youtube.com youtube.com m.youtube.com youtu.be )
  150. for domain in "${ytdomains[@]}"; do
  151. if [[ "$URL" =~ ^(https?)://$domain/* ]]; then
  152. if ping -c1 -n -w"$timeout" "$ytmirror" >/dev/null; then
  153. URL="${URL/$domain/$ytmirror}" && break
  154. else
  155. ytmirror=''
  156. fi
  157. break
  158. fi
  159. done
  160. fi
  161. # introducing raw options passed to youtube-dl
  162. if [[ "$x11name" == *"-audioonly" ]]; then
  163. ytdlrawopts="${ytdlrawopts},format=bestaudio"
  164. fi
  165. #~ ytdlrawopts="${ytdlrawopts},socket-timeout=$timeout"
  166. [[ "$slang" == "" ]] || ytdlrawopts="${ytdlrawopts},sub-lang=\"$slang\""
  167. # for indentifying windows (and notifications)
  168. x11name="$x11name-$(printf '%(%H:%M:%S)T')"
  169. # Building the command line
  170. opts=( ${opts[@]}
  171. --network-timeout=$timeout
  172. --ytdl-raw-options="${ytdlrawopts#,}"
  173. --x11-name="$x11name"
  174. --quiet
  175. --no-msg-color
  176. --no-input-terminal --term-playing-msg='media-title: ${media-title}'
  177. --no-resume-playback
  178. --watch-later-directory=/dev/null
  179. )
  180. ((notify>1)) && notify "Trying: $URL" "$x11name"
  181. echo "Trying: $URL"
  182. coproc mpv "${opts[@]}" "$URL"
  183. error=0
  184. while read -r -u "${COPROC[0]}" line; do
  185. echo "$line"
  186. if [[ "$line" == *"media-title: "* ]]; then
  187. title="${line#*media-title: }"
  188. notify "$title" "$x11name $cbtext"
  189. clipboard
  190. #~ often there are http errors, but playback continues regardless. so let's drop it.
  191. #~ elif [[ "$line" == *"HTTP error"* ]] || [[ "$line" == *"ytdl_hook: ERROR:"* ]]; then
  192. elif [[ "$line" == *"ytdl_hook: ERROR:"* ]]; then
  193. line="${line%%; please report this issue*}"
  194. notify=1; error=1
  195. notify "$line" "$x11name"
  196. fi
  197. done
  198. [[ "$error" == 1 ]] && exit 1
  199. notify "That was: $title" "Exiting $x11name $cbtext"