diff options
author | Gregor Kleen <gkleen@yggdrasil.li> | 2018-04-06 14:17:41 +0200 |
---|---|---|
committer | Gregor Kleen <gkleen@yggdrasil.li> | 2018-04-06 14:17:41 +0200 |
commit | 04fe40544c0ef912bdd72a4146f88827eb3bf5f9 (patch) | |
tree | a20aa6c6ae64e2beae7be4a5af84ae1fc85612b2 | |
parent | 8156ce5a53dea671701d342a2db3346074dceb7b (diff) | |
download | nixos-04fe40544c0ef912bdd72a4146f88827eb3bf5f9.tar nixos-04fe40544c0ef912bdd72a4146f88827eb3bf5f9.tar.gz nixos-04fe40544c0ef912bdd72a4146f88827eb3bf5f9.tar.bz2 nixos-04fe40544c0ef912bdd72a4146f88827eb3bf5f9.tar.xz nixos-04fe40544c0ef912bdd72a4146f88827eb3bf5f9.zip |
strm
-rw-r--r-- | odin/strm.nix | 21 | ||||
-rwxr-xr-x | odin/strm/cleanup_youtube | 61 | ||||
-rwxr-xr-x | odin/strm/download_youtube | 337 | ||||
-rw-r--r-- | users/gkleen@odin.nix | 3 |
4 files changed, 422 insertions, 0 deletions
diff --git a/odin/strm.nix b/odin/strm.nix new file mode 100644 index 00000000..33e605af --- /dev/null +++ b/odin/strm.nix | |||
@@ -0,0 +1,21 @@ | |||
1 | { stdenv, zsh, ffmpeg-full, youtube-dl, pv, notmuch }: | ||
2 | |||
3 | stdenv.mkDerivation { | ||
4 | name = "strm"; | ||
5 | src = ./strm; | ||
6 | |||
7 | phases = [ "unpackPhase" "buildPhase" "installPhase" ]; | ||
8 | |||
9 | inherit zsh ffmpeg-full youtube-dl pv notmuch; | ||
10 | |||
11 | buildPhase = '' | ||
12 | substituteAllInPlace download_youtube | ||
13 | substituteAllInPlace cleanup_youtube | ||
14 | ''; | ||
15 | |||
16 | installPhase = '' | ||
17 | mkdir -p $out/bin | ||
18 | |||
19 | install -m 755 -t $out/bin download_youtube cleanup_youtube | ||
20 | ''; | ||
21 | } | ||
diff --git a/odin/strm/cleanup_youtube b/odin/strm/cleanup_youtube new file mode 100755 index 00000000..0df15d0b --- /dev/null +++ b/odin/strm/cleanup_youtube | |||
@@ -0,0 +1,61 @@ | |||
1 | #!@zsh@/bin/zsh | ||
2 | |||
3 | function notmuch { | ||
4 | while true; do | ||
5 | result=$($_env NOTMUCH_CONFIG=${HOME}/.notmuch-rss-config @notmuch@/bin/notmuch "$@" 2>&1) | ||
6 | if ! [[ $result =~ "already locked" ]]; then | ||
7 | echo -nE $result | ||
8 | return | ||
9 | fi | ||
10 | sleep 2 | ||
11 | done | ||
12 | } | ||
13 | |||
14 | tagFile="" | ||
15 | |||
16 | function cleanup { | ||
17 | [[ -n "${tagFile}" ]] && rm -fv ${tagFile} | ||
18 | } | ||
19 | |||
20 | dir=/srv/media/youtube | ||
21 | maxsize=$((1024 * 1024 * 1024 * 50)) | ||
22 | |||
23 | while [[ $(du -bs $dir | awk '{ print $1; }') -gt $maxsize ]]; do | ||
24 | find $dir -type f -not -path $dir/CACHEDIR.TAG -print0 | \ | ||
25 | xargs -0 -- $_stat --printf '%W %Y %Z %X %n\0' | \ | ||
26 | sort -t '\0' -z | \ | ||
27 | awk -F '\0' '{ gsub("^\\S+\\s+\\S+\\s+\\S+\\s+\\S+\\s+", "", $1); printf "%s\0", $1; }' | \ | ||
28 | xargs -0 -- rm -v | ||
29 | done | ||
30 | |||
31 | find $dir -xtype l -delete | ||
32 | find $dir -type d -empty -delete | ||
33 | |||
34 | notmuch search --output=messages --format=text 'is:cached' | \ | ||
35 | while read id; do | ||
36 | untag=false | ||
37 | url=$(notmuch show --format=raw "$id" | grep 'http://odin.asgard.yggdrasil/youtube' | sed -r 's|^.*href="http://odin.asgard.yggdrasil/youtube/([^"]+)".*$|\1|' | sed -f /usr/lib/url_unescape.sed) | ||
38 | |||
39 | printf "%s\n ‘%s’\n" ${id} ${dir}/${url} | ||
40 | |||
41 | if [[ -z "${url}" ]]; then | ||
42 | printf " Could not extract filename.\n" | ||
43 | untag=true | ||
44 | fi | ||
45 | |||
46 | if [[ -n "${url}" && ! -e "${dir}/${url}" ]]; then | ||
47 | printf " File vanished\n" | ||
48 | untag=true | ||
49 | fi | ||
50 | |||
51 | if ${untag}; then | ||
52 | if [[ -z "${tagFile}" ]]; then | ||
53 | tagFile=$(mktemp --tmpdir $$.tags.XXXXXXXXXX) | ||
54 | printf "Using %s\n" ${tagFile} | ||
55 | fi | ||
56 | |||
57 | { printf "-cached -- %s\n" ${id} >> ${tagFile} } && printf " Tagging ‘-cached’...\n" | ||
58 | fi | ||
59 | done | ||
60 | |||
61 | [[ -n "${tagFile}" ]] && notmuch tag --batch --input=${tagFile} | ||
diff --git a/odin/strm/download_youtube b/odin/strm/download_youtube new file mode 100755 index 00000000..33e9a454 --- /dev/null +++ b/odin/strm/download_youtube | |||
@@ -0,0 +1,337 @@ | |||
1 | #!@zsh@/bin/zsh | ||
2 | |||
3 | alwaysTranscode=false | ||
4 | |||
5 | ffmpeg() { { { @ffmpeg-full@/bin/ffmpeg $@ 1>&3 } 2>&1 | stdbuf -o 0 tr '\r' '\n' | grep -v --line-buffered -E '^$' 1>&2 } 3>&1 } | ||
6 | pv() { { { @pv@/bin/pv -D 2 -i 2 -w 100 -H 1 -f $@ 1>&3 } 2>&1 | stdbuf -o 0 tr '\r' '\n' | grep -v --line-buffered -E '^$' 1>&2 } 3>&1 } | ||
7 | trimName() { prefix=$1; shift; printf "%s%s" ${prefix} $(awk -v len=$((19 - $#prefix)) '{ if (length($0) > len) print substr($0, 1, len-3) "..."; else print; }' <<<"${(j: :)@}")} | ||
8 | |||
9 | function notmuch { | ||
10 | while true; do | ||
11 | result=$(env NOTMUCH_CONFIG=${HOME}/.notmuch-rss-config @notmuch@/bin/notmuch "$@" 2>&1) | ||
12 | if ! [[ $result =~ "already locked" ]]; then | ||
13 | echo -nE $result | ||
14 | return | ||
15 | fi | ||
16 | sleep 2 | ||
17 | done | ||
18 | } | ||
19 | |||
20 | logTag=${0:t} | ||
21 | |||
22 | exec 1> >(logger -t "$logTag" -p news.notice) | ||
23 | exec 2> >(logger -t "$logTag" -p news.error) | ||
24 | |||
25 | debug() { logger -t "$logTag" -p news.debug } | ||
26 | warn() { logger -t "$logTag" -p news.warn } | ||
27 | |||
28 | typeset -a cleanupCmds | ||
29 | cleanupCmds=() | ||
30 | |||
31 | function doCleanup { | ||
32 | for cmd (${cleanupCmds}); do | ||
33 | # print -- ${cmd} | debug | ||
34 | eval $cmd | debug | ||
35 | done | ||
36 | } | ||
37 | |||
38 | function cleanup() { | ||
39 | local cmd | ||
40 | cmd="" | ||
41 | for arg ($@); do | ||
42 | [[ -n ${cmd} ]] && cmd="$cmd " | ||
43 | cmd="${cmd}${(qq)arg}" | ||
44 | done | ||
45 | |||
46 | cleanupCmds+=(${cmd}) | ||
47 | } | ||
48 | |||
49 | mungefilename () | ||
50 | { | ||
51 | tr "\`\"' /[:upper:]" "_____[:lower:]" <<<${@} | sed -r 's/[^0-9a-z\!\#\$\%\&\(\)\*\+\,\-\.\:\;\<\=\>\?\@\[\]\~\^\_\{\}\|]//g; s/_+/_/g' | ||
52 | } | ||
53 | |||
54 | [[ $#@ -le 0 || $#@ -gt 1 ]] && exit 2 | ||
55 | |||
56 | msgId=$1 | ||
57 | message=$(notmuch search --output=files --duplicate=1 $msgId) | ||
58 | [[ ! -e $message ]] && exit 1 | ||
59 | |||
60 | printf ">>> %s <<<\n %s\n" "${msgId}" "${message}" | ||
61 | |||
62 | ( print " waiting for lock..." | ||
63 | |||
64 | if ! flock -xn 9; then | ||
65 | print " could not acquire lock." | ||
66 | exit 0 | ||
67 | fi | ||
68 | |||
69 | print " locked." | ||
70 | |||
71 | trap doCleanup EXIT | ||
72 | |||
73 | typeset -a msgTags | ||
74 | msgTags=($(notmuch search --output=tags $msgId )) | ||
75 | |||
76 | if [[ ${msgTags[(i)cached]} -le $#msgTags ]]; then | ||
77 | print "Message in Cache" | warn | ||
78 | exit 0 | ||
79 | fi | ||
80 | |||
81 | if [[ 'base64' == $(sed '/^Content-Transfer-Encoding: */!d; s///;q' ${message}) ]]; then | ||
82 | tmpFile=$(mktemp --tmpdir=/home/gkleen/rss/tmp .writeOut.XXXXXX) | ||
83 | printf "Decoding base64 message content for ‘%s’" ${message} | debug | ||
84 | sed '/^Content-Transfer-Encoding: */d; /^$/q' ${message} >! $tmpFile | ||
85 | sed '1,/^$/d' ${message} | base64 -d >> $tmpFile | ||
86 | mv -v $tmpFile ${message} | ||
87 | fi | ||
88 | |||
89 | from=$(mungefilename $(awk '/^From/ { gsub("^\"", "", $2); print $2; exit; }' "${message}")) | ||
90 | if grep -q "<p>Enclosure: <a" "${message}"; then | ||
91 | url=$(tr -d '\n' < "${message}" | sed -r 's/^.*<p>Enclosure: <a[^>]+href=[^">]*"([^"]+)".*$/\1/') | ||
92 | else | ||
93 | url=$(awk '/^X-RSS-URL/ { print $2; exit; }' "${message}") | ||
94 | fi | ||
95 | |||
96 | sld=$(cut -d '/' -f 3 <<<${url} | rev | cut -d '.' -f 1-2 | rev) | ||
97 | |||
98 | if [[ -e ${HOME}/.dl-backup ]] && grep -q ${sld} ${HOME}/.dl-backup; then | ||
99 | printf "Was told to back up off %s\n" ${sld} | warn | ||
100 | exit 0 | ||
101 | else | ||
102 | printf "Proceeding on %s\n" ${sld} | debug | ||
103 | fi | ||
104 | |||
105 | dir="/srv/media/youtube" | ||
106 | if ! { [[ -d $dir ]] || mkdir -p $dir }; then | ||
107 | exit 1 | ||
108 | fi | ||
109 | |||
110 | formatString='bestvideo[width<=2560][height<=1440][fps<=60][vcodec=vp9]+bestaudio[acodec=opus]/bestvideo[width<=2560][height<=1440][fps<=60]+bestaudio/best[width<=2560][height<=1440][fps<=60]/best' | ||
111 | |||
112 | if [[ ${msgTags[(i)tv]} -le $#msgTags ]]; then | ||
113 | formatString="bestvideo[width<=2560][height<=1440][fps<=60][vcodec=h264]+bestaudio[acodec=aac]/bestvideo[width<=2560][height<=1440][fps<=60][vcodec=mpeg]+bestaudio[acodec=aac]/mp4[width<=2560][height<=1440][fps<=60][vcodec=h264][acodec=aac]/mp4[width<=2560][height<=1440][fps<=60][vcodec=mpeg][acodec=aac]/${formatString}" | ||
114 | fi | ||
115 | |||
116 | typeset -a args | ||
117 | args=(--no-playlist --add-metadata --newline --mark-watched -f ${formatString}) | ||
118 | |||
119 | youtube-dl ${args} --get-filename -o $'%(extractor)s\n%(id)s\n%(format)s\n%(title)s\n%(ext)s' -- ${url} | { | ||
120 | oldIFS=${IFS} | ||
121 | export IFS= | ||
122 | |||
123 | read extractor | ||
124 | read videoId | ||
125 | read format | ||
126 | read title | ||
127 | read fileExtension | ||
128 | |||
129 | export IFS=${oldIFS} | ||
130 | } || { | ||
131 | ret=$? | ||
132 | printf "An error occured while determining video metadata (exitcode: %d)\n" ${ret} >&2 | ||
133 | notmuch tag +failed -- ${msgId} | ||
134 | exit $? | ||
135 | } | ||
136 | |||
137 | if [[ "${extractor}" == "generic" ]]; then | ||
138 | title=$(grep -E '^Subject' "${message}" | head -n 1 | cut -d ' ' -f 2-) | ||
139 | fi | ||
140 | |||
141 | filename="${dir}/${title}-${extractor}.${videoId}.${from}.${fileExtension}" | ||
142 | filename=$(tr $'\n\0\/' ' ' <<<${filename} | tr -d $'!') | ||
143 | |||
144 | printf "%s\n%s via %s: %s\nExpecting ‘%s’\n" ${title} ${videoId} ${extractor} ${format} ${filename:t} | ||
145 | |||
146 | if [[ -n ${extractor} && -n ${videoId} ]]; then | ||
147 | printf "Searching for %s on %s in cache...\n" ${videoId} ${extractor} | debug | ||
148 | |||
149 | typeset -a messages | ||
150 | messages=($(notmuch search --output=messages -- from:${extractor} and ${videoId} and is:cached)) | ||
151 | |||
152 | if [[ ${#messages} -ne 0 ]]; then | ||
153 | printf "Video already cached: %s\n" "${messages[*]}" | warn | ||
154 | else | ||
155 | printf "Video not yet cached\n" | debug | ||
156 | fi | ||
157 | fi | ||
158 | |||
159 | youtube-dl $args -o "${filename:r}.%(ext)s" -- ${url} 2>&1 </dev/null | stdbuf -o0 tr '\r' '\n' | ||
160 | ytResult=$? | ||
161 | |||
162 | if [[ ! -e ${filename} && -n ${filename} ]]; then | ||
163 | for f (${filename:r}.*(N)); do | ||
164 | shouldDelete=false | ||
165 | |||
166 | grep -qE '\.f[0-9]+\.' <<<${f} && shouldDelete=true | ||
167 | grep -qE '\.part(-Frag[0-9]+)?$' <<<${f} && shouldDelete=true | ||
168 | grep -qE '\.temp\.[^\.]+$' <<<${f} && shouldDelete=true | ||
169 | |||
170 | if ${shouldDelete}; then | ||
171 | rm -v ${f} | ||
172 | else | ||
173 | filename=${f} | ||
174 | fi | ||
175 | done | ||
176 | fi | ||
177 | |||
178 | [[ -n ${filename} ]] && printf "Found ‘%s’\n" ${filename:t} || debug | ||
179 | |||
180 | # newFilename=${filename:h}/$(mungefilename ${filename:t}) | ||
181 | # if [[ "${filename}" != "${newFilename}" ]]; then | ||
182 | # mv -v ${filename} ${newFilename} && filename=${newFilename} | ||
183 | # fi | ||
184 | |||
185 | if [[ -n "${filename}" && -e "${filename}" && ${ytResult} -eq 0 ]]; then | ||
186 | max_vol=$( \ | ||
187 | pv -N "$(trimName "vol:" ${title})" ${filename} | ffmpeg -i pipe:0 -af "volumedetect" -vn -f null /dev/null 2>&1 | \ | ||
188 | grep 'max_volume' | sed -r 's/^.*max_volume: ([-0-9\.]+) dB$/\1/' | ||
189 | ) | ||
190 | printf "Maximum volume: %.2fdB\n" "${max_vol}" | ||
191 | [[ -n "${max_vol}" ]] || max_vol=0 | ||
192 | bare_amp=$(printf "%.2f" $(($max_vol * (-1)))) | ||
193 | amp=$(printf "volume=%sdB" "${bare_amp}") | ||
194 | |||
195 | typeset -a extensions | ||
196 | extensions=("mkv" "mp3" "mp4" "webm") | ||
197 | |||
198 | if [[ ${msgTags[(i)tv]} -le $#msgTags ]]; then | ||
199 | extensions=("mp4") | ||
200 | fi | ||
201 | |||
202 | if [[ "${bare_amp}" -ne 0 || ${extensions[(i)${filename:e}]} -gt ${#extensions} ]]; then | ||
203 | printf "Transcoding ‘%s’" ${title} | debug | ||
204 | printf "%d %d %d/%d(%d)…\n" \ | ||
205 | $([[ $($alwaysTranscode; print $?) -eq 0 ]]; print $?) \ | ||
206 | $([[ "${bare_amp}" -ne 0 ]]; print $?) \ | ||
207 | ${extensions[(i)${filename:e}]} ${#extensions} \ | ||
208 | $([[ ${extensions[(i)${filename:e}]} -gt ${#extensions} ]]; print $?) \ | ||
209 | | debug | ||
210 | tempfile=$(mktemp --tmpdir=${filename:h} .transcode.${filename:t:r}.$$.XXXXXX.${filename:e}) | ||
211 | cleanup rm -v -- "${tempfile}" | ||
212 | mv -vf "${filename}" "${tempfile}" | debug | ||
213 | |||
214 | typeset -A fileInfo | ||
215 | for line ($(ffprobe -v error -show_format -show_streams -show_entries stream=codec_name,codec_type:format=:stream_tags=:stream_disposition=:format_tags= -of flat=h=0 -- ${tempfile})); do | ||
216 | fileInfo[${line%=*}]=${(Q)line#*=} | ||
217 | done | ||
218 | |||
219 | ext=${extensions[1]} | ||
220 | filename=${filename%.*}.${ext} | ||
221 | typeset -a args | ||
222 | args=(-y) | ||
223 | typeset -a prePass | ||
224 | prePass=() | ||
225 | typeset -a cargs | ||
226 | cargs=() | ||
227 | typeset -a p1args | ||
228 | p1args=() | ||
229 | typeset -a p2args | ||
230 | p2args=() | ||
231 | for formatKey (${(k)fileInfo}); do | ||
232 | [[ ${formatKey} =~ "stream.([0-9]+).codec_type" ]] || continue | ||
233 | local i=$match[1] | ||
234 | local cType=${fileInfo[${formatKey}]} | ||
235 | local cName=${fileInfo[stream.${i}.codec_name]} | ||
236 | |||
237 | printf "Stream %d: %s (%s)\n" ${i} ${cName} ${cType} | debug | ||
238 | |||
239 | if [[ ${cType} == "video" ]]; then | ||
240 | if [[ ${ext} == "mkv" ]]; then | ||
241 | case ${cName} in | ||
242 | h264|vp9|png) | ||
243 | cargs+=(-c:${i} copy) | ||
244 | ;; | ||
245 | *) | ||
246 | # p1args=(-pass 1 -threads 8 -speed 4 -tile-columns 6 -frame-parallel 1) | ||
247 | # prePass+=(-c:${i} libvpx-vp9 -b:${i} 0 -crf:${i} 33) | ||
248 | # p2args+=(-pass 2 -threads 8 -speed 2 -tile-columns 6 -frame-parallel 1 -auto-alt-ref 1 -lag-in-frames 25) | ||
249 | # cargs+=(-c:${i} libvpx-vp9 -b:${i} 0 -crf:${i} 33) | ||
250 | cargs+=(-c:${i} libvpx-vp9 -b:${i} 2M -threads 8 -tile-columns 6 -frame-parallel 1) | ||
251 | ;; | ||
252 | esac | ||
253 | elif [[ ${ext} == "mp4" ]]; then | ||
254 | case ${cName} in | ||
255 | mpeg|h264) | ||
256 | cargs+=(-c:${i} copy) | ||
257 | ;; | ||
258 | *) | ||
259 | p2args+=(-strict -2) | ||
260 | cargs+=(-c:${i} libx264) | ||
261 | ;; | ||
262 | esac | ||
263 | fi | ||
264 | elif [[ ${cType} == "audio" ]]; then | ||
265 | if [[ ${ext} == "mkv" ]]; then | ||
266 | if [[ ( ${cName} == opus || ${cName} == flac || ${cName} == vorbis ) && "${bare_amp}" -eq 0 ]]; then | ||
267 | cargs+=(-c:${i} copy) | ||
268 | else | ||
269 | p2args+=(-vbr on -compression_level 10) | ||
270 | cargs+=(-c:${i} libopus -b:${i} 256K) | ||
271 | fi | ||
272 | elif [[ ${ext} == "mp4" ]]; then | ||
273 | if [[ ${cName} == aac && "${bare_amp}" -eq 0 ]]; then | ||
274 | cargs+=(-c:${i} copy) | ||
275 | else | ||
276 | cargs+=(-c:${i} aac) | ||
277 | fi | ||
278 | fi | ||
279 | fi | ||
280 | done | ||
281 | |||
282 | [[ "${bare_amp}" -ne 0 ]] && p2args+=(-af "${amp}") | ||
283 | |||
284 | if [[ $#prePass -gt 0 ]]; then | ||
285 | args+=(-v info -i ${tempfile}) | ||
286 | |||
287 | oldPwd=${PWD} | ||
288 | cd $(mktemp -d --tmpdir "transcode.${0:t}.$$.XXXXXX") | ||
289 | cleanup rm -rfv -- ${PWD} | ||
290 | |||
291 | p1args+=(-an -f matroska) | ||
292 | print -- ${prePass} ${p1args} | debug | ||
293 | ffmpeg ${args} ${prePass} ${p1args} -- /dev/null | ||
294 | |||
295 | print -- ${cargs} ${p2args} | debug | ||
296 | ffmpeg ${args} ${cargs} ${p2args} -- "${filename}" | ||
297 | |||
298 | cd ${oldPwd} | ||
299 | else | ||
300 | args+=(-v warning -i pipe:0) | ||
301 | |||
302 | print -- ${cargs} ${p2args} | debug | ||
303 | pv -N "$(trimName "trans:" ${title})" ${tempfile} | ffmpeg ${args} ${cargs} ${p2args} -- "${filename}" | ||
304 | fi | ||
305 | fi | ||
306 | |||
307 | chmod -v 644 "${filename}" | debug | ||
308 | tmpFile=$(mktemp --tmpdir=/home/gkleen/rss/tmp .insertUrl.$$.XXXXXX) | ||
309 | relUrl=$(realpath --relative-to=/srv/media ${filename}) | ||
310 | typeset -a relUrlComponents | ||
311 | relUrlComponents=(${(s./.)relUrl}) | ||
312 | relUrl="" | ||
313 | for urlPiece (${relUrlComponents}); do | ||
314 | [[ -n "${relUrl}" ]] && relUrl+="/" | ||
315 | relUrl+=$(sed -f ${HOME}/url_escape.sed <<<${urlPiece}) | ||
316 | done | ||
317 | awk -v "link=http://odin.asgard.yggdrasil/${relUrl}" '{ if (r == 0) { r = gsub("href=\"[^\"]+\"", "href=\"" link "\""); }; print; }' "${message}" >! $tmpFile | ||
318 | mv -v $tmpFile $message | debug | ||
319 | notmuch tag '+cached' -- $msgId && printf "Tagged ‘%s’ as 'cached'" ${msgId} | ||
320 | |||
321 | if [[ -n "$(notmuch search "tag:inbox AND $msgId")" ]]; then | ||
322 | if [[ ${msgTags[(i)tv]} -le $#msgTags ]]; then | ||
323 | printf "%s\n%s\n" "Media available on odin" "${title}" \ | ||
324 | | uux -p -n 'hel!notify-gkleen' -a download_youtube -u normal \ | ||
325 | && print "Sent notification to hel via uucp" | ||
326 | else | ||
327 | queue.hel "${filename}" && notmuch tag '-inbox' '-unread' -- $msgId | ||
328 | fi | ||
329 | else | ||
330 | print "Message vanished from inbox" | warn | ||
331 | fi | ||
332 | else | ||
333 | printf "An error occured while downloading video at ‘%s’ (exitcode: %d)\n" ${url} ${ytResult} >&2 | ||
334 | # notmuch tag +failed -- ${msgId} | ||
335 | # exit $? | ||
336 | fi | ||
337 | ) 9<>"${message}" | ||
diff --git a/users/gkleen@odin.nix b/users/gkleen@odin.nix index 2c63c085..d06215d1 100644 --- a/users/gkleen@odin.nix +++ b/users/gkleen@odin.nix | |||
@@ -1,2 +1,5 @@ | |||
1 | { | 1 | { |
2 | packageOverrides = pkgs: with pkgs; { | ||
3 | strm = pkgs.callPackage ../odin/strm.nix {}; | ||
4 | }; | ||
2 | } | 5 | } |