-
Notifications
You must be signed in to change notification settings - Fork 6
/
transcode.cleanup.sh
2680 lines (2559 loc) · 148 KB
/
transcode.cleanup.sh
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
# ! /bin/sh
SCRIPT_DIR=/config/ffmpeg
TRANSCODES_DIR=/config/transcodes
SEMAPHORE_DIR=/config/semaphore # use RAM drive for FFMPEG transcoding PID and PAUSE files
LOG_DIR=/config/log
CLEANUP_LOG=$LOG_DIR/transcode.cleanup.log # create a single log file (easier when using OFF, WARN or INFO level logging)
CLEANUP_LOG_MAXSIZE=10485760 # maximum size of the log file reaching which the log file will be truncated (default is 10485760 bytes=10 MB)
#SEMAPHORE_DIR=$SCRIPT_DIR/log # use mounted directory on host machine for easier access
#LOG_DIR=$SCRIPT_DIR/log
#CLEANUP_LOG=$LOG_DIR/transcode.cleanup.$$.log # create separate log file per each cleanup wrap trigger (easier for DEBUG or TRACE level logging)
CLEANUP_PID=$SEMAPHORE_DIR/transcode.cleanup.pid
CLEANUP_CMD_EXIT=$SEMAPHORE_DIR/transcode.cleanup.stop # flag file instructing that cleanup script must shutdown
CLEANUP_SHUTDOWN=$SEMAPHORE_DIR/transcode.cleanup.stopping # flag file informing that cleanup script is shutting down
CLEANUP_STOPPED=$SEMAPHORE_DIR/transcode.cleanup.stopped # flag file informing that cleanup script has been shut down
#
# 0 - OFF No logging (log file is not created)
# 1 - WARN Log warnings (very few occurences)
# 2 - INFO Log few most important events (deletion of files)
# 3 - DEBUG Log explanatory messages
# 4 - TRACE Log commands executed during processing and their output
#
CLEANUP_LOG_LEVELS=(O W I D T) # prefix log entries to notify about log level
CLEANUP_LOG_LEVEL_NAMES=(OFF WARN INFO DEBUG TRACE)
CLEANUP_LOG_LEVEL=$1 # 0 - OFF | 1 - WARN | 2 - INFO | 3 - DEBUG | 4 - TRACE
CLEANUP_LOG_TIMESTAMP=$2 # 1 - log entries will be prefixed with timestamp | 0 - log entries without timestamp
CLEANUP_INACTIVITY_SHUTDOWN_SECONDS=3600 # seconds of inactivity when no PID file is found after which the cleanup script will exit (default: 3600 = 1 hour)
CLEANUP_ALL_WHEN_SPACE_REACHES_PERC=98 # when used space is at least this number % of total space then delete most of TS files (default: 98)
CLEANUP_ALL_KEEP_TS_MOD_SECONDS=1 # when deleting most of TS files to free up space, do not delete files which where modified less than this num of sec before
# allowed space will be calculated per each Segment ID
#CLEANUP_WHEN_SPACE_REACHES_PERC=35 # when used space is at least this number % of total space then delete TS files keeping few subsequent by TS ID
FFMPEG_WRAP_PAUSE_PERC=10 # FFMPEG WRAP will be paused (SIGSTOP) when space used by TS files takes up this much % of total space
FFMPEG_POS_TIME_STALL=3 # if TS file won't be created within this number of seconds since last FFMPEG position change then FFMPEG will be resumed even if it runs over allowed space
NO_SPACE_LM_FILE_COUNT=5 # when no space left in transcodes directory, this is number of last modified TS files to keep in array (potentially corrupted)
# cannot keep min number of subsequent files - for each Segment ID there will be different allowed space, and files will be kept based on consumed space
#KEEP_MIN_SEQUENTIAL_TS_COUNT=6 # when no space left, this is number of minimum TS files to keep after Last Accessed TS file, rest will be deleted (use -1 to keep all sequential TS files)
TS_SPACE_ALLOWED_PERC_DEFAULT=70 # this is the default % of used space allowed per Segment ID (it is used before calculation based on number of Segment IDs)
TS_SPACE_RESERVED_MIN_DIVIDER=3 # this is minimum divider - used when PID count is less than this number to determine the reserved size of directory space (for possible next TS segment)
TS_SPACE_RESERVED_MAX_DIVIDER=0 # this is maximum divider - used to specify count of parallel playbacks for optimal space usage (default 0 means infinite count)
# For example, if specify 2 then 95% of transcoding directory space will be split in half for 2 TS segments (minimum reserved space of 5% is still
# required for tolerable space calculation and 5% additional for any other files, but there is not enough reseve
# for possible next TS segment) if 3rd client is starting playback then there will not be enough space for any playback and
# latest TS files will get deleted, however when 3rd playback is started then this variable will be ignored and space will be
# calculated for 3 TS segments
TS_TOLERABLE_SPACE_OVERRUN_PERC=3 # this is the additional allowed (tolerable) space when allowed space is exceeded by TS files sizes
TS_INACTIVITY_RESTART_SECONDS=90 # FFMPEG will be restarted and TS files deleted when PID and TS files exist, but no file is accessed by client for this number of seconds
KEEP_TS_MOD_SECONDS=1 # number of seconds after Last Modified Date/Time when TS files will not be deleted
KEEP_PID_MOD_SECONDS=120 # number of seconds after Last Modified Date/Time when PID files will not be deleted
TRANSCODES_DIR_ESCAPED=${TRANSCODES_DIR//\//\\\/} # convert "/config/transcodes" to "\/config\/transcodes"
#SCHEDULE_RESTART_FFMPEG_TS_ID_COUNT=5 # number of segments left till scheduled restart of FFMPEG process
SCHEDULE_RESTART_FFMPEG_TS_ID_COUNT=2 # number of segments left till scheduled restart of FFMPEG process - 2 is default value, because
# Jellyfin checks for existance of the next two TS files following after currently buffered (Last Accessed) and
# if the second file does not exist then it checks if FFMPEG process is running, further there are two cases:
# a) FFMPEG process is running - client playback will stall because there is only one file after currently buffered (buffering of last one will not start)
# b) FFMPEG process is not running - Jellyfin will start new FFMPEG process to generate TS files starting with the missing one
# Example: 20 21 22 23
# ^^ (currently buffered)
# If we specify value 2 for this variable and TS ID=24 was deleted, TS ID=21 is currently buffered then
# FFMPEG process will be killed by this script when TS ID=22 will be buffered (deleted file - 2 files).
# Jellyfin will immediately start new FFMPEG process to create TS ID=24 and playback will continue without interruption.
SCHEDULE_RESUME_FFMPEG_TS_ID_COUNT=3 # number of segments left till scheduled resume of FFMPEG process
DEFAULT_LA_BUFFER_TIME=6 # default=6. Last x TS segment rotation times (time required to buffer till next TS file is accessed by player) for avg calculation
#DEFAULT_FFMPEG_RESTART_TIME=25 # default=25. Last x FFMPEG restart times for avg calculation
SEGMENT_LA_BUFF_STAT_SIZE=3 # how many entries will be stored to calculate average statistics (for array SEGMENT_LA_BUFF_TIME_STAT and SEGMENT_LA_BUFF_SIZE_STAT)
SEGMENT_LA_BUFF_SIZE_STAT_RESET_PERC=25 # what % increase in buffering size will trigger re-set of stored statistics values (for array SEGMENT_LA_BUFF_TIME_STAT and SEGMENT_TS_SIZE_STAT)
#SEGMENT_TS_SIZE_STAT_SIZE=6 # how many entries will be stored to calculate average statistics (for array SEGMENT_LA_BUFF_TIME_STAT and SEGMENT_TS_SIZE_STAT)
#SEGMENT_TS_SIZE_STAT_RESET_PERC=25 # what % increase in TS file size will trigger re-set of stored statistics values (for array SEGMENT_TS_SIZE_STAT)
#SEGMENT_FFMPEG_RESTART_TIME_STAT_SIZE=3 # how many entries ("x") will be stored to calculate average statistics (for array SEGMENT_FFMPEG_RESTART_TIME_STAT)
REMOVE_UNUSED_SEGMENTS_INTERVAL=3600 # interval in seconds to clean-up unused Segment IDs from runtime arrays (3600 seconds = 1 hour)
CLEANUP_DELETED_FILES_INTERVAL=1800 # interval in seconds to clean-up deleted TS files which still hold up space in transcoding directory (1800 seconds = 30 minutes)
CLEANUP_DELETED_FILES_ACC_SECONDS=900 # seconds since deleted file was last accessed before it is deleted during maintenance task (900 seconds = 15 minutes)
CLEANUP_ABENDONED_PROCESSES_INTERVAL=30 # interval in seconds to clean-up abendoned and sleeping FFMPEG processes (having process state = T)
TS_AVG_SIZE_DEFAULT=30000000 # assume this default TS file average size in bytes when statistics are not yet collected for a Segment ID
DATE_FORMAT="%F %T.%9N" # this format should match to the format that is returned by stat, ls and other commands
#
# Load from file
#
# IMPORTANT! Function remove_unused_segments uses $MAINTAINED_ARRAYS - it must be updated when new array is declared
# The function will remove elements from the array that have name of old unused Segment IDs
#
# add "SEGMENT_LA_BUFF_TIMESTAMP_ARRAY" if used
MAINTAINED_ARRAYS=("SEGMENT_LA_BUFF_TIME_STAT" \
"SEGMENT_LA_BUFF_SIZE_STAT" \
"SEGMENT_LA_BUFF_TIME_ARRAY" \
"SEGMENT_TS_SIZE_STAT" \
"SEGMENT_LA_TS_ID_ARRAY" \
"SEGMENT_INACTIVE_TIME_ARRAY" \
"SEGMENT_LAST_POS_CHANGE" \
"SCHEDULE_RESTART_FFMPEG_FOR_TS_ID" \
"SCHEDULE_RESUME_FFMPEG_FOR_TS_ID" \
) # "SEGMENT_FFMPEG_RESTART_TIME_STAT")
#declare -A SEGMENT_LA_BUFF_TIMESTAMP_ARRAY # key-value pairs, where key is Segment ID and value is last time of buffering TS file as date/time timestamp
declare -A SEGMENT_LA_BUFF_TIME_ARRAY # key-value pairs, where key is Segment ID and value is last time of buffering TS file in seconds
declare -A SEGMENT_LA_BUFF_TIME_STAT # key-value pairs, where key is Segment ID and value is array of x last TS segment rotation times
declare -A SEGMENT_LA_BUFF_SIZE_STAT
declare -A SEGMENT_TS_SIZE_STAT # key-value pairs, where key is Segment ID and value is array of x last TS segment file sizes
declare -A SEGMENT_LA_TS_ID_ARRAY # key-value pairs, where key is Segment ID and value is last accessed TS ID
declare -A SEGMENT_INACTIVE_TIME_ARRAY # key-value pairs, where key is Segment ID and value is last time of no buffering of TS files
declare -A SEGMENT_LAST_POS_CHANGE # key-value pairs, where key is Segment ID and value is last registered playback position change
#declare -A SEGMENT_FFMPEG_RESTART_TIME_STAT # key-value pairs, where key is Segment ID and value is array of x last FFMPEG restart times
declare -A SCHEDULE_RESTART_FFMPEG_FOR_TS_ID # key-value pairs, where key is Segment ID and value is TS_ID which was deleted
#declare -A SEGMENT_FFMPEG_RESUME_TIME_STAT # key-value pairs, where key is Segment ID and value is array of x last FFMPEG resume times
declare -A SCHEDULE_RESUME_FFMPEG_FOR_TS_ID # key-value pairs, where key is Segment ID and value is TS_ID which was paused
#
# Following arrays are re-defined in every loop
#
declare -A TS_SPACE_ALLOWED_PERC_ARRAY
declare -A TS_SPACE_TOLERABLE_PERC_ARRAY
#
# Reset bash script configuration
#
SECONDS=0 # reset timer
REMOVE_UNUSED_SEGMENTS_LAST=$SECONDS # Store last time when clean-up was performed
USER_ID=$(id -u) # USER_ID is used for tracking if script is run by ROOT user
CLEANUP_DELETED_FILES_LAST=$((SECONDS + 1800)) # Store last time when clean-up was performed (first maintenance after 1800 seconds = 30 minutes)
CLEANUP_ABENDONED_PROCESSES_LAST=$SECONDS # Store last time when clean-up was performed
TS_SPACE_ALLOWED=
TS_SPACE_ALLOWED_PERC=$TS_SPACE_ALLOWED_PERC_DEFAULT
[ "$CLEANUP_LOG_LEVEL" != "" ] || CLEANUP_LOG_LEVEL=0 # set to default (OFF) if not configured
[ "$CLEANUP_LOG_TIMESTAMP" != "" ] || CLEANUP_LOG_TIMESTAMP=0 # set to default if not configured
CLEANUP_INACTIVITY_SHUTDOWN_COUNTER=-1 # deactivate
CLEANUP_LOG_MAXSIZE_COUNTER=$SECONDS # time counter to check log file size at hardcoded interval (5 minutes)
#
# $1 log level (1 to 4) - message will be logged only if CLEANUP_LOG_LEVEL is equal or higher than the $1
# $2 log message
#
function log {
if [ $CLEANUP_LOG_LEVEL -ge $1 ]; then
if [ $CLEANUP_LOG_TIMESTAMP -eq 1 ]; then # log with timestamp
echo "${CLEANUP_LOG_LEVELS[$1]} $(date +"$DATE_FORMAT") [$$] $2" >> $CLEANUP_LOG;
else
echo "[$$] $2" >> $CLEANUP_LOG;
fi
fi
}
function log_warn {
log 1 "$1"
}
function log_info {
log 2 "$1"
}
function log_debug {
log 3 "$1"
}
function log_trace {
log 4 "$1"
}
function trace_is_on {
[ $CLEANUP_LOG_LEVEL -ge 4 ]
return
}
function debug_is_on {
[ $CLEANUP_LOG_LEVEL -ge 3 ]
return
}
function info_is_on {
[ $CLEANUP_LOG_LEVEL -ge 2 ]
return
}
function log_print_config {
log_info "--------------------------------------------------------------------------------"
log_info "Starting Transcoding clean-up process"
log_info ""
log_info "Configuration:"
log_info " SCRIPT_DIR: $SCRIPT_DIR"
log_info " TRANSCODES_DIR: $TRANSCODES_DIR"
log_info " SEMAPHORE_DIR: $SEMAPHORE_DIR"
log_info " CLEANUP_PID: $CLEANUP_PID"
log_info " CLEANUP_LOG: $CLEANUP_LOG"
log_info " CLEANUP_LOG_LEVEL: $CLEANUP_LOG_LEVEL (${CLEANUP_LOG_LEVEL_NAMES[$CLEANUP_LOG_LEVEL]})"
log_info " FFMPEG_WRAP_PAUSE_PERC: $FFMPEG_WRAP_PAUSE_PERC"
log_info " CLEANUP_ALL_WHEN_SPACE_REACHES_PERC: $CLEANUP_ALL_WHEN_SPACE_REACHES_PERC"
#log_info " CLEANUP_WHEN_SPACE_REACHES_PERC: $CLEANUP_WHEN_SPACE_REACHES_PERC"
log_info " NO_SPACE_LM_FILE_COUNT: $NO_SPACE_LM_FILE_COUNT"
#log_info " KEEP_MIN_SEQUENTIAL_TS_COUNT: ${KEEP_MIN_SEQUENTIAL_TS_COUNT}$(if [ $KEEP_MIN_SEQUENTIAL_TS_COUNT -eq -1 ]; then echo ' (keep all)'; fi)"
log_info " KEEP_TS_MOD_SECONDS: $KEEP_TS_MOD_SECONDS"
log_info " KEEP_PID_MOD_SECONDS: $KEEP_PID_MOD_SECONDS"
log_info ""
if [ $USER_ID -eq 0 ]; then
log_warn "WARNING: RUNNING SCRIPT UNDER ROOT USER. FILES WITHOUT READ PERMISSION WHICH WERE CREATED BY JELLYFIN USER ARE NOT ACCESSIBLE FOR ROOT."
fi
}
log_print_config
#
# Decomposes given argument into global variables
# $1 - line containing Last Accessed, Last Modified, File size, Filename
# (eg, 2023-02-02 22:33:37.234998714 +0000 2023-02-02 22:33:29.095998604 +0000 23018532 ./572f0e503dcbc2ea18bff03a55e2162610.ts)
#
# ACC_DATE - Last Accessed Date
# ACC_TIME - Last Accessed Time
# MOD_DATE - Last Modified Date
# MOD_TIME - Last Modified Time
# FILESIZE - File size in bytes
# FILENAME - Filename (may include path)
#
function decompose_file_xysn {
t=$1
ACC_DATE=${t%% *}; t=${t#* }
ACC_TIME=${t%% *}; t=${t#* }
t=${t#* } # ignore +0000
MOD_DATE=${t%% *}; t=${t#* }
MOD_TIME=${t%% *}; t=${t#* }
t=${t#* } # ignore +0000
FILESIZE=${t%% *}; t=${t#* }
FILENAME=${t%% *}
}
#
# Decomposes given argument into global variables
# $1 - line containing Filename, Last Accessed, Last Modified
# (eg, ./572f0e503dcbc2ea18bff03a55e2162610.ts 2023-02-02 22:33:37.234998714 +0000 2023-02-02 22:33:29.095998604 +0000)
#
# ACC_DATE - Last Accessed Date
# ACC_TIME - Last Accessed Time
# MOD_DATE - Last Modified Date
# MOD_TIME - Last Modified Time
# FILENAME - Filename (may include path)
#
function decompose_file_nxy {
t=$1
FILENAME=${t%% *}; t=${t#* }
ACC_DATE=${t%% *}; t=${t#* }
ACC_TIME=${t%% *}; t=${t#* }
t=${t#* } # ignore +0000
MOD_DATE=${t%% *}; t=${t#* }
MOD_TIME=${t%% *}; t=${t#* }
}
#
# Decomposes given argument into global variables
# $1 - line containing Filename, Size, Last Accessed, Last Modified
#
function decompose_file_nsxy {
t=$1
FILENAME=${t%% *}; t=${t#* }
FILESIZE=${t%% *}; t=${t#* }
ACC_DATE=${t%% *}; t=${t#* }
ACC_TIME=${t%% *}; t=${t#* }
t=${t#* } # ignore +0000
MOD_DATE=${t%% *}; t=${t#* }
MOD_TIME=${t%% *}; t=${t#* }
}
#
# Decomposes given argument into global variables
# $1 - line containing Last Modified, Filename
#
function decompose_file_yn {
t=$1
MOD_DATE=${t%% *}; t=${t#* }
MOD_TIME=${t%% *}; t=${t#* }
t=${t#* } # ignore +0000
FILENAME=${t%% *};
}
#
# Decomposes given argument into global variables
# $1 - line containing Last Modified (milliseconds), Filename
#
function decompose_file_Yn {
t=$1
MOD_MILLIS=${t%% *}; t=${t#* }
FILENAME=${t%% *};
}
#
# $1 - filepath to retrieve modification date/time
#
# Usage: if get_file_mod_date test.txt; then
# echo "File modified at: $MOD_DATE $MOD_TIME"
# else
# echo "File not found"
# fi
#
function get_file_mod_date {
t=$(stat -c '%y' $1 2>&1) # 2>&1 - error to stdout
if [ "${t:0:5}" == "stat:" ]; then # stat: cannot statx 'test.txt': No such file or directory
[ 1 != 1 ] # return false
else
MOD_DATE=${t%% *}; t=${t#* }
MOD_TIME=${t%% *} # return true
fi
}
#
# $1 - Segment ID (eg, 036975d222b737b6f47a82ac186e5d47)
# $2 - TS filepath (eg, /config/transcodes/036975d222b737b6f47a82ac186e5d47251.ts)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# $TS_ID will contain TS file ID
#
function extract_ts_id {
[[ "$2" =~ .*$1([0-9]+).ts ]]
TS_ID=${BASH_REMATCH[1]}
}
#
# Returns date/time difference in seconds comparing two date/time
# $1 - date/time to compare (earlier time)
# $2 - date/time to compare (later time)
#
# Result is returned in DIFF_SECONDS
#
# Example: datetime_diff "2023-02-11 17:27:48.019751128 +0000" "2023-02-11 17:28:02.300751321 +0000"
# echo $DIFF_SECONDS
# 14
#
function datetime_diff {
DIFF_SECONDS=$(( $(date -d "$2" "+%s") - $(date -d "$1" "+%s") ))
}
#
# Returns date/time difference in seconds comparing to current date/time
# $1 - date/time to compare
#
# Result is returned in DIFF_SECONDS
#
# Example: datetime_diff "2023-02-11 17:28:02.300751321 +0000"
# echo $DIFF_SECONDS
# 14917
#
function datetime_diff_now {
DIFF_SECONDS=$(( $(date "+%s") - $(date -d "$1" "+%s") ))
}
#
# $1 date/time in any supported format by "date" command
# $2 number of seconds to deduct from $1
#
# Returns date/time with added (or deducted if negative) x number of seconds in format: $DATE_FORMAT
# Returns new value in global variable $DATE_TIME
# Example: "2023-02-11 17:28:02.300751321"
#
function datetime_add_seconds {
DATE_TIME=$(date -d "$1 $2 sec" +"$DATE_FORMAT")
}
#
# $1 date/time in any supported format by "date" command
# $2 number of seconds to deduct from $1
#
# Returns milliseconds with added (or deducted if negative) x number of seconds in format: nnn.fff
# Returns new value in global variable $MILLIS
# Example: 1676136482.300751321 to 1676136481.300751321
#
function millis_add_seconds {
MILLIS="$(( ${1%%.*}$2 )).${1##*.}"
}
#
# $1 date/time in any supported format by "date" command
#
# Returns representation of date/time in milliseconds using "echo".
# Example: "2023-02-11 17:28:02.300751321 +0000" to 1676136482.300751321
#
function date_to_millis {
echo $(date -d "$1" "+%s.%N")
}
#
# $1 milliseconds (format nnn.fff)
#
# Returns representation of milliseconds in date/time using "echo".
# Example: 1676136482.300751321 to "2023-02-11 17:28:02.300751321 +0000"
#
function millis_to_date {
echo $(date -d @$1 +"$DATE_FORMAT")
}
#
# Test if $1 argument is found in array specified in $2 argument
# Example: if elementInArr "$test" "${LM_FILES_ARRAY[@]}"; then
#
#function elementInArr {
# local e match="$1"
# shift
# for e; do [[ "$e" == "$match" ]] && return 0; done
# return 1
#}
#
# Test whether the value is present in an array
#
# Example:
# if value "test" in array; then echo exists; fi
#
function value {
if [ "$2" != in ]; then
echo "Incorrect usage."
echo "Correct usage: value {value} in {array}"
return
fi
local e match="$1"
for e in $(eval 'echo ${'$3'[@]}'); do [[ "$e" == "$match" ]] && return 0; done
return 1
}
#
# Tests if filepath specified in $1 argument is found in $LM_FILES_ARRAY
# LM_FILES_ARRAY represents 5 last modified files when there is no free space left in transcodes directory
#
function is_in_lm_files_array {
#elementInArr "$1" "${LM_FILES_ARRAY[@]}"
[ value "$1" in LM_FILES_ARRAY ]
return $?
}
#
# $1 - Segment ID
#
# Returns FFMPEG WRAP PID from PID file into variable $FFMPEG_WRAP_PID
#
function get_ffmpeg_pid {
local segment_id=$1
log_trace "#READ PID# head -1 $SEMAPHORE_DIR/${segment_id}.pid"
if trace_is_on; then head -1 $SEMAPHORE_DIR/${segment_id}.pid >> $CLEANUP_LOG 2>&1; echo "#" >> $CLEANUP_LOG; fi;
FFMPEG_WRAP_PID=$(head -1 $SEMAPHORE_DIR/${segment_id}.pid)
if [[ "$FFMPEG_WRAP_PID" == "" ]]; then
log_warn "FFMPEG WRAP PID file is empty. Terminating clean-up process."
#
# TODO: DELETE ALL TS FILES AND KILL ALL *FFMPEG* CHILD PROCESSES - GLOBAL RESTART
# FFMPEG processes are listed with "ps" command where process name is FFMPEG WRAP
#
exit
fi
}
#
# $1 - Segment ID
#
# Returns FFMPEG WRAP PID from PAUSE file into variable $PAUSE_PID, full path to PAUSE file in $PAUSE_FILEPATH
#
function get_ffmpeg_pause_pid {
local segment_id=$1
PAUSE_FILEPATH=$SEMAPHORE_DIR/${segment_id}.pause
PAUSE_PID=
if [ -f $PAUSE_FILEPATH ]; then
log_trace "#READ PID# head -1 $PAUSE_FILEPATH"
if trace_is_on; then head -1 $PAUSE_FILEPATH >> $CLEANUP_LOG 2>&1; echo "#" >> $CLEANUP_LOG; fi;
PAUSE_PID=$(head -1 $PAUSE_FILEPATH)
log_trace "PAUSE PID: $PAUSE_PID"
fi
}
#
# $1 - Segment ID
#
# Returns FFMPEG last position from which the TS files are created (FFMPEG argument -START_NUMBER)
# Position is set in variable $FFMPEG_POS, time of the last position change in $FFMPEG_POS_TIME
# Full filepath to POS file in $POS_FILEPATH
#
function get_ffmpeg_pos {
local segment_id=$1
POS_FILEPATH=$SEMAPHORE_DIR/${segment_id}.pos
if [ -f $POS_FILEPATH ]; then
log_trace "#READ POS# head -1 $POS_FILEPATH"
if trace_is_on; then head -1 $POS_FILEPATH >> $CLEANUP_LOG 2>&1; echo "#" >> $CLEANUP_LOG; fi;
FFMPEG_POS="$(head -1 $POS_FILEPATH)" # Sample value: 123 05-03-2023 12:20:17.655142648, where 123 is the position number
FFMPEG_POS_TIME="${FFMPEG_POS#* }" # extract the part after the first space
FFMPEG_POS=${FFMPEG_POS%% *} # extract the part before the first space
log_trace "FFMPEG POS: $FFMPEG_POS"
else
FFMPEG_POS=
FFMPEG_POS_TIME=
fi
}
#
# $1 - Segment ID
# $2 - Signal (for "kill" command)
#
# Send signal to FFMPEG WRAP child processes of the specified Segment ID
# Process ID for FFMPEG WRAP will be retrieved from PID file
#
# Global variable FFMPEG_WRAP_PID will be set to the WRAP PID
#
function signal_ffmpeg {
local segment_id=$1
get_ffmpeg_pid $segment_id # get $FFMPEG_WRAP_PID
log_info "Signaling [$2] to child processes of FFMPEG WRAP with PID=$FFMPEG_WRAP_PID:"
#
# FNR>1 will list all subsequent child processes, except the first (main) process
#
log_trace "#LIST CHILD PROCESSES# ps -o pid= -p $FFMPEG_WRAP_PID --ppid $FFMPEG_WRAP_PID --forest | awk 'FNR>1{print \$1}'"
if trace_is_on; then ps -o pid= -p $FFMPEG_WRAP_PID --ppid $FFMPEG_WRAP_PID --forest | awk 'FNR>1{print $1}' >> $CLEANUP_LOG 2>&1; echo "#" >> $CLEANUP_LOG; fi;
for PID in $(ps -o pid= -p $FFMPEG_WRAP_PID --ppid $FFMPEG_WRAP_PID --forest | awk 'FNR>1{print$1}'); do
log_info " Signaling FFMPEG child process PID=$PID [$2]"
kill $2 $PID
if [[ "$2" == "-SIGKILL" || "$2" == "-9" ]]; then
#
# When killing sleeping process, it will remain sleeping (in status=T). When FFMPEG is using video card which
# has limited the number of allowed processes then such sleeping process will be counted as active. And card
# may report Out of memory error when number of active process reaches the limit (even though the processes were killed).
#
log_info " Signaling FFMPEG child process PID=$PID [-SIGCONT] to properly kill the process in case if it is sleeping (status=T)"
kill -SIGCONT $PID 2>/dev/null
fi
done
}
#
# $1 - Segment ID
#
function is_ffmpeg_paused {
local segment_id=$1
get_ffmpeg_pid $segment_id # get $FFMPEG_WRAP_PID
log_debug "Verifying if FFMPEG for Segment ID=$segment_id is paused."
#
# FNR>1 will list all subsequent child processes, except the first (main) process
#
log_trace "#LIST CHILD PROCESSES# ps -o state= -p $FFMPEG_WRAP_PID --ppid $FFMPEG_WRAP_PID --forest | awk 'FNR>1{print\$1}'"
if trace_is_on; then ps -o state= -p $FFMPEG_WRAP_PID --ppid $FFMPEG_WRAP_PID --forest | awk 'FNR>1{print $1}' >> $CLEANUP_LOG 2>&1; echo "#" >> $CLEANUP_LOG; fi;
for STATE in $(ps -o state= -p $FFMPEG_WRAP_PID --ppid $FFMPEG_WRAP_PID --forest | awk 'FNR>1{print$1}'); do
if [ "$STATE" == "T" ]; then
log_info "FFMPEG is paused (one of FFMPEG PID=$FFMPEG_WRAP_PID child processes is paused)"
return
fi
done
log_info "FFMPEG is not paused"
[ 1 == 0 ] # return false answer
}
#
# $1 - Segment ID
#
# All FFMPEG WRAP child processes will be killed for the specified Segment ID
# process ID for FFMPEG WRAP will be retrieved from PID file
#
# NOTE: It takes ~25 seconds after FFMPEG child process restart till the first TS file
# is created with the new FFMPEG process
#
function restart_ffmpeg {
cancel_ffmpeg_resume_if_scheduled $1
log_info "Restarting FFMPEG child processes for Segment ID=$1"
signal_ffmpeg $1 -SIGKILL
#
# If there is PAUSE file then delete it
#
get_ffmpeg_pause_pid $segment_id # get $PAUSE_PID and $PAUSE_FILEPATH
if [ "$PAUSE_PID" != "" ]; then
log_debug "Deleting PAUSE file due to restart of FFMPEG"
rm -f $PAUSE_FILEPATH
fi
}
#
# $1 - Segment ID
#
# All FFMPEG WRAP child processes will be killed for the specified Segment ID
# Process ID for FFMPEG WRAP will be retrieved from PID file
#
# NOTE: It takes ~25 seconds after FFMPEG child process restart till the first TS file
# is created with the new FFMPEG process
#
# Return: 0 - failure, 1 - success, 2 - already paused
#
function pause_ffmpeg {
FFMPEG_WRAP_PID=
local segment_id=$1
local result=0
get_ffmpeg_pid $segment_id # get $FFMPEG_WRAP_PID
#
# Create PAUSE file
#
if [ "$FFMPEG_WRAP_PID" == "" ]; then
log_warn "Cannot pause FFMPEG because cannot read PID file for the Segment ID=$1"
else
get_ffmpeg_pause_pid $segment_id # get $PAUSE_PID and $PAUSE_FILEPATH
#
# Pause FFMPEG WRAP process PID if not already paused
#
if [[ "$PAUSE_PID" != "" ]] && [[ "$PAUSE_PID" == "$FFMPEG_WRAP_PID" ]]; then
log_info " FFMPEG WRAP process PID child processes are already paused: $FFMPEG_WRAP_PID"
result=2
else
log_info " Pausing FFMPEG child processes for Segment ID=$1"
signal_ffmpeg $1 -SIGSTOP
echo $FFMPEG_WRAP_PID > $PAUSE_FILEPATH
result=1
fi
fi
return $result
}
#
# $1 - Segment ID
#
# All FFMPEG WRAP child processes will be resumed for the specified Segment ID
# Process ID for FFMPEG WRAP will be retrieved from PID file and PAUSE file
#
# NOTE: It takes ~25 seconds after FFMPEG child process restart till the first TS file
# is created with the new FFMPEG process
#
function resume_ffmpeg {
log_info "Resuming FFMPEG child processes for Segment ID=$1"
FFMPEG_WRAP_PID= # need to determine the value before "signal_ffmpeg" function
local segment_id=$1
get_ffmpeg_pid $segment_id # get $FFMPEG_WRAP_PID
#
# Read PID from PAUSE file
#
get_ffmpeg_pause_pid $segment_id # get $PAUSE_PID and $PAUSE_FILEPATH
#
# Resume the process
#
if [ "$FFMPEG_WRAP_PID" != "" ]; then
#
# Resume FFMPEG WRAP process if the current process PID is the same as for earlier paused process
#
if [[ "$PAUSE_PID" != "" ]] && [[ "$PAUSE_PID" == "$FFMPEG_WRAP_PID" ]]; then
log_info "Resuming FFMPEG child processes for Segment ID=$1"
signal_ffmpeg $1 -SIGCONT
else
log_info "Cannot resume FFMPEG WRAP process with PID=$FFMPEG_WRAP_PID becausee it is not the same as PID from PAUSE file: $PAUSE_PID"
log_warn "Deleting PAUSE file $PAUSE_FILEPATH because FFMPEG WRAP PID=$FFMPEG_WRAP_PID differs from PID=$PAUSE_PID stored in the PAUSE file"
fi
rm -f $PAUSE_FILEPATH
cancel_ffmpeg_resume $1 # mark that scheduled resume was completed
fi
}
#
# $1 - Segment ID
# $2 - TS ID
#
# Store TS ID provided in argument $2.
# Only most earlier TS ID will be stored when calling function multiple times
#
function schedule_ffmpeg_restart_for_ts_id {
local value=${SCHEDULE_RESTART_FFMPEG_FOR_TS_ID[$1]}
if [ "$value" == "" ]; then
SCHEDULE_RESTART_FFMPEG_FOR_TS_ID[$1]=$2
log_info "Scheduled restart of FFMPEG process for TS ID: $2"
else
if [ $2 -lt $value ]; then # store earliest TS ID
SCHEDULE_RESTART_FFMPEG_FOR_TS_ID[$1]=$2
log_info "Scheduled restart of FFMPEG process for TS ID: $2"
fi
fi
}
#
# $1 - Segment ID
# $2 - TS ID (currently played and last accessed by the player)
#
# Function will verify TS ID provided in argument $2 and compare
# with TS ID that is stored for scheduled killing of FFMPEG process
#
# If there are less than 3 segments till the scheduled TS ID then
# all FFMPEG child processes are killed, example:
# Scheduled: 575 (earlier deleted, so need to kill FFMPEG to re-create it)
# Argument: 573 (currently accessed by player)
# Result: FFMPEG is going to be killed (because 2 segments 573,574 are remaining till gap)
#
# NOTE: It takes ~25 seconds after FFMPEG child process restart till the first TS file
# is created with the new FFMPEG process
#
function restart_ffmpeg_if_scheduled {
local value=${SCHEDULE_RESTART_FFMPEG_FOR_TS_ID[$1]}
if [ "$value" != "" ]; then
log_trace "Scheduled restart of FFMPEG will happen when LA TS ID will be $(($value - $SCHEDULE_RESTART_FFMPEG_TS_ID_COUNT)), current TS ID=$2, deleted TS ID=$value"
if [ $(($2 + $SCHEDULE_RESTART_FFMPEG_TS_ID_COUNT + 1)) -gt $value ]; then # matching sequence + 1
log_info "Scheduled restart of FFMPEG activated. Reason: LA TS ID=$2 is approaching deleted TS ID=$value"
restart_ffmpeg $1
SCHEDULE_RESTART_FFMPEG_FOR_TS_ID[$1]="" # mark that restart was completed
# log_info " Waiting 3 seconds till FFMPEG is restarted and new files created"
# sleep 3
fi
fi
}
#
# $1 - Segment ID
#
function cancel_ffmpeg_restart {
SCHEDULE_RESTART_FFMPEG_FOR_TS_ID[$1]=""
}
#
# $1 - Segment ID
#
function is_ffmpeg_restart_scheduled {
[ "${SCHEDULE_RESTART_FFMPEG_FOR_TS_ID[$1]}" != "" ]
return
}
function cancel_ffmpeg_restart_if_scheduled {
if is_ffmpeg_restart_scheduled $1; then
log_info " Cancelling FFMPEG restart"
cancel_ffmpeg_restart $1
fi
}
#
# $1 - Segment ID
# $2 - TS ID
#
# Store TS ID provided in argument $2.
# Only most earlier TS ID will be stored when calling function multiple times
#
function schedule_ffmpeg_resume_for_ts_id {
local value=${SCHEDULE_RESUME_FFMPEG_FOR_TS_ID[$1]}
if [ "$value" == "" ]; then
SCHEDULE_RESUME_FFMPEG_FOR_TS_ID[$1]=$2
log_info "Scheduled resume of FFMPEG process for TS ID: $2"
else
if [ $2 -lt $value ]; then # store earliest TS ID
SCHEDULE_RESUME_FFMPEG_FOR_TS_ID[$1]=$2
log_info "Scheduled resume of FFMPEG process for TS ID: $2"
fi
fi
}
#
# $1 - Segment ID
# $2 - TS ID (currently played and last accessed by the player)
#
# Function will verify TS ID provided in argument $2 and compare
# with TS ID that is stored for scheduled resuming of FFMPEG process
#
# If there are less than 3 segments till the scheduled TS ID then
# all FFMPEG child processes are resumed, example:
# Scheduled: 575 (earlier paused, so need to resume FFMPEG to continue writing the file)
# Argument: 573 (currently accessed by player)
# Result: FFMPEG is going to be resumed (because 2 segments 573, 574 are remaining till potentially unfinished TS file 575)
#
# NOTE: Resume takes immediate effect on FFMPEG processes
#
function resume_ffmpeg_if_scheduled {
local value=${SCHEDULE_RESUME_FFMPEG_FOR_TS_ID[$1]}
if [ "$value" != "" ]; then
log_trace "Scheduled resume of FFMPEG will happen when LA TS ID will be $(($value - $SCHEDULE_RESUME_FFMPEG_TS_ID_COUNT)), current TS ID=$2, paused TS ID=$value"
if [ $(($2 + $SCHEDULE_RESUME_FFMPEG_TS_ID_COUNT + 1)) -gt $value ]; then # matching sequence + 1
log_info "Scheduled resume of FFMPEG is requested. Reason: LA TS ID=$2 is approaching paused TS ID=$value"
[ 1 == 1 ] # return true answer
return
fi
fi
[ 1 == 0 ] # return false answer
}
#
# $1 - Segment ID
#
# Resume FFMPEG when buffering will be approaching TS files that were
# modified within last second of Last Modified date/time
#
function schedule_ffmpeg_resume_for_last_modified {
local segment_id=$1
local ts_pattern=${segment_id}*.ts
local line3
local store_ts_id=$TS_ID # store original $TS_ID
#
# Resume FFMPEG for TS files which were modified within the last second
#
log_info "Schedule FFMPEG resume for TS files (with non-zero size) which were modified within the last second of latest DATE MODIFIED."
log_trace "#LIST MODIFIED FILES DESCENDING# find $TRANSCODES_DIR -name \"$ts_pattern\" -type f ! -size 0 -exec stat -c '%.Y %n' {} \; 2>/dev/null | sort -n -r"
if trace_is_on; then find $TRANSCODES_DIR -name "$ts_pattern" -type f ! -size 0 -exec stat -c '%.Y %n' {} \; 2>/dev/null | sort -n -r >> $CLEANUP_LOG 2>&1; echo "#" >> $CLEANUP_LOG; fi
RESUME_FROM_MOD_MILLIS=
for line3 in $(find $TRANSCODES_DIR -name "$ts_pattern" -type f ! -size 0 -exec stat -c '%.Y %n' {} \; 2>/dev/null | sort -n -r); do
decompose_file_Yn $line3
RESUME_MOD_MILLIS=$MOD_MILLIS # %Y
RESUME_FILENAME=$FILENAME # %n
#
# Determine the date/time range of TS files for resuming FFMPEG
#
if [ "$RESUME_FROM_MOD_MILLIS" == "" ]; then
#
# Store modification date/time of the last modified TS file -1 second in $RESUME_FROM_MOD_MILLIS
#
millis_add_seconds $RESUME_MOD_MILLIS -1 # deduct 1 second
RESUME_FROM_MOD_MILLIS=$MILLIS
if info_is_on; then
log_info "Schedule resume for the smallest TS ID from all TS files in range between $(millis_to_date $RESUME_FROM_MOD_MILLIS) and $(millis_to_date $RESUME_MOD_MILLIS)."
fi
fi
if [ "$RESUME_MOD_MILLIS" \> "$RESUME_FROM_MOD_MILLIS" ]; then # stop deleting TS files when their modification date is within the last one second range
#
# Schedule resuming FFMPEG process before the next TS ID
#
extract_ts_id $SEGMENT_ID "$RESUME_FILENAME" # into $TS_ID
schedule_ffmpeg_resume_for_ts_id $SEGMENT_ID $(($TS_ID + 1)) # only for the smallest TS ID in this loop will be scheduled
# TS_ID + 1 is used because resuming will happen before reaching TS_ID + 1
else
if ([ "$RESUME_MOD_MILLIS" \< "$RESUME_FROM_MOD_MILLIS" ] || [ "$RESUME_MOD_MILLIS" == "$RESUME_FROM_MOD_MILLIS" ]); then
break # do not continue looping through files because the list is sorted by modification date in descending order
fi
fi
done
TS_ID=$store_ts_id # revert back the original TS_ID as it may be globally used by the calling function
}
#
# $1 - Segment ID
#
function cancel_ffmpeg_resume {
SCHEDULE_RESUME_FFMPEG_FOR_TS_ID[$1]=""
}
#
# $1 - Segment ID
#
function is_ffmpeg_resume_scheduled {
[ "${SCHEDULE_RESUME_FFMPEG_FOR_TS_ID[$1]}" != "" ]
return
}
function cancel_ffmpeg_resume_if_scheduled {
if is_ffmpeg_resume_scheduled $1; then
log_info " Cancelling FFMPEG resume"
cancel_ffmpeg_resume $1
fi
}
#
# $1 - Segment ID
#
# Store time when buffering started for the last accessed TS file
#
function store_ts_buffer_seconds {
SEGMENT_LA_BUFF_TIME_ARRAY["$1"]=$SECONDS
}
#
# $1 - Segment ID
#
# Return seconds lapsed since buffering started for the last accessed TS file
#
function get_ts_buffer_seconds {
echo $(( $SECONDS-SEGMENT_LA_BUFF_TIME_ARRAY["$1"] ))
}
#
# $1 - Segment ID
#
# Set time for inactive time monitoring when LA not found
#
function store_ts_inactive_seconds {
SEGMENT_INACTIVE_TIME_ARRAY["$1"]=$SECONDS
}
#
# $1 - Segment ID
#
# Remove inactive time monitoring
#
function clear_ts_inactive_seconds {
SEGMENT_INACTIVE_TIME_ARRAY["$1"]=""
}
#
# $1 - Segment ID
#
# Return TS ID of the TS file that was last accessed (buffered)
#
function get_last_accessed_ts_id {
echo ${SEGMENT_LA_TS_ID_ARRAY["$1"]}
}
#
# $1 - Segment ID
# $1 - TS ID
#
# Set TS ID of the last accessed (buffered) TS file
#
function store_last_accessed_ts_id {
SEGMENT_LA_TS_ID_ARRAY["$1"]=$2
}
#
# $1 - Segment ID
#
# Retrieve time for inactive time monitoring when LA not found
#
function get_ts_inactive_seconds {
echo $(( $SECONDS-SEGMENT_INACTIVE_TIME_ARRAY["$1"] ))
}
#
# $1 - Segment ID
#
# True if inactive time monitoring is used when LA not found
#
function tracking_ts_inactive_seconds {
[ "${SEGMENT_INACTIVE_TIME_ARRAY[$1]}" != "" ]
return
}
#
# $1 - Key
# $2 - Array
#
# Return element from array $2 by using key in $1
# Returns empty string if not found
#
#unction get_element_by_key {
# if exists "$1" in $2; then
# eval 'echo ${'$2'[$1]}'
# else
# echo ""
# fi
#
#
# $1 - Segment ID
#
# Return last recorded buffering time
# Returns -1 if no record exists, or if Segment ID not found
#
function get_ts_buffer_time_stats_last {
if exists "$1" in SEGMENT_LA_BUFF_TIME_STAT; then
local values=($(echo ${SEGMENT_LA_BUFF_TIME_STAT["$1"]} | tr ' ' "\n"))
local count=${#values[@]}
if [ $count -eq 0 ]; then
echo -1
else
echo ${values[(($count - 1))]}
fi
else
echo -1
fi
}
#
# $1 - Segment ID
#
# Return last recorded buffering size
# Returns -1 if no record exists, or if Segment ID not found
#
function get_ts_buffer_size_stats_last {
if exists "$1" in SEGMENT_LA_BUFF_SIZE_STAT; then
local values=($(echo ${SEGMENT_LA_BUFF_SIZE_STAT["$1"]} | tr ' ' "\n"))
local count=${#values[@]}
if [ $count -eq 0 ]; then
echo -1
else
echo ${values[(($count - 1))]}
fi
else
echo -1
fi
}
#
# $1 - first number
# $2 - second number
# $3 (optional) - if specified (eg, "true") then will return always positive percentage
#
# Calculate difference between numbers in percentage
#
# Source: https://stackoverflow.com/a/24253318
function diff_perc {
local divider=$2
local positive=$3
[ "$divider" -ne 0 ] || divider=1 # set divider to 1 if divider is 0
local diff=$(($divider - $1))
[ "$positive" == "" ] || [ "$diff" -ge 0 ] || diff=$((-diff)) # ensure it is always a positive number when $3 is specified
echo $((diff * 100 / $divider))
}
#
# $1 - first number
# $2 - second number
#
# Returns argument which is largest number
#
function max {
if [ "$1" -gt "$2" ]; then
echo $1
else
echo $2
fi
}
#
# $1 - Segment ID
# $2 - Seconds measured for the buffering between two subsequent TS files
# $3 - Buffered TS file size in bytes
#
# Add new measurement to the last x measurements of how much time it takes for
# client player to buffer between two subsequent TS files. Store the file size.
#
# Number of measurements is determined by $SEGMENT_LA_BUFF_STAT_SIZE
#
# NOTE: when playback quality is changed (for example, 4K to 1080p) then
# collected stats will still be re-usable because calculated
# network transfer speed bytes per second per Segment ID will not change.
#
# NOTE: call this function only when sure that file was buffered fully -