0

I need to subtract two time codes from each other, I have multiple in an array and the output looks like this:

**Input:**
echo ${arr3[0]}
echo ${arr3[1]}

**Output:**    
00:00:22.180 --> 00:00:25.600
00:00:24.070 --> 00:00:27.790

In this example the following equation needs to take place 00:00:25.600 - 00:00:22.180 = output into arrayand 00:00:27.790 - 00:00:24.070 = output into the same array It needs to be in the same format so can use it in FFMPEG.

I also need the first timecode of each array entry, so:

00:00:22.180 
00:00:24.070

in another array so I can use these inputs in ffmpeg as well.

EDIT:

I'll use the data as follows

time=$(The first timecode of the array)
duration=$(Timecodes subtracted)

ffmpeg -i movie.mp4 -ss $time -t $duration -async 1 cut.mp4 
5
  • I think you should spell out a bit more what data is going into what arrays. If you have arr3 with some timestamp/string data, where should the differences go? Into the same array & position, or a new array? Where should the first timecode data go? If you show how you'll use them, you'll get more useful answers.
    – Jeff Schaller
    Feb 26, 2018 at 16:27
  • The default time utilities exposed in the shell only work with 1 second level granularity. You're going to have to write your own parser/generator from scratch.
    – symcbean
    Feb 26, 2018 at 17:30
  • 1
    @symcbean date (GNU coreutils) 8.25 allows nanosecond accuracy.
    – FelixJN
    Feb 26, 2018 at 17:51
  • @Fiximan: I stand corrected, and I see you can specify the OUPUT format with nanosecond precision, but how do you convert an input string with fractional seconds into a numeric value?
    – symcbean
    Feb 26, 2018 at 21:07
  • @symcbean See my answer below - I found no entry in info regarding a nanosecond time -> human readable conversion, thus relied on bash integer arithmetic and separated treatment of epoch time and fractional seconds. It's dangerous though, as e.g. discussed here nanoseconds seem to be a GNU-extension not present in BSD-Unices.
    – FelixJN
    Feb 26, 2018 at 22:19

2 Answers 2

1

Given an array arr3 that contains sample data:

declare -a arr3=([0]="00:00:22.180 --> 00:00:25.600"
                 [1]="00:00:24.070 --> 00:00:27.790")

You could loop through each element in the array, strip out the start time and end time, convert them to fractional seconds, compute the duration, then covert that duration back to hh:mm:ss.sss format for the ffmpeg command.

# converts HH:MM:SS.sss to fractional seconds
codes2seconds() (
  local hh=${1%%:*}
  local rest=${1#*:}
  local mm=${rest%%:*}
  local ss=${rest#*:}
  printf "%s" $(bc <<< "$hh * 60 * 60 + $mm * 60 + $ss")
)

# converts fractional seconds to HH:MM:SS.sss
seconds2codes() (
  local seconds=$1
  local hh=$(bc <<< "scale=0; $seconds / 3600")
  local remainder=$(bc <<< "$seconds % 3600")
  local mm=$(bc <<< "scale=0; $remainder / 60")
  local ss=$(bc <<< "$remainder % 60")
  printf "%02d:%02d:%06.3f" "$hh" "$mm" "$ss"
)

subtracttimes() (
  local t1sec=$(codes2seconds "$1")
  local t2sec=$(codes2seconds "$2")
  printf "%s" $(bc <<< "$t2sec - $t1sec")
)

declare -a arr3=([0]="00:00:22.180 --> 00:00:25.600"
                 [1]="00:00:24.070 --> 00:00:27.790")

for range in "${arr3[@]}"
do
  duration=$(subtracttimes "${range%% -->*}" "${range##*--> }")
  printf "%s\n" "ffmpeg -i movie.mp4 -ss ${range%% -->*} -t $duration -async 1 cut.mp4"
done

The codes2seconds function expects input in HH:MM:SS.sss format; it strips out the various elements using parameter expansion then passes them to bc for the conversion into total number of seconds.

The seconds2codes function expects a fractional number of seconds and reverses the conversion, resulting in an HH:MM:SS.sss string.

The subtracttimes function converts the two parameters to fractional seconds then asks bc for their difference.

The loop at the end goes through each element of arr3; it uses the above functions to calculate the duration (again using parameter expansion to retrieve the two times) then prints out a sample ffmpeg call to match your sample output.

Results:

ffmpeg -i movie.mp4 -ss 00:00:22.180 -t 3.420 -async 1 cut.mp4
ffmpeg -i movie.mp4 -ss 00:00:24.070 -t 3.720 -async 1 cut.mp4
1

Well, for time codes date usually pops into my mind. Not the most elegant script, but it does the job...

I assume from your question, that you have beginning and end times already read into respective arrays and will use start and end as these values here and do it step by step:

#!/bin/bash

start=00:00:22.180
end=00:00:24.070

#convert timestamps to epoch time in nano seconds
start_ns="$(date -u -d"$start" +%s%N)"
end_ns="$(date -u -d"$end" +%s%N)"

difference="$(($end_ns-$start_ns))"

#date does not read epoch time in nanoseconds, 
#so it must be converted by division vith 10^9 before display
#date_diff is accurate up to full seconds
#use UTC time (-u) to avoid problems with time zones

date_diff="$( date -u -d@$((difference/1000000000)) +%H:%M:%S )"

#take care of milliseconds as remainder of division by 10^9 and correction by 10^6
ms_diff="$(( $difference%1000000000/1000000 ))"

#combine output to create desired format

echo "$date_diff"."$ms_diff"

Result

bash script.sh
00:00:01.890

All in one line

echo "$(date -u -d@"$(( ($(date -u -d"$end" +%s%N) - $(date -u -d"$start" +%s%N))/1000000000 ))" +%H:%M:%S)"."$(( ($(date -u -d"$end" +%s%N) - $(date -u -d"$start" +%s%N))%1000000000/1000000 ))"

Please note that I am not aware if nanosecond time in date is a POSIX standard feature.

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .