Bash: problemas matemáticos de data / hora

Oct 02 2020

O script a seguir usa duas entradas sourceFile="${1}"e trimFile="${2}". O $sourceFileé um arquivo de vídeo que deve ser cortado em vários novos arquivos com base nos dados do $trimFile. O $trimFileé um arquivo de texto com a seguinte estrutura (arquivo real relacionado a este problema):

00:49:30,00:53:00 DescriptionA
03:33:30,03:38:40 DescriptionB
04:54:32,04:55:37 DescriptionC

O $trimFileusa vírgulas e espaços como delimitadores no script a seguir. Embora bastante óbvio, cada linha representa um clipe a ser criado a partir do $sourceFile. Além disso, o primeiro campo é a hora de início do clipe a ser aparado, o segundo campo é a hora de término do clipe a ser aparado e o último campo é uma descrição do clipe.

Meu problema não é com o corte do $sourceFile. Estou tentando renomear os novos clipes com uma data e hora em relação à data dos arquivos de vídeo originais. Cada $sourceFilee $trimFileé nomeado como o exemplo a seguir (que são os nomes de arquivo reais que estou usando no momento): 2017-05-15_14-17-22 (2017-05-16 00-45-41.151674100 -0400) (HEVC27).mp4e 2017-05-15_14-17-22 (2017-05-16 00-45-41.151674100 -0400) (HEVC27).txt, respectivamente. Novamente, embora bastante óbvios, os componentes de data são Year: 2017, Month: 05, Day: 16, Hour: 14, Minute: 17, e Second:22(ignore a data é o parêntese, pois é uma referência antigo com o UTC errado data ajustada).

Deve ficar evidente no script a seguir como a data / hora no arquivo é extraída e como a data / hora dentro do $trimFilesão extraídas. Para ilustrar o problema, preciso mostrá-lo com e sem algumas linhas comentadas. Aqui está, com certas linhas comentadas (fará sentido conforme discuti-lo a seguir).

01: sourceFile="${1}"
02: trimFile="${2}" 03: 04: IFS=$'\n'
05: 
06: dos2unix "$trimFile" 07: numberOfSegments=`cat "$trimFile" | wc -l`
08: numberOfSegments=$((numberOfSegments + 1)) 09: extension=`echo "$sourceFile" | awk -F'.' '{print $NF}'` 10: 11: base=`echo "$sourceFile" | sed -e "s|.$extension||g"` 12: 13: #~/bin/ffmpeg -i "${sourceFile}" -c:v copy "/dev/shm/$base.${extension}"
14: #sourceFile="/dev/shm/$base.${extension}"
15: 
16: # File date/time information
17: origYear="${sourceFile:0:4}" 18: origMonth="${sourceFile:5:2}"
19: origDay="${sourceFile:8:2}" 20: origHour="${sourceFile:11:2}"
21: origMinute="${sourceFile:14:2}" 22: origSecond="${sourceFile:17:2}"
23: 
24: origDate="${origYear}-${origMonth}-${origDay} ${origHour}:${origMinute}:${origSecond}"
25: 
26: for (( i=1;i<="$numberOfSegments";i++ )) 27: do 28: 29: lineEntry=`cat "$trimFile" | head -"$i" | tail -1` 30: 31: startHour=`echo "$lineEntry" | awk -F'[:,]' '{print $1}'` 32: startMinute=`echo "$lineEntry" | awk -F'[:,]' '{print $2}'` 33: startSecond=`echo "$lineEntry" | awk -F'[:,]' '{print $3}'` 34: 35: endHour=`echo "$lineEntry" | awk -F'[:,]' '{print $4}'` 36: endMinute=`echo "$lineEntry" | awk -F'[:,]' '{print $5}'` 37: endSecond=`echo "$lineEntry" | awk -F'[:,]' '{print $6}'` 38: 39: description=`echo "$lineEntry" | awk -F'[:, ]' '{print $7}'` 40: 41: beginSeconds=`awk "BEGIN {print ($startHour*3600+$startMinute*60+$startSecond)}"`
42:         stopSeconds=`awk "BEGIN {print ($endHour*3600+$endMinute*60+$endSecond)}"` 43: duration=`awk "BEGIN {print $stopSeconds-$beginSeconds}"` 44: 45: newDate=$(date -d "@$(( $(date -d "${origDate}" +%s) + ${beginSeconds}))" +'%Y-%m-%d_%H-%M-%S')
46: 
47:         new="${newDate}_${description}"
48:         echo "${lineEntry}" 49: echo "${origDate}"
50:         echo "${beginSeconds}" 51: echo "${new}"
52:         echo ""
53:         
54:         #~/bin/ffmpeg -n -vsync drop -fflags +genpts -i "$sourceFile" -ss "$beginSeconds" -t "$duration" -c:v libx265 -crf 27 -preset slow "$new.mkv"
55: 
56: 
57:     done
58:     
59: #rm "/dev/shm/${base}.${extension}"

Quando executo o script desta forma, obtenho a seguinte saída correspondente às linhas 48-52 do script:

01: 00:49:30,00:53:00 DescriptionA
02: 2017-05-15 14:17:22
03: 2970
04: 2017-05-15_15-06-52_DescriptionA
05: 
06: 03:33:30,03:38:40 DescriptionB
07: 2017-05-15 14:17:22
08: 12810
09: 2017-05-15_17-50-52_DescriptionB
10: 
11: 04:54:32,04:55:37 DescriptionC
12: 2017-05-15 14:17:22
13: 17672
14: 2017-05-15_19-11-54_DescriptionC
15: 
16: 04:54:32,04:55:37 DescriptionC
17: 2017-05-15 14:17:22
18: 17672
19: 2017-05-15_19-11-54_DescriptionC

Como você pode ver, a nova data / hora esperada é exibida corretamente nas linhas 4,9,14 (não tenho certeza por que a última linha do $trimFileé exibida duas vezes, mas isso não é minha preocupação no momento).

O problema reside na real renomeação do arquivo quando removo as linhas comentadas (linhas 13, 14, 54, 59) do script para que o script fique assim:

01: sourceFile="${1}"
02: trimFile="${2}" 03: 04: IFS=$'\n'
05: 
06: dos2unix "$trimFile" 07: numberOfSegments=`cat "$trimFile" | wc -l`
08: numberOfSegments=$((numberOfSegments + 1)) 09: extension=`echo "$sourceFile" | awk -F'.' '{print $NF}'` 10: 11: base=`echo "$sourceFile" | sed -e "s|.$extension||g"` 12: 13: ~/bin/ffmpeg -i "${sourceFile}" -c:v copy "/dev/shm/$base.${extension}"
14: sourceFile="/dev/shm/$base.${extension}"
15: 
16: # File date/time information
17: origYear="${sourceFile:0:4}" 18: origMonth="${sourceFile:5:2}"
19: origDay="${sourceFile:8:2}" 20: origHour="${sourceFile:11:2}"
21: origMinute="${sourceFile:14:2}" 22: origSecond="${sourceFile:17:2}"
23: 
24: origDate="${origYear}-${origMonth}-${origDay} ${origHour}:${origMinute}:${origSecond}"
25: 
26: for (( i=1;i<="$numberOfSegments";i++ )) 27: do 28: 29: lineEntry=`cat "$trimFile" | head -"$i" | tail -1` 30: 31: startHour=`echo "$lineEntry" | awk -F'[:,]' '{print $1}'` 32: startMinute=`echo "$lineEntry" | awk -F'[:,]' '{print $2}'` 33: startSecond=`echo "$lineEntry" | awk -F'[:,]' '{print $3}'` 34: 35: endHour=`echo "$lineEntry" | awk -F'[:,]' '{print $4}'` 36: endMinute=`echo "$lineEntry" | awk -F'[:,]' '{print $5}'` 37: endSecond=`echo "$lineEntry" | awk -F'[:,]' '{print $6}'` 38: 39: description=`echo "$lineEntry" | awk -F'[:, ]' '{print $7}'` 40: 41: beginSeconds=`awk "BEGIN {print ($startHour*3600+$startMinute*60+$startSecond)}"`
42:         stopSeconds=`awk "BEGIN {print ($endHour*3600+$endMinute*60+$endSecond)}"` 43: duration=`awk "BEGIN {print $stopSeconds-$beginSeconds}"` 44: 45: newDate=$(date -d "@$(( $(date -d "${origDate}" +%s) + ${beginSeconds}))" +'%Y-%m-%d_%H-%M-%S')
46: 
47:         new="${newDate}_${description}"
48:         echo "${lineEntry}" 49: echo "${origDate}"
50:         echo "${beginSeconds}" 51: echo "${new}"
52:         echo ""
53:         
54:         ~/bin/ffmpeg -n -vsync drop -fflags +genpts -i "$sourceFile" -ss "$beginSeconds" -t "$duration" -c:v libx265 -crf 27 -preset slow "$new.mkv"
55: 
56: 
57:     done
58:     
59: rm "/dev/shm/${base}.${extension}"
60: 
61: 

Os arquivos criados pelo script são nomeados da seguinte maneira:

1969-12-31_19-49-30_DescriptionA.mkv
1969-12-31_22-33-30_DescriptionB.mkv
1969-12-31_23-54-32_DescriptionC.mkv

Obviamente, as datas usadas para nomear o arquivo não se parecem em nada com a saída do stout criada quando eu não recodifiquei o vídeo, mas apenas testei se as novas variáveis ​​de data estavam sendo calculadas corretamente.

Então, minha pergunta se resume a isso, por que, depois de fazer algumas contas de data / hora, a data / hora está correta quando ecoada para forte, mas grosseiramente errada quando usada para nomear os arquivos uma vez codificados.

Obrigado!

Respostas

2 KamilMaciorowski Oct 02 2020 at 13:02

por que, depois de fazer algumas contas de data / hora, a data / hora está correta quando ecoada para forte, mas grosseiramente errada quando usada para nomear os arquivos uma vez codificados?

Ao descomentar

sourceFile="/dev/shm/$base.${extension}"

você fez as linhas seguintes extraírem fragmentos errados.

origYear="${sourceFile:0:4}" origMonth="${sourceFile:5:2}"
…

Por exemplo, agora $origYearexpande para /dev.

Eu esperaria date -dlançar erros semelhantes a date: invalid date '/dev-sh-/2 17:05:15'. Talvez seu date -dnão valide sua entrada (ou você ignorou os erros?).

Você pode corrigir esse problema específico atribuindo substrings a origYear, origMonthetc. antes de alterar o valor de sourceFile.

Brian Oct 03 2020 at 11:20

Caso alguém faça referência a esta postagem no futuro, abaixo está o script de trabalho corrigido. Houve erros adicionais além do que foi abordado nesta postagem que também foram corrigidos (ou seja, o comando ffmpeg e a localização de -sse -tdentro do comando.

01: sourceFile="${1}" 02: trimFile="${2}"
03: 
04: IFS=$'\n' 05: 06: dos2unix "$trimFile"
07: numberOfSegments=`cat "$trimFile" | wc -l` 08: numberOfSegments=$((numberOfSegments))
09: extension=`echo "$sourceFile" | awk -F'.' '{print $NF}'`
10: 
11: base=`echo "$sourceFile" | sed -e "s|.$extension||g"`
12: 
13: ~/bin/ffmpeg -i "${sourceFile}" -c:v copy "/dev/shm/$base.mkv"
14: sourceFile="/dev/shm/$base.mkv" 15: 16: # File date/time information 17: origYear="${sourceFile:9:4}"
18: origMonth="${sourceFile:14:2}" 19: origDay="${sourceFile:17:2}"
20: origHour="${sourceFile:20:2}" 21: origMinute="${sourceFile:23:2}"
22: origSecond="${sourceFile:26:2}" 23: 24: origDate="${origYear}-${origMonth}-${origDay} ${origHour}:${origMinute}:${origSecond}" 25: 26: for (( i=1;i<="$numberOfSegments";i++ ))
27:     do
28: 
29:         lineEntry=`cat "$trimFile" | head -"$i" | tail -1`
30: 
31:         startHour=`echo "$lineEntry" | awk -F'[:, ]' '{print $1}'`
32:         startMinute=`echo "$lineEntry" | awk -F'[:, ]' '{print $2}'`
33:         startSecond=`echo "$lineEntry" | awk -F'[:, ]' '{print $3}'`
34: 
35:         endHour=`echo "$lineEntry" | awk -F'[:, ]' '{print $4}'`
36:         endMinute=`echo "$lineEntry" | awk -F'[:, ]' '{print $5}'`
37:         endSecond=`echo "$lineEntry" | awk -F'[:, ]' '{print $6}'`
38: 
39:         description=`echo "$lineEntry" | awk -F'[:, ]' '{print $7}'`
40: 
41:         beginSeconds=`awk "BEGIN {print ($startHour*3600+$startMinute*60+$startSecond)}"` 42: stopSeconds=`awk "BEGIN {print ($endHour*3600+$endMinute*60+$endSecond)}"`
43:         duration=`awk "BEGIN {print $stopSeconds-$beginSeconds}"`
44: 
45:         newDate=$(date -d "@$(( $(date -d "${origDate}" +%s) + ${beginSeconds}))" +'%Y-%m-%d_%H-%M-%S') 46: newDate=`echo ${newDate}`
47:         
48:         new="${newDate}_${description}"
49:         
50:         ~/bin/ffmpeg -n -ss "$beginSeconds" -t "$duration" -i "$sourceFile" -c:v copy "$new.mkv"
51: 
52:     done
53:     
54: rm "/dev/shm/${base}.mkv"