Bash: Problèmes mathématiques de date / heure

Oct 02 2020

Le script suivant prend deux entrées, sourceFile="${1}"et trimFile="${2}". Le $sourceFileest un fichier vidéo qui doit être découpé en plusieurs nouveaux fichiers basés sur les données du $trimFile. Il $trimFiles'agit d'un fichier texte de la structure suivante (fichier réel lié à ce problème):

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

Le $trimFileutilise à la fois des virgules et des espaces comme délimiteurs dans le script ci-dessous. Bien qu'assez évidente, chaque ligne représente un clip à créer à partir du $sourceFile. En outre, le premier champ est l'heure de début du clip à découper, le second champ est l'heure de fin du clip à découper et le dernier champ est une description du clip.

Mon problème ne concerne pas le découpage du fichier $sourceFile. J'essaie de renommer les nouveaux clips avec une date et une heure relatives à la date d'origine des fichiers vidéo. Chacun $sourceFileet $trimFileest nommé comme l'exemple suivant (qui sont les noms de fichiers réels que j'utilise actuellement): 2017-05-15_14-17-22 (2017-05-16 00-45-41.151674100 -0400) (HEVC27).mp4et 2017-05-15_14-17-22 (2017-05-16 00-45-41.151674100 -0400) (HEVC27).txt, respectivement. Encore une fois, tout en assez évidentes, les composants de date sont Year: 2017, Month: 05, Day: 16, Hour: 14, Minute: 17et Second:22(ignorer la date est la parenthèse car il est une ancienne référence avec le mauvais UTC date ajustée).

Il devrait être évident dans le script ci-dessous comment la date / heure dans le fichier est extraite ainsi que comment la date / heure dans le $trimFilesont extraites. Pour illustrer le problème, je dois le montrer à la fois avec et sans certaines lignes commentées. Le voici avec certaines lignes commentées (cela aura du sens lorsque j'en discuterai ensuite).

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}"

Lorsque j'exécute le script sous cette forme, j'obtiens la sortie suivante correspondant aux lignes 48-52 du 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

Comme vous pouvez le voir, la nouvelle date / heure attendue est sortie correctement dans les lignes 4, 9, 14 (je ne sais pas pourquoi la dernière ligne de la $trimFileest sortie deux fois mais ce n'est pas ma préoccupation pour le moment).

Le problème réside dans le changement de nom réel du fichier lorsque je supprime les lignes commentées (lignes 13, 14, 54, 59) du script afin que le script ressemble maintenant à ceci:

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: 

Les fichiers créés par le script sont nommés comme suit:

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

De toute évidence, les dates utilisées pour nommer le fichier ne ressemblent en rien à la sortie vers stout créée lorsque je n'ai pas réencodé la vidéo, mais simplement testé que les nouvelles variables de date étaient calculées correctement.

Donc, ma question se résume à ceci, pourquoi, après avoir fait des calculs de date / heure, la date / heure est-elle correcte lorsqu'elle est renvoyée en écho mais grossièrement fausse lorsqu'elle est utilisée pour nommer les fichiers une fois encodés.

Merci!

Réponses

2 KamilMaciorowski Oct 02 2020 at 13:02

Pourquoi, après avoir fait quelques calculs de date / heure, la date / heure est-elle correcte lorsqu'elle est renvoyée à un écho mais est-elle grossièrement erronée lorsqu'elle est utilisée pour nommer les fichiers une fois encodés?

En décommentant

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

vous avez fait les lignes qui suivent extraire de mauvais fragments.

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

Par exemple, $origYears'étend maintenant à /dev.

Je m'attendrais date -dà lancer des erreurs similaires à date: invalid date '/dev-sh-/2 17:05:15'. Peut-être que vous date -dne validez pas son entrée (ou avez-vous ignoré les erreurs?).

Vous pouvez résoudre ce problème particulier en attribuant des sous-chaînes à origYear, origMonthetc. avant de modifier la valeur de sourceFile.

Brian Oct 03 2020 at 11:20

Au cas où quelqu'un ferait référence à cet article à l'avenir, vous trouverez ci-dessous le script corrigé et fonctionnel. Il y avait des erreurs supplémentaires au-delà de ce qui était abordé dans ce post qui ont également été corrigées (à savoir, la commande ffmpeg et l'emplacement de -sset -tdans la commande.

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"