-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtubes.sh
More file actions
executable file
·1692 lines (1395 loc) · 59.8 KB
/
tubes.sh
File metadata and controls
executable file
·1692 lines (1395 loc) · 59.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
# grbl play and get from youtube casa@ujo.guru 2022
# Based on yt-dlp https://github.com/yt-dlp/yt-dlp
# youtube player/downloader on terminal window
# - based on yt-dlp, youtube-search, mpv, tiv,
# - youtbe puzzle solver
# - search and download
# - colorful menu view
# - text mode thumbnails (tiv)
# - player gui when playing
# - download guide
# - mpv playtime keyboard ui
# - scalable on terminal window size
# - no adds
# Active degub and color mode, and setup verbole level: 0:no colors >1: colors >3 tumbnails
export GRBL_COLOR=true
export GRBL_DEBUG=
export GRBL_VERBOSE=2
# Home folder
base=$HOME/.tubes
# Additional functions
source $base/bin/color.sh
source $base/bin/common.sh
# Locations
declare -g youtube_data=$HOME/.tubes
declare -g youtube_cfg=$youtube_data/tubes.cfg
declare -g youtube_rc="/tmp/tubes.rc"
declare -g youtube_error=/tmp/tubes.error
declare -g mpv_error=/tmp/mpv.error
declare -g save_location=
# Options for external software
declare -g youtube_remote_java="--remote-components ejs:github "
declare -g youtube_java=
declare -g youtube_options= #"-f worst"
# Otions for this module
declare -g save_to_file=
declare -g continue_to_play=
declare -g media_type=
declare -g module_command=()
declare -g autoplay_delay=4
# Debug stuff (for final version remove all lines that contains '#!debug' tag)
__youtube=$(readlink --canonicalize --no-newline $BASH_SOURCE) #!debug
__youtube_color="red" #!debug
# more global variables downstairs (after sourcing rc file)
youtube.help () {
# main help of youtube module
gr.msg -v1 "grbl youtube help " -h
gr.msg -v2
gr.msg -v0 "Usage: tubes play|get|list|song|search|install|uninstall|help"
gr.msg -v2
gr.msg -v1 "Commands: " -c white
gr.msg -v2
gr.msg -v1 " play <id|url> play media from stream (BROKEN!)"
gr.msg -v1 " get <ids|url> download list of media to media folder "
gr.msg -v1 " list <search string> play list of search results, no video playback"
#gr.msg -v3 " song <id|url> download audio to audio folder "
gr.msg -v3 " search <string> search, printout list of results "
gr.msg -v1 " install install requirements"
gr.msg -v1 " uninstall remove requirements "
gr.msg -v1 " upgrade upgrades (run this if stops working)"
gr.msg -v1 " help this help window"
gr.msg -v2
gr.msg -v1 "Examples: " -c white
gr.msg -v2
gr.msg -v1 " tubes get https://www.youtube.com/watch?v=z9PWGAxy5zU"
gr.msg -v1 " tubes get 3l65TSnK8h4"
gr.msg -v1 " tubes play r9Qma8tpSeE <- some issues for now"
gr.msg -v1 " tubes nyan cat "
gr.msg -v1 " tubes juna kulkee taas "
gr.msg -v1 " tubes mitä kuuluu marja leena"
gr.msg -v1 " tubes upgrade # fixes 9/10 times when youtube changes something "
gr.msg -v2
# own function to able player menu access for menu player function
gr.player_help
gr.msg -v2
}
youtube.player_help () {
# menu help
gr.msg -v1 "Search menu: " -c white
gr.msg -v2
gr.msg -v1 " Menu navigation is key based, see command keys below. "
gr.msg -v1 " Search string can be given as argument. Options should also apply here. "
gr.msg -v2
gr.msg -v1 " <search phrase> just type to something to find in youtube"
gr.msg -v1 " <1..20> select media from result list"
gr.msg -v1 " (n)ext jump to next list item "
gr.msg -v1 " (p)revious jump to previous in list"
gr.msg -v1 " (w)ait wait until previous audio is ended"
gr.msg -v1 " (a)utoplay continue to play next item "
gr.msg -v1 " (s)ingin sing in with google account add details to youtube.cfg"
gr.msg -v1 " (t)ype switch between audio or video mode"
gr.msg -v1 " (d)onwload switch between download ans player mode"
gr.msg -v1 " (l)ist printout list of search result"
gr.msg -v1 " (e)rror print last error "
gr.msg -v1 " (v)erbose <1..3> 1= basic, 2=more details, 3= thumbnails"
gr.msg -v1 " (q)uit bye bye"
gr.msg -v2
gr.msg -v1 "Functions:" -c white
gr.msg -v2
}
youtube.main () {
# module command parser
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug
youtube.options $@
case ${module_command[@]:0:1} in
install|uninstall|upgrade|search|help|status)
youtube.${module_command[@]:0:1} ${module_command[@]:1}
;;
get|dl|download)
gr.msg "Download mode"
for item in ${module_command[@]:1} ; do
youtube.get_media $item
done
;;
song|music)
youtube.get_audio ${module_command[@]:1}
;;
list)
youtube.search_list ${module_command[@]:1}
;;
play)
youtube.search_n_play ${module_command[@]:1}
;;
*)
youtube.search ${module_command[@]:0:1} ${module_command[@]:1}
;;
esac
return 0
}
youtube.venv () {
# activated venv
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug
gr.msg -v3 -n "checking venv.. "
if [[ $VIRTUAL_ENV == $HOME/.venv ]]; then
gr.msg -v4 -c green "activated"
else
if ! [[ -d $HOME/.venv ]]; then
python3 -m venv $HOME/.venv
gr.msg -v3 -n -c white "set up "
fi
source $HOME/.venv/bin/activate
gr.msg -v3 -c aqua "actived"
fi
}
youtube.status () {
# printout status
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug
gr.msg -n -v1 -t "${FUNCNAME[0]}: "
if [[ -f /usr/local/bin/yt-dlp ]] || [[ -f /usr/bin/yt-dlp ]] ; then
gr.msg -c green "installed"
return 0
else
gr.msg -c dark_grey "not installed"
return 1
fi
}
youtube.options () {
# module argument parser
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug
local got_args=($@)
for (( i = 0; i < ${#got_args[@]}; i++ )); do
case ${got_args[$i]} in
--get|--download|--save|dl)
youtube_options="-f b"
save_to_file=true
;;
--continue|--c|--cont)
continue_to_play=true
;;
--repeat|--l|--loop)
mpv_options="$mpv_options --loop"
;;
--fullscreen|--fs|--f)
mpv_options="$mpv_options --fs"
;;
--video|--v)
youtube_options= # export
save_location=$GRBL_MOUNT_VIDEO
media_type=video
;;
--audio|--a)
youtube_options="-f bestaudio --no-resize-buffer --ignore-errors"
mpv_options="$mpv_options --no-video"
save_location=$GRBL_MOUNT_AUDIO
media_type=audio
;;
--list-formats)
youtube_options="$youtube_options --list-formats"
;;
*)
module_command+=("${got_args[$i]}")
;;
esac
done
# When file is downloaded donwloader settings include file format
if [[ $save_to_file ]] ; then
if [[ $media_type == audio ]]; then
youtube_options="$youtube_options -x --audio-format mp3"
else # video
youtube_options="$youtube_options --recode-video mp4"
fi
fi
}
config.make_rc () {
# make rc file out of configuration file
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo $@ #!debug
local _chapter=
local _source_cfg="$1" # source configuration file
local _target_rc="$2" # target rc file
gr.msg -n -v3 -c gray "$_source_cfg "
if ! [[ -f $_source_cfg ]] ; then
gr.msg -c yellow "$_source_cfg not found"
return 100
fi
case $(head -n 1 $_source_cfg) in
*"source"*) gr.msg -v3 -c dark_grey "..no need to compile this type of configs"
return 0 ;;
esac
# append mode is third static argument
if [[ $3 == "append" ]] ; then
gr.msg -n -v3 -c gray "(append mode) "
else
[[ -f $_target_rc ]] && rm $_target_rc
fi
gr.msg -v3 -c gray "$_mode $_target_rc"
# read config file, use chapter name as second part of variable name
while IFS='= ' read -r lhs rhs ; do
if [[ ! $lhs =~ ^\ *# && -n $lhs ]]; then
rhs="${rhs%%\#*}" # remove in line right comments
rhs="${rhs%%*( )}" # remove trailing spaces
rhs="${rhs// =/=}" # remove spaces from =
rhs="${rhs//= /=}"
case "$lhs" in
*[*) _chapter=${lhs//[}
_chapter=${_chapter//]}
[[ $_chapter ]] && _chapter="${_chapter}_" ;;
*) echo "export TUBES_${_chapter^^}${lhs^^}=$rhs"
esac
fi
done < $_source_cfg >> $_target_rc
chmod +x $_target_rc
return $?
}
youtube.rc () {
# source configurations (to be faster)
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug
local config_date=$(stat -c %Y $youtube_cfg)
local rc_date=$(stat -c %Y $youtube_rc)
gr.varlist "debug youtube_cfg config_date youtube_rc rc_date" #!debug #!debug
# check are user configs changed, and update rc file if are
if [[ ! -f $youtube_rc ]] || [[ $(( $config_date - $rc_date )) -gt 0 ]]; then
config.make_rc "$youtube_cfg" "$youtube_rc"
fi
# get config
source $youtube_rc
}
youtube.search () {
# search from youtube, print list of results and ask user to select one, then play it
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug
# initialize
local items=
local optimization="video"
local list=()
local urls=()
local duration=()
local last_item=
local thubnails=
local search_phrase=
local todo="play"
local mp4=
local cols=
# local youtube_data=$GRBL_DATA/youtube
[[ $save_to_file ]] && todo="download"
# terminal type
case $TERM in
xterm-256color)
thubnails=true
;;
linux|*)
optimization="audio"
[[ $TUBES_LIMIT_VERBOSE -gt 2 ]] && export TUBES_LIMIT_VERBOSE=2
export youtube_options=
export mpv_options="--input-ipc-server=$TUBES_MPV_SOCKET_FILE --vo=null --no-video "
esac
# make needed folders
if ! [[ -d $youtube_data ]] ; then
[[ -d $youtube_data ]] || mkdir $youtube_data
fi
[[ -d "$youtube_data/played" ]] || mkdir -p "$youtube_data/played"
[[ -d "$youtube_data/cache" ]] || mkdir -p "$youtube_data/cache"
search () {
# search from by yt-dlp
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug
# get search phrase
search_phrase=$@
# user did not give search string
[[ $search_phrase ]] || read -p "search term: " search_phrase
gr.debug "$FUNCNAME search_phrase:'$search_phrase'" #!debug
[[ $search_phrase == "" ]] && return 0
# search from youtube.com, i think youtube limits results to 20 items
local _json="/tmp/youtube_results.json"
youtube.find $TUBES_LIMIT_SEARCH_RESULT json "$search_phrase" >$_json
# fulfill list variables from data got from youtube search
ifs=$IFS ; IFS=$'\n'
id=($(jq '.videos [] | .id ' $_json))
title=($(jq '.videos [] | .title ' $_json))
url=($(jq '.videos [] | .url_suffix ' $_json))
duration=($(jq '.videos [] | .duration ' $_json))
channel=($(jq '.videos [] | .channel ' $_json))
views=($(jq '.videos [] | .views ' $_json))
publish=($(jq '.videos [] | .publish_time ' $_json))
description=($(jq '.videos [] | .long_desc ' $_json))
thumb=($(jq '.videos [] | .thumbnails [0] ' $_json | cut -d'?' -f1))
IFS=$ifs
last_item=1
}
compose_list () {
# make list from search results
[[ ${#title[@]} -lt 10 ]] && width=3
[[ ${#title[@]} -gt 9 ]] && width=4
[[ ${#title[@]} -gt 99 ]] && width=5
# go trough found list items
for (( i = 0; i < ${#title[@]}; i++ )); do
title[$i]=${title[$i]//'"'}
url[$i]=${url[$i]//'"'}
duration[$i]="${duration[$i]//'"'}"
id[$i]=${id[$i]//'"'}
publish[$i]=${publish[$i]//'"'}
[[ ${publish[$i]} == "0" ]] && publish[$i]="" || publish[$i]="${publish[$i]}"
[[ ${description[$i]} == "null" ]] && description[$i]=""
thumb[$i]=${thumb[$i]//'"'}
views[$i]=${views[$i]//'"'}
channel[$i]="${channel[$i]//'"'}"
[[ $TUBES_LIMIT_VERBOSE -gt 2 ]] && ! [[ -f $youtube_data/cache/${id[$i]}.jpg ]] \
&& curl -s ${thumb[$i]} --output "$youtube_data/cache/${id[$i]}.jpg"
done
items=${#title[@]}
}
print_list() {
# format and printout list of composed search results
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug
cols=$(echo "cols" | tput -S) # cols variable has to be available for all search sub functions
# Header for search results
local clean_search_phrase=$(sed -e "s/\b\(.\)/\u\1/g" <<<$([[ $search_phrase ]] && echo $search_phrase || echo $@))
if [[ $items -gt 0 ]]; then
if [[ $cols -lt 10 ]] ; then
gr.msg "${clean_search_phrase:0:5}"
elif [[ $cols -lt 15 ]] ; then
gr.msg -N -h "$clean_search_phrase"
elif [[ $cols -lt 25 ]] ; then
gr.msg -N -h "Search: $clean_search_phrase"
elif [[ $cols -lt 100 ]] ; then
gr.msg -N -h "Results for '$clean_search_phrase'"
else
gr.msg -N -h "Search results for '$clean_search_phrase'"
fi
fi
# limit thumbnail size to whatever specified in user conf
[[ $cols -gt $TUBES_LIMIT_THUMBNAIL_SIZE ]] && thumb_cols=$TUBES_LIMIT_THUMBNAIL_SIZE || thumb_cols=$cols
# go trough found list items
for (( i = 0; i < ${#title[@]}; i++ )); do
# print list without thumbnails, and try to optimize to column width
if [[ $TUBES_LIMIT_VERBOSE -lt 3 ]] ; then
# printout only number add title
if [[ $cols -lt 10 ]] ; then
gr.msg -hn "$(( $i + 1 ))"
gr.msg -n -c dark_grey "|"
elif [[ $cols -lt 80 ]] ; then
gr.msg -hn -w $width "$(( $i + 1 )))"
gr.msg -w $(($cols - $width - 1)) -c light_blue "${title[$i]}"
# printout number, title and duration
elif [[ $cols -ge 80 ]] && [[ $cols -lt 120 ]] ; then
gr.msg -hn -w $width "$(( $i + 1 )))"
gr.msg -n -c light_blue "${title[$i]} "
gr.msg -c gray "${duration[$i]}"
# printout number, title, duration and publish date
elif [[ $cols -ge 120 ]] ; then
gr.msg -hn -w $width "$(( $i + 1 )))"
gr.msg -n -c light_blue "${title[$i]} "
gr.msg -v0 -nc gray "(${duration[$i]}) "
gr.msg -c dark_gray "(${publish[$i]}) "
fi
# verbosity 3, all information
else
# printout number add title
gr.msg -hn -w $width "$(( $i + 1 )))"
gr.msg -n -c light_blue "${title[$i]} "
# printout rest of information
gr.msg -c gray "(${duration[$i]}) "
gr.msg -nc dark_gray "[${channel[$i]}] "
gr.msg -c dark_gray "(${publish[$i]}) "
gr.msg -c dark_gray "${views[$i]} "
# printout descriptions (it any)
[[ ${description[$i]} ]] && gr.msg -c gray "${description[$i]} "
# printout thumbnail
[[ $thubnails ]] && tiv -w $thumb_cols "$youtube_data/cache/${id[$i]}.jpg"
gr.msg
fi
done
}
print_prompt () {
# print help and prompt
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug
# print menu help bar
if [[ $items -lt 1 ]] ; then
gr.msg -c dark_cyan "search by typing search phrase and press enter"
else
if [[ $cols -gt 79 ]] ; then
_white=(n p w a l t d s e v q 1 $'\b'$items $'\n')
_grey=(ext revious ait utoplay ogin ype onwload earch rror erbose uit '..')
_space=' '
elif [[ $cols -gt 74 ]] ; then
_white=(n p w a l t d s e v q $'\n')
_grey=(ext revious ait utoplay ogin ype onwload earch rror erbose uit )
_space=' '
elif [[ $cols -gt 59 ]] ; then
_white=(n p w a l t d s e v q $'\n')
_grey=(ext rev ait utop ogin ype onwl earch rror erb uit )
_space=' '
elif [[ $cols -gt 29 ]] ; then
_white=('' n p w a l t d s e v q 1 $items $'\n')
_grey=('[' '|' '|' '|' '|' '|' '|' '|' '|' '|' '|' '|' '..' ']')
_space=''
elif [[ $cols -gt 10 ]] ; then
_white=('' n p w a l t d s e v q $'\n')
_grey=('' '|' '|' '|' '|' '|' '|' '|' '|' '|' '|' '')
_space=''
else
_white=('' n p w a l t d s e v q $'\n')
_grey=('' '' '' '' '' '' '' '' '' '' '' '')
_space=''
fi
for (( i = 0; i < ${#_white[@]}; i++ )); do
gr.msg -n -c white "${_white[$i]}"
gr.msg -n -c grey "${_grey[$i]}$_space"
done
fi
# genrating prompt
if [[ $cols -lt 6 ]] ; then
_prompt=$'\r'""
_prompt+="$(gr.msg -n -c cyan ${todo:0:1})"
_prompt+="$(gr.msg -n -c white ':')"
elif [[ $cols -lt 10 ]] ; then
_prompt=$'\r'""
_prompt+="$(gr.msg -n -c cyan ${todo:0:1}${optimization:0:1})"
_prompt+="$(gr.msg -n -c white ':')"
elif [[ $cols -lt 25 ]] ; then
_prompt=$'\r'""
_prompt+="$(gr.msg -n -c cyan ${todo:0:2}/${optimization:0:2})"
_prompt+="$(gr.msg -n -c white ': ')"
elif [[ $cols -lt 60 ]] ; then
_prompt=$'\r'"[$last_item/${#title[@]}] "
_prompt+="$(gr.msg -n -c cyan $todo $optimization)"
_prompt+="$(gr.msg -n -c white ': ')"
else
_prompt=$'\r'"[$last_item/${#title[@]}] "
_prompt+="$(gr.msg -n -c dark_cyan command or number to) "
_prompt+="$(gr.msg -n -c cyan $todo $optimization)"
_prompt+="$(gr.msg -n -c white ': ')"
fi
# if continue playing is set , wait few second and jump to next item
if [[ $continue_to_play ]]; then
read -e -t $autoplay_delay -p "$_prompt" ans
ans=${ans:-continue}
# initially with no arguments go stait to search
elif [[ $last_item -lt 1 ]]; then
ans='s'
# Normal prompt
else
read -e -p "$_prompt" ans
fi
}
play_item () {
# play item
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug
# limit thumbnail size
cols=$(echo "cols"|tput -S) # update column size
local _error=0
[[ $cols -gt 80 ]] && cols=80
# make shown list number to real list location
local item=$(($1-1))
# format header and now playing data
local np="youtube [$1/$items] ${title[$item]} ${duration[$item]}"
# place formatted headers, sleep one second cause last player might delete updated file
echo "$np" >$TUBES_NOWPLAYING_FILE
create_time=$(stat -c %Y $TUBES_NOWPLAYING_FILE)
gr.msg -N -h "$np"
# log what played today
echo "$(date -d now +%Y-%m-%d-%H:%M:%S) ${id[$item]} ${title[$item]}" >>"$youtube_data/played/$(date -d now +%Y-%m-%d).list"
# fetch thumbnail if not done already
if [[ $thubnails ]] && [[ $TUBES_LIMIT_VERBOSE -ge 1 ]] && [[ ! $save_to_file ]]; then
[[ -f $youtube_data/cache/${id[$item]}.jpg ]] || curl -s ${thumb[$item]} --output "$youtube_data/cache/${id[$item]}.jpg"
tiv -w $cols "$youtube_data/cache/${id[$item]}.jpg"
fi
local media_address="https://www.youtube.com${url[$item]}"
# if download selected
if [[ $save_to_file ]] ; then
# open repository to select quality separately for video and audio
gr.debug "download call $media_address" #!debug
youtube.get_media $media_address
# nothing more to do here?
return $?
fi
# print media address below the thumbnail (link to youtube)
gr.msg -c dark_grey "$media_address"
# this is shown only then debug mote '-d' is flagged (or verbose 4+)
if [[ $GRBL_DEBUG ]]; then
echo "yt-dlp --no-playlist $youtube_remote_java $youtube_java $youtube_options $sing_in_option $media_address -o - 2>$youtube_error | mpv $mpv_options -"
yt-dlp --no-playlist $youtube_remote_java $youtube_java $youtube_options $sing_in_option $media_address -o - | mpv $mpv_options -
gr.ask "continue?" || return 192
fi
yt-dlp --no-playlist $youtube_remote_java $youtube_java $youtube_options $sing_in_option $media_address -o - 2>$youtube_error | mpv $mpv_options -
# in every module, function error is returned to core in continued return chain, and can be processed by core to
# exits from module function are logged specially old method gr.msg -x 128 exits can cause extra logging, more for lacy prototyping
_error=$?
# transfer errors for future processing in main loop
gr.debug "$FUNCNAME _error:$_error" #!debug
# remove now playing only if it's created by current me. made by checking creation time
# Okey, here is late night implementation, but it seem to work, stay there then.
[[ -f $TUBES_NOWPLAYING_FILE ]] && [[ $(($(stat -c %Y $TUBES_NOWPLAYING_FILE) - $create_time)) -lt 1 ]] \
&& rm $TUBES_NOWPLAYING_FILE
# module maker (2025) declare module error as $_error=(), compatible with below
(( $_error > 0 )) && gr.msg -c yellow "${FUNCNAME[0]} returned $_error"
return $_error
}
set_sing_in () {
# get cookies from firefox and set youtube sing in options for user set in configuration
# NOTE: this might not work anymore: https://github.com/yt-dlp/yt-dlp/issues/8079
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug
if ! [[ -f $youtube_data/cookies.txt ]] ; then
yt-dlp --cookies-from-browser $TUBES_COOKIE_BROWSER --cookies $youtube_data/cookies.txt --skip-download 2>$youtube_error
gr.msg -c white "cookies saved to $youtube_data/cookies.txt"
gr.ask "file contains all your cookies, so you might like to delete it on exit? " && delete_cookies=true
fi
if ! [[ $TUBES_YOUTUBE_USER ]] ; then
gr.msg -e1 "no youtube user set in configuration "
return 122
fi
[[ $TUBES_YOUTUBE_PASSWORD ]] \
&& sing_in_option="-u $TUBES_YOUTUBE_USER -p $TUBES_YOUTUBE_PASSWORD" \
|| sing_in_option="-u $TUBES_YOUTUBE_USER"
sing_in_option="$sing_in_option --cookies $youtube_data/cookies.txt"
}
# search and printout search results
[[ $1 ]] && search "$@"
compose_list
print_list
# main menu loop
while true ; do
# update column count every loop
cols=$(echo "cols" | tput -S)
print_prompt
# parse user input
case ${ans%% *} in
# play selected search result
[1-9]|1[0-9]|2[0-9]|3[0-9])
if [[ $ans -gt $items ]] ; then
gr.msg -c error "list is $items items long"
continue
fi
last_item=$ans
;;
continue) # continuous playing, increase item number
[[ $last_item -ge ${#title[@]} ]] && last_item=0
last_item=$(( last_item + 1 ))
;;
v*) # set verbosity
[[ ${#ans} -le 1 ]] && read -p "set verbosity 0..3: " ans
[[ ${#ans} == 2 ]] && ans=${ans:1}
[[ "${#ans}" -ge 3 ]] && ans=${ans#* }
gr.debug "VERBOSE:'$ans" #!debug
[[ ! $thubnails ]] && [[ $ans -gt 2 ]] && ans=2
export TUBES_LIMIT_VERBOSE=$ans
export GRBL_VERBOSE=$TUBES_LIMIT_VERBOSE
[[ $TUBES_LIMIT_VERBOSE -gt 2 ]] && compose_list
print_list
continue
;;
d) if [[ $save_to_file ]] ; then
save_to_file=
todo="play"
else
save_to_file=true
todo="download"
# if [[ $optimization == "video" ]] ; then
# gr.ask "convert to webm files to mp4 format?" && mp4=true || mp4=
# fi
fi
gr.msg "selected to $todo $optimization"
continue
;;
t) # content type selector, video or audio
if [[ $optimization == "audio" ]] ; then
optimization=video
youtube_options="-f best"
mpv_options="--input-ipc-server=$TUBES_MPV_SOCKET_FILE"
else
optimization=audio
youtube_options=
mpv_options="--input-ipc-server=$TUBES_MPV_SOCKET_FILE --vo=null --no-video "
fi
gr.msg "selected to $todo $optimization"
continue
;;
a) # toggle continuous and normal playing
if [[ $continue_to_play ]] ; then
gr.msg "autoplay canceled"
continue_to_play=
else
gr.msg "autoplay set ($autoplay_delay sec delay)"
continue_to_play=true
fi
continue
;;
f) # not really sure what this were
if ! [[ $mpv_temp_options ]] ; then
mpv_temp_options=$mpv_options
mpv_options="$mpv_options --fs"
else
mpv_options=$mpv_temp_options
mpv_temp_options=
fi
continue
;;
n) # play next item
last_item=$(( last_item + 1 ))
[[ $last_item -ge ${#title[@]} ]] && last_item=1
;;
p) # play previous item
last_item=$(( last_item - 1 ))
[[ $last_item -le 0 ]] && last_item=${#title[@]}
;;
w) # wait until now playing is empty and start to play
[[ "${#ans}" -ge 3 ]] && last_item=${ans#* }
item=$(( $last_item - 1 ))
sleep 1
gr.msg -n "waiting [$last_item/$items] ${title[$item]} ${duration[$item]} (press any key to play now)"
while [[ -f $TUBES_NOWPLAYING_FILE ]] ; do
read -s -n1 -t1 && break
done
;;
l) # sing in toggle
if [[ $sing_in_option ]] ; then
gr.msg -c dark_grey "removing login options"
sing_in_option=
else
gr.msg -c white "setting $TUBES_YOUTUBE_USER login options"
set_sing_in
fi
continue
;;
e) #show last error log
gr.msg -h "printing error log from $youtube_error "
[[ -f $youtube_error ]] && cat $youtube_error || gr.msg "no error log found"
continue
;;
q*|exit|bye) # exit
break
;;
s) # new search
local s_phrase=${ans#* }
[[ ${#ans} -le 1 ]] && read -p "search: " s_phrase
search "$s_phrase"
compose_list
print_list
continue
;;
l|"") # print list of results again
print_list
continue
;;
*) gr.msg -c yellow "unknown command"
continue
esac
# pause other players, but only once
if ! [[ $stopped ]] && [[ $optimization == audio ]] ; then
local stopped=true
# audio.pause others youtube
fi
play_item $last_item
if [[ $? -eq 143 ]] ; then
# # some module asked to hold on a moment
# if flag.get audio_hold ; then
# while flag.get audio_hold >/dev/null ; do
# sleep 2
# done
# else
# gr.msg "continues playing canceled"
# continue_to_play=
# fi
true
fi
done
# do not compromise user privacy
if [[ $delete_cookies ]] && [[ -f $youtube_data/cookies.txt ]] ; then
gr.msg -n "deleting cookies $youtube_data/cookies.txt.. "
rm $youtube_data/cookies.txt && gr.msg -c green "ok" || gr.msg -e3 "failed"
fi
return 0
}
youtube.find () {
# search from youtube and return json of $1 amount of results
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug
# deliver decimal value for inline python
result_count=$1
# if output format is specified, set it and remove it from input string
case $2 in
dict) return_format=dict ; shift ;;
json) return_format=json ; shift ;;
*) return_format=json
esac
# remove result count
shift
search_string="$@"
# with python, indentation is critical, therefore following lines needs to be like this
if [[ -f $HOME/.venv/bin/activate ]]; then
python3 -m venv $HOME/.venv
fi
source $HOME/.venv/bin/activate
python3 - << EOF
import os
import json
from youtube_search import YoutubeSearch
results = YoutubeSearch("$search_string", max_results=$result_count).to_$return_format()
print(results)
EOF
}
youtube.firefox_cookies () {
# get youtube cookie from firefox
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug
local cookie_yt="$youtube_data/youtube_cookie.txt"
if [[ -f $cookie_yt ]]; then
if ! gr.ask "$cookie_yt exist, overwrite?"; then
gr.msg "skipping.. "
return 99
fi
fi
local cookie_db="/tmp/fox-cookies.sqlite"
local profiles="$HOME/.mozilla/firefox/profiles.ini"
if ! [[ -f $profiles ]] ; then
gr.msg -e2 no profiles '$profiles' found
return 112
fi
# fast way to get default profile
head -n 3 $profiles | grep "Default" >/tmp/fox-profile
source /tmp/fox-profile
profile=$Default
rm /tmp/fox-profile
gr.debug "profile:'$profile'" #!debug
[[ $profile ]] || return 113
# get cookies database
fox_cookie_db=$HOME/.mozilla/firefox/$profile/cookies.sqlite
gr.debug "cookie_db:'$fox_cookie_db'" #!debug
[[ -f $fox_cookie_db ]] || return 114
cp $fox_cookie_db $cookie_db
python3 "$GRBL_BIN/audio/get_cookie.py" youtube.com $cookie_db >$cookie_yt
rm $cookie_db
}
youtube.search_n_play () {
# search input and play it from youtube. use long arguments --video or --audio to select optimization
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug
local base_url="https://www.youtube.com"
local _error=
# to fulfill global variables: save_to_file save_location mpv_options youtube_options
youtube.options $@
# make search and get media data and address
local query=$(youtube.find 1 json ${module_command[@]})
# get information of found media
# TBD make able to parse multiple search results ans for them trough to replace search_list function"
local title=$(echo $query | jq | grep title | cut -d':' -f2 | sed 's/"//g' | sed 's/,//g' | xargs -0 )
local duration=$(echo $query | jq | grep duration | cut -d':' -f2 | xargs | sed 's/,//g')
local media_address=$base_url$(echo $query | jq | grep url_suffix | cut -d':' -f 2 | xargs)
gr.msg -v1 -h "$title ($duration) "
gr.msg -v2 $media_address
# if just saving the file
if [[ $save_to_file ]]; then
#save the file to media folder
youtube_options="$youtube_options --continue --output $save_location/%(title)s.%(ext)s"
gr.msg -v1 "downloading to $save_location.. "
# save file
yt-dlp --no-playlist $youtube_remove_java $youtube_options $media_address
# a bit dangero if some of location variables are empty
#new_name=$(detox -v *mp4 -n | grep ">" | cut -d '>' -f 2 |xargs)
detox -v *mp3 *mp4 $save_location 2>/dev/null
#source tag.sh
#tag.main add $new_name "grbl youtube.sh $title"
return $?
fi
# make now playing info available for audio module
echo "youtube $title" >$TUBES_NOWPLAYING_FILE
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] #!debug
# start stream and play
yt-dlp --no-playlist $youtube_remove_java $youtube_java \
$youtube_options $media_address -o - 2>$youtube_error \
| mpv $mpv_options - >$mpv_error
# in some cases there is word fuck or exposed tits in video, therefore:
if grep 'Sign in to' $youtube_error; then
[[ -f $mpv_error ]] && rm $mpv_error
[[ -f $youtube_error ]] && rm $youtube_error
# if user willing to save password in configs (who would?) serve him/her anyway
[[ $TUBES_YOUTUBE_PASSWORD ]] \
&& sing_in="-u $TUBES_YOUTUBE_USER -p $TUBES_YOUTUBE_PASSWORD --cookies-from-browser" \
|| sing_in="-u $TUBES_YOUTUBE_USER --cookies-from-browser"
gr.msg -v2 "signing in as $TUBES_YOUTUBE_USER"
# then perform re-try
yt-dlp --no-playlist $youtube_remove_java -v $youtube_options $sing_in $media_address -o - 2>$youtube_error \
| mpv $mpv_options - >$mpv_error
fi
# lacy error printout
if [[ -f $mpv_error ]]; then
_error=$(grep 'ERROR:' $youtube_error)
[[ $_error ]] && gr.msg -v2 -c red $_error
rm "$mpv_error"
fi
if [[ -f $youtube_error ]]; then
_error=$(grep 'Failed' $mpv_error)
[[ $_error ]] && gr.msg -v2 -c yellow $_error
rm "$youtube_error"
fi
# remove now playing and error data
[[ -f $TUBES_NOWPLAYING_FILE ]] && rm $TUBES_NOWPLAYING_FILE
return 0
}
youtube.search_list () {
# search input and play it from youtube, optimized for audio, no video at all
gr.msg -v4 -n -c $__youtube_color "$__youtube [$LINENO] $FUNCNAME: " >&2 ; [[ $GRBL_DEBUG ]] && echo "'$@'" >&2 #!debug