Je skript, který umožňuje střih asciicastových záznamů pořízených aplikací asciinema, jejich vyčištění od překlepů a vložení dalších událostí.
#!/usr/bin/env bash
asciinemaKód, který zpracovává odchycené čísla chyb
trap '''(
CHYBA=$? ;
case $CHYBA in
3) echo "Dočasné přerušení skriptu. Pokud se mají zachovat dočasné soubory, použij exit kód 33"
;;
22) echo "ERROR: coreutils - tool for detection absolute path of directory"
;;
25) echo "ERROR: xmlstarlet - tool for work with xml not installed"
;;
29) echo "ERROR: 7z - tool for work with compressed archives is not installed"
;;
33) NORMDIR=yes
;;
37) echo "ERROR: terminator - not installed"
;;
38) echo "ERROR: grep - a basic GNU matching tool not installed"
;;
39) echo "ERROR: sed - GNU stream editor not installed"
;;
40) echo "ERROR: ed - classic UNIX line editor, used for action 'event' not installed"
;;
41) echo "ERROR: jq - flexible command-line JSON processor not installed"
;;
esac
) >> /dev/stderr
[ $NORMDIR ] || rm -rf -- "$TEMP_DIR"
exit $CHYBA
''' EXIT
REALPATH=$(command -v realpath)
[ ! ${REALPATH} ] && exit 1
REALPATH="$REALPATH -e "
Aby tenhle skript fungoval jak má, musí být v systému nainstalovány následující aplikace:
I když pro úpravy asciicastu není nezbytně nutná, využívá ji příkaz dump při akci event a také akce play
ASCIINEMA=$(command -v asciinema)
Textový editor ed, se využívá především při akci event
ED=$(command -v ed)
[ ! -x "${ED}" ] && exit 40
Aplikace grep, se využívá především při akci find
GREP=$(command -v grep)
[ ! -x "${GREP}" ] && exit 38
Flexibilní nástroj pro práci s formátem JSON v prostředí příkazové řádky (CLI) jq, se využívá ke zpracování hlaviček asciicastových souborů a klipů při akci clip a rovněž při akci view
JQ=$(command -v jq)
[ ! -x "${JQ}" ] && exit 41
Proudový GNU editor sed, je klíčový nástroj se kterým tenhle skript pracuje na všech úrovních
SED=$(command -v sed)
[ ! -x "${SED}" ] && exit 39
Je nástroj z balíku [gnu.org/software/coreutils coreutils], který se využívá k vytvoření dočasných souborů
MKTEMP=$(command -v mktemp)
[ ! -x "${MKTEMP}" ] && exit 22
${XTERM}Psedoterminál který se použije při akci play
XTERM=$(command -v xterm)
${TERMINATOR}Psedoterminál který se použije při akci play
TERMINATOR=$(command -v terminator)
${XDCE4-TERMINAL}Psedoterminál který se použije při akci play
XFCE4TERM=$(command -v xfce4-terminal)
${APP_NAME}
${AUTHOR}
${CLIPFILE}
${DEBUG}
${FORCE}
${HELP} ${INFO} ${JSONFILE}
${LINES[@]}
${MKTEMP}
${RESIZELINES[@]}${TEMP_DIR}
${VERSION}
${APP_NAME} obsahuje aktuální
název tohoto skriptu. Pracuje se s ní např. ve funkci help.
APP_NAME="${0##*/}"
${TEMP_DIR}, je dočasný adresář
ve kterém se zakládají dočasné, pomocné soubory ${JSONFILE}, ${CLIPFILE}, aj.
TEMP_DIR=$(${MKTEMP} -d --suffix=-asciinema)
${VERSION}, obsahuje číslo
verze, které se zobrazuje v logu
tohoto skriptu.
VERSION="0.8"
Kontakt: |
ales@kapica.cz |
|---|---|
Licence: |
GNU General Public License (GPL) |
${AUTHOR}, je proměnná, která
se používá ve funkci logo(), která
zobrazuje logo tohoto skriptu, informaci o verzi a copyright.
AUTHOR='(C) 2024 Aleš Kapica - Keny Otter'
Zobrazí se, když se použije parametr -x
logo () {
(
echo " ";
echo " █████ ███████ ██████ ██ ██ ███████ ██████ █████ ███ ███ ███████ ";
echo " ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ██ ";
echo " ███████ ███████ ██ ██ ██ █████ ██████ ███████ ██ ████ ██ █████ ";
echo " ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ";
echo " ██ ██ ███████ ██████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████ ";
echo " ";
printf ' version: %10s %20s \n' "${VERSION}" "${AUTHOR}";
echo " ";
_history
) >> /dev/stderr
}
https://discourse.asciinema.org/t/the-plan-for-the-asciinema-cli-3-0/790
S vývojem bylo započato v září 2024, kdy se ukázalo, že stále není k dispozici žádný nástroj, který by umožnil intuitivní střih a úpravu asciicastových záznamů pořízených aplikací asciinema.
Jak jsem zjistil, většinu záznamů, na webu https://asciinema.org, tvoří (až na výjimky) neupravené záznamy. Některé zcela nepochybně vznikly tak, že autor přehrál předem připravený skript. Ale pokud chceme udělat tutoriál ze záznamu, který neměl předem připravený scénář, pořízeného on-the-fly při běžné práci na konzoli, potřebujeme nástroj, který umožňuje nejenom vkládat značky a komentáře, ale také podle potřeby zkracovat či naopak prodlužovat intervalu zobrazení událostí, což vyžaduje přepočítání času zobrazení všech následujících událostí.
Na webu jsem sice našel několik nástrojů pro úpravu asciicastu:
Ovšem ani jeden z nich nesplňoval mé očekávání. Některé jsem ani nezkusil, protože vyžadovaly instalaci dalšího software a já hledal aplikaci, které by stačily jen běžně dostupné utility, jako je sed, grep, atp.
Takže jsem si napsal jednoduchý skript
asciiframe_move.sh, který řešil korekci časování událostí
(teď akce clip).
Pracoval jen se dvěma parametry – číslem řádku události LINE a posunem času SHIFT. Řádky načítal průběžně přes
sed a čas zobrazení každé události
přepočítával přes bc. Výstup šel na
stdout a ten se podle potřeby přesměroval buď do nového
souboru, nebo na konec jiného asciicastu.
Při prodloužení intervalu zobrazení, se vypsal celý asciicast, ale při zkrácení jen klip, pro který se použila hlavička výchozího souboru.
Z takto vytvořených klipů bylo možné sestavit nový asciicast – opět se použil skript
asciiframe_move.sh, s parametrem SHIFT, kde nastavil čas zobrazení poslední události záznamu, na jehož
konec se zapsal výstup skriptu.
Dalších dva skripty, asciiframe_merge.sh a
asciiframe_merge.sh, které měly umožnit slučování a dělení
událostí již zůstaly nedodělané, protože na základě skriptu
asciiframe_move.sh začal vznikat tento skript, který tyhle
operace dělá (stejně jako přidávání nových událostí) přes akci event
První velkou změnou, která vedla k výraznému zlepšení výkonu bylo
vyřazení aplikace bc, kterou používal výchozí skript i
první verze tohoto skriptu k počítání času a její nahrazení funkcemi float_to_string() a float_from_string() které
převádí čas.
U asciicastových souborů je totiž čas (v sekundách) zapsán jako číslo typu float, kde je plovoucí čárka nahrazena tečkou. Jenže bash s čísly typu float pracovat neumí. Pracuje pouze s čísly typu integer, bez desetinných míst.
Další velkou změnou, která přispěla ke zlepšení výkonu tohoto
skriptu, bylo načtení událostí do pole ${LINES[@]}.
Původně se události zpracovávaly postupně, ale ukázalo se, že je
rychlejší načíst vstupní data rovnou do pole. Nejprve se k tomu používal
sed, ale ten začal postupně ve skriptu
nahrazovat jq, protože umožňuje rychle a
efektivně pracovat s formátem JSON a vyrábět potřebné vstupy.
A protože se ukázalo, že bude nutné od základu přepracovat celý kód tehdy již použitelného a funkčního skriptu, byla tahle verze označena jako 0.1beta
Během vývoje, při implementaci dalších funkcionalit se ukázalo, že je potřeba začít uvažovat jinak, a tak došlo na revizi akcí. Současná logika vychází z logiky asciicastu. Nepracuje se s rámci (frame), jako u videa, ale s událostmi (event). Proto se operace, při nichž se nehýbe s časem událostí následujících řeší příkazy, přes akci event a posuny přes akci clip. Ke zkoumání a prohlížení obsahu asciicastu jsou k dispozici akce find a view, aby nedošlo k neúmyslnému zásahu do záznamu, k jakému by mohlo náhodně dojít při akci #akce_event.
U verze 0.1 beta již byla syntaxe víceméně ustálena a základní akce které umožňují zpracování asciicastového souboru ustáleny. Zbývalo jen dopsat nápovědy a doplnit dokumentaci
Bohužel průběžné psaní dokumentace je největší brzdou vývoje. Obzvláště zpočátku, protože se kód často mění, vznikají nové funkce a upravují ty stávající, což vyžaduje také změny v dokumentaci.
Tou největší, byla změna formátu dokumentace, která si vynutila kompletní přepsání dokumentace stávající – viz Manuál, ohledně psaní dokumentace, která stále ještě není u konce, protože je spojena s důkladným testováním funkcí.
Zdokumentovat funkci asciiplayer() a také akci play
Byla přepracovaná funkce get_string(), pravděpodobně tím byl rozbit příkaz text, který vyrábí monochromatickou verzi záznamu
Udělat záznam, ve kterém bude aktuální verze skriptu a zároveň demo pro jednotlivé akce Začátek s příklady bude neměnný, tak aby se ten stažený asciicast dal použít rovnou k demonstraci kódu.
Vyhledání události Vložení značky export skriptu z ascii.castu vložení do asciicastu.
Vytvoření klipu se skriptem, který lze naimportovat do jiného asciicastu: 1. export skriptu script.sh do asciicastu script.cast
asciinema rec -c "cat script.sh | grep -v '^# ' | grep -v '^#$'" script.cast
$ asciiframe view script.cast -1 1
...
102: 101: [0.073059, "o", "... \r\n"]
$ asciiframe set script.cast newline 0.08 AAA-script.sh-AAA
$ asciiframe set script.cast newline 0.000001 VVV-script.sh-VVV
$ asciiframe view script.cast 1 1
...
2: 1: [0.000001, "m", ...
$ asciiframe set script.cast line 2 o
$ asciiframe set script.cast newline 2
Asciiframe? Asciicast editor from asciicast
Export to asciicast
$ asciinema rec -c "cat asciiframe.sh | grep -v '^# ' | grep -v '^#$'" ascii.cast
Export script from asciicast
$ asciinema cat ascii.cast | tr -d '\r' > asciiframe
$ chmod +x asciiframe
$ ./asciiframe -h
_manual_export_script () {
echo """
[4mHow to find event by string?[0m
$0 find ascii.cast Text
, can be installed via raw terminal output, if used next filter:
$ asciinema cat ascii.cast |\\
sed -n '/^VVV/,/^AAA/p' |\\
sed '1d;$d' |\\
tr -d '\r' > asciiframe
[4mHow to find event by string?[0m
If line of string is 10, use -10 to delete of it
$0 event ascii.cast delete -10
""" >> /dev/stderr
}
„Poster” je událost, která překreslí celé terminálové okno svým obsahem, na kterou lze odkázat časem z jejího intervalu zobrazení, než začne obsah okna připisovat následující událost.
Nemusí to být pouze jedna událost. Může to být celá série událostí. Podstatné je, že mají stejný čas, takže se během přehrávání zobrazí všechny najednou. A tohle je postup jak takovou sérii událostí vyrobit.
Možností je několik.
Jednou z nich je export přes akci clip. Nedostane-li tahle akce příkaz clip, exportuje kvůli zachování kontextu také události, které předchází události, kde se začíná něco dít.
Protože mají všechny nastaven čas 0.0, „vysype” je přehrávač všechny najednou, takže bude vypadat výsledek stejně, jako bych přeskočili na ten čas rovnou přes značku (marker).
Pokud odstraníme události, které jsou navíc - ty co už nejsou v okamžiku kdy se souští přehrávání v záběru terminálového okna - získáme klip, jemuž předchází „poster”, Který se bude po naimportování klipu zobrazovat v čase, na který klip vložíme.
Další možnost nabízí příkaz time, který, lze nastavit stejný čas i celému
rozsahu řádků. Ideální je, před tento blok událostí přidat také značku -
událost typu m (marker), na kterou lze v
přehrávači asciicastu rovnou
přeskočit.
A B C D E F G
$ asciiframe event ascii.cast time TIME B-F
Po aplikaci tohoto příkazu, by to v rámci časové osy událostí vypadalo - při nastavení času TIME stejného, jaký měla událost na řádku F - asi takto:
A BCDEF G
Takto vytvořený blok událostí BCDEF lze spojit do jedné příkazem merge, ale také se dá opět rozdělit, příkazem pause. A to tak, že se těm událostem postupně opět nastaví intervaly zobrazení. A to rozdělením jejich současného intervalu.
$ asciiframe event ascii.cast pause LINE-B 1
AB CDEF G
Pokud bych události z řádku B nastavili výše uvedenou hodnotu 1 (maximální využití intervalu), přistrčili bychom ji bezprostředně za událost na řádku A, což ale nechceme. Takže abychom ji vrátili víceméně na původní čas, musíme nastavit jen poměrnou část intervalu
$ asciiframe event ascii.cast pause LINE-B 0.8
A B CDEF G
Poměr postupně snižujeme, protože se celková délka intervalu, tím jak nastavujeme intervaly zobrazení, postupně zkracuje
$ asciiframe event ascii.cast pause LINE-C 0.77
A B C DEF G
$ asciiframe event ascii.cast pause LINE-D 0.73
A B C D EF G
.. až do okamžiku, kdy nastavíme čas události na řádku E tak, aby její interval zobrazení byl stejně dlouhý jako za událostí z řádku D.:
$ asciiframe event ascii.cast pause LINE-E 0.5
A B C D E F G
S nějakou přesností si nemusíme lámat hlavu. Během přehrávání stejně pouhým rozdíl v délce intervalu poznáme, jen pocitově.
_manual_set_poster () {
echo TODO-manual-set-poster-in-english
return
}
První řádka nápovědy je prázdná. Po ní následuje obecná ukázka příkazu, a pod ní popis.
Toto je manuál k tomu, jak se má dokumentovat kód tohoto skriptu. Jakým způsobem se s ním pracuje, je zdokumentováno v rámci funkce help.
# Prostý komentář se stane součástí dokumentace, pokud je na řádku
# co začíná znakem #, následovaným jednou mezerou.
#Komentář, který bude následovat bezprostředně za tímto znakem,
#bude v dokumentaci interpretován jako součást dokumentovaného kódu
echo """Stejně jako každý jiný řádek"""
# Včetně zakomentovaných řádků, kterým předchází mezera či tabulátor
# <!--
# Ale pokud bude zakomentovaný blok, ošetřen jako HTML komentář,
# bude při zobrazení vygenerovaného HTML skryt, bez ohledu na to
# jde-li o komentář, nebo
echo "funkční kód"
# -->
Takovým způsobem jsou ošetřeny například pomocné funkce, které slouží výhradně k rychlé orientaci v kódu.
Pro úrovně textových oddílů je následující:
== vymezuje hlavní bloky textu,
=== je úroveň pro dokmentaci funkcí a proměnných,
==== je úrovní pro akce a pododdíly,
===== je úrovní pro vše ostatní
Pro vizuální odlišení odkazů dodržujte pravidlo že:
Odkazy na proměnné, se odlišují strojopisem a
obsahují znak $:
[[#$VAR_FOO|$VAR_FOO]]
Odkazy na funkce se zobrazují jako běžný text, ale
za zdvojenou hranatou závorku se umísťují dvě kulaté závorky:
[[#lineblock|lineblock]]() – lineblock()
Odkazy na akce se zobrazují kurzívou a obsahují v
názvu cíle prefix akce_, viz např. odkaz na akci:
''[[#akce_set|set]]'' – set
Odkazy na příkazy obsahují v názvu cíle prefix akce,
pod kterou patří, např. : [[#set_line|line]] – line
Odkazy na volby, přes které se nastavují globální
proměnné, se zvýrazňují tučně, např. : '''[[#HELP|-h]]''' –
-h
A kód, který se má zobrazit v dokumentaci jako ukázkový musí být ošetřen tagy <nowiki> … , až uvnitř párového tagu, <code> … </code>
Dokumentace, se umísťuje vždy před dokumentovaný kód a v jednom zakomentovaném bloku je popis i příklady použití.
Většina odkazů rámci této dokumentace je lokálních, takže pokud se
odkazuje na funkci help, neodkazuje
se na wiki stránku s názvem help, ale na lokální
kotvu v rámci téhle vygenerované HTML stránky. Proto je nutné
psát za zdvojenou hranatou závorkou [[ znak
#, bezprostředně za ním název kotvy a
teprve za svislítkem text odkazu.
Tedy ne [[help]] ale
[[#help|help]]
Odkazovat lze, tak jako u běžné MediaWiki také na nadpisy, ale je
lepší se tomu vyhnout, protože to má svá specifika. Např. to, že
pandoc při generování id, ale všechna
velká písmena převede na malá.
Mnohem závažnější komplikací je ale to, že se při překladu
dokumentace do jiné jazykové verze automaticky generované
id změní, proto je lepší psát kotvy rovnou jako html
kód:
<span id="kotva">x</span>,
kde je místo 'x' text na na který se lze přes
'id' 'kotva' odkazovat.
Obsah řádku by neměl přesáhnout 72 znaků. Delší řádky komplikují čtení a prohledávání kódu. A delší text, může bez problému pokračovat na další řádce, protože se při generování html tak jako tak řádky, které jdou za sebou spojí do jednoho odstavce.
V případě funkčního kódu lze rozložit příkazy na více řádků tak, že
na konec řádku přidá zpětné lomítko \, viz
následující sekvence, kterou se generuje z tohoto skriptu přes aplikaci
pandoc statická HTML
stránka.
cat asciiframe.sh | \
sed 's/^/\ /' | \
sed 's/\ #$//' | \
sed 's/\ #\ //' | \
pandoc -f mediawiki -w html -o asciiframe.html
Výsledkem je statická HTML stránka, u které není možné tak jak je zvykem u MediaWiki využít šablony. proto je nutné při psaní dokumentace dodržet všechna výše uvedená pravidla
Tento skript byl vytvořen pro zpracování asciicastových záznamů uploadovaných na server Thewoodcraft.org.
Ten jede na systému MediaWiki, proto se i pro dokumentaci, která je součástí tohoto kódu používá mediawiki syntaxe.
Tohle je funkce pro nápovědu. A její součástí není jen kód, co se vypisuje na terminál po předání parametru $1, ale i podrobný popis, ke každé akci, včetně ukázkového kódu, ze kterého se generuje stránka s dokumentací.
Funkce samotná, pokud nemá předaný žádný parametr $1 vypisuje globální nápovědu, kde je uveden seznam implementovaných akcí, včetně informace, jak si vypsat nápovědu pro konkrétní akci.
help () {
case ${1} in
event)
V rámci akce event se spouštějí příkazy, které lze vykonat, aniž by bylo nutné řešit posun časování následujících událostí. Příkazy, které do asciicastu nic nového nepřidávají a řeší jenom události, kterých se příkaz bezprostředně týká:
Událost, ve které je hledaný řetězec zapsaný jako součást textu, lze vyhledat přes akci find, ale zvýraznit ho bude možné až poté se se její obsah příkazem split rozdělí. Nic ale nebrání tomu, obsah původní události, po vložení sekvencí co se postarají o zvýraznění příslušného místa, opět scelit příkazem merge.
Užitečným příkazem akce event je
Časy se kterými skript pracuje jsou v sekundách (a mikrosekundách),
což může představovat pro ty, co jsou zvyklí dělit čas na minuty a celé
sekundy problém. Hodnota 1, předaná jako parametru totiž
není jedna sekunda, ale pořadové číslo události, nebo řádek. Jako čas je
akceptováno číslo 1.0, jenže pokud počet sekund překročí
hodnotu 60.0, může někomu dělat problém uvědomit si, jak
dlouho takový interval trvá. Proto je k dispozici příkaz time, který umožňuje hodnoty času
(zaokrouhlené na celé sekundy) oběma směry převádět. Takže hodnota
9999.999999 vrátí 02:46:39, ale obráceně bude
výsledek o necelou sekundu kratší 9999.000000
echo """
asciiframe [-h] event ascii.cast COMMAND [EVENT [PARAM]]
The 'event' action only affects EVENTS identified by LINE (the event line
in the ascii.cast file) TIME or LINE RANGE. Does not affect the others
events timing. For command help use the '-h' option.
[4mCOMMANDS[0m:
'delete' - remove event
'decolor' - print asciicast as monochrome asciicast
'dump' - decolorize RAW output
'merge' - concate the range or set of events into one event
'split' - split event into range or set of events
'time' - conversion string (integer|float) <=> hh:mm:ss and set
time for event or rang of events.
""" >> /dev/stderr
;;
event_delete)
echo """
asciiframe event ascii.cast [1mdelete[0m LINE|TIME|RANG [LIMIT]
Command remove from ascii.cast file events. Next example demonstrate how to
remove last event:
$ asciiframe event ascii.cast delete -0
Nex example demonstrate how to remove scope from line 20 to 22:
$ asciiframe event ascii.cast delete 20-22
LIMIT is count or interval of time in seconds. Next example demonstrate
how remove scope from last event before time 0:14 (14.0 sec.) to first
event after time 16.0:
$ asciiframe event ascii.cast delete 0:14 2.0
""" >> /dev/stderr
;;
event_decolor|event_dump)
echo """
Command [1mdecolor[0m merge singlechars events into strings,
remove every sequency what do change font properties and print output
to stdout as the asciicast.
$ asciiframe event ascii.cast decolor > monochrome.cast
Command [1mdump[0m first apply to the 'ascii.cast' asciinema command 'cat',
and before print output to stdout, apply own function decolor to remove all
sequencies changing the font properties. And send as monochrome output to
stdout, which can be redirected into file
$ asciiframe event ascii.cast dump > monochrome.raw
File monochrome.raw can be transformed by sed into normal ASCII text – for
example see egg for action [3msplit[0m. Next example demonstrate
decolorization raw output from another apps by [1mdump[0m command
$ asciinema cat color.cast > color.raw
$ asciiframe event ascii.cast color.raw dump > decolor.raw
File ascii.cast ignore, decolor apply only to output from color.raw file
""" >> /dev/stderr
;;
event_merge) help_line
echo """[4mExample[0m
$0 event ascii.cast merge 12.34:5
If line 5 from range, and 12.34 is time from context of this range, be
concated into one event, with time 12.34
WARNING:
If line of event is a long, may be it the problem. For this use
next variant:
$0 event ascii.cast merge 12-15
Concate events from line 12 to 15 into one event and use time of line 12
$0 event ascii.cast merge 12.34:12-15
Concate events from line 12 to 15 into one event and use time 12.34 for
it, if is from range of concated events
$0 event ascii.cast merge 12-15:12.34
This variant is for situation when line 12 is from range. Merge do
concation with content lines 14 and 15, but use time 12.34 if is from
range of concated events, but biggest then time of line 12.
""" >> /dev/stderr
help_line
;;
event_pause)
echo """[4mExample[0m
""" >> /dev/stderr
;;
event_split) help_line
echo """[4mExample[0m
$0 event ascii.cast split 12
Split event from line 12 into range of events with the same time
$0 event ascii.cast split 12 0.3
Split event from line 12 into set of events, and use interval 0.3 to
set new time
$0 event ascii.cast split 12-15
Split events from line 12 to 15 into set events and use time from line
12 for every them. After merge of this range it be as 'poster'
""" >> /dev/stderr
help_line
;;
event_time)
~$ asciiframe event ascii.cast time TIME [INTERVAL]
Příkaz time má dvojí funkci. Pokud není předán žádný INTERVAL, převádí čas TIME. Je-li v sekundách, zobrazí ho v „humánním” formátu [HH:]MM:SS, a obráceceně. Víz následující ukázkový příklad:
$ asciiframe event ascii.cast time 85.000000
1:25
$ asciiframe event ascii.cast time 1:25
85.000000
INTERVAL je buď LINE nebo RANG (rozsah řádků) a pokud je uveden, nastaví se TIME - pokud spadá do jejich časového intervalu - všem událostem na těchto řádcích. Události, které se spouští ve stejný čas, jsou tzv. rozsahy.
Ty během nahrávání záznamu nevznikají, protože asciinema zapisuje to co se zobrazí v jeden čas do jedné události. Taková událost je ovšem z hlediska dodatečného střihu dosti nepřehledná, proto tenhle skript umožňuje přes akci split takové události rozdělit a tak se v asciicastu objeví rozsahy událostí, co mají stejný čas. Z hlediska přehrávání se nic nemění, protže se zobrazí najednou, stejně jako kdyby byly součástí jedné události.
Jako jeden rozsah vypisuje při exportu události co předcházejí první události klipu akce clip. Je to proto, aby zůstal zachován kontext klipu, který lze dodatečně odstranit příkazem delete.
Tento příkaz není pod akcí set ptotože pracuje s již existujícími událostmi. A na rozdíl od clip neřeší posun následujících událostí, protože dovoluje nastavit jen čas, který je v rozsahu událostí kterých se ta změna týká. Nelze nastavit čas zobrazení dřívější, ani pozdější, než je čas událostí, které rozsahu předchází a které po něm následují.
Rozsah lze bez problému kdykoliv „rozbít” příkazem pause, který pracuje s časovým INTERVALEM rozsahu.
echo """
asciiframe event ascii.cast [1mtime[0m TIME [INTERVAL]
Command set same TIME to every event by the INTERVAL scope of events.
INTERVAL is LINE or RANG of lines. If not passed, command convert TIME
from seconds to „human” format, if passed in seconds..
$ asciiframe event ascii.cast time 85.000000
1:25
..or to seconds, if passed in „human” format
$ asciiframe event ascii.cast time 1:25
85.000000
""" >> /dev/stderr
;;
action) echo """
Action unknown. Try
$0 -h
""" >> /dev/stderr && return 1
;;
clip)
$ asciiframe.sh clip ascii.cast PARAMS [> clip.cast]
Při akci clip se
realizují operace, při kterých se pracuje s jedním až dvěma asciicastovými soubory. Je-li
předán pouze soubor ascii.cast, bude tahle akce na základě
PARAMS exportovat bloky událostí do klipu.
$ asciiframe.sh code ascii.cast clip.cast PARAMS
Je-li předán druhý asciicastový soubor, nebo klip
clip.cast, záleží co se bude dít na předaných parametrech,
Je-li předán příkaz diff, tak
se z nich vytáhnou pouze jejich události (bez časování) a to umožní
porovnat, do jaké míry se obsahově shodují, bez ohledu na aktuální časy
spouštění těchto událostí.
V případě, že se předá jako parametr čas, nebo číslo řádky události v
souboru ascii.cast, přistoupí asciiframe k souboru clip.cast jako ke
klipu, který začlení na onu pozici do souboru ascii.cast, a
odpovídajícím způsobem upraví časování všech událostí. Klipem může být
jak nový záznam aplikace asciinema, tak záznam vyexportovaný původně ze
stejného souboru přes clip, doplněný o značky a komentáře.
Parametrizovaný export v kombinaci s parametrizovaným importem umožňuje přeskupit události v jiném pořadí – tedy střih asciicastových záznamů.
Přes clip lze také zkracovat, či naopak prodlužovat intervaly zobrazení událostí, což rovněž vyžaduje změny v časování událostí:
$ asciiframe.sh clip ascii.cast -- PARAMS [> clip.cast]
a klipem ().
přepočítává časování všech událostí Umožňuje:
Pro zrychlení, či zpomalení asciicastu (tedy i vyexportovaného klipu), lze využít rovnou aplikaci asciinema, které lze nastavit rychlost záznamu i přehrávání.
$ asciinema rec -c "asciinema play -s 5 -i 2 ascii.cast" zrychleny.cast
Klipem, může být jiný záznam aplikace asciinema, ale také asciicast nově sestavený, nebo vyexportovaný z libovolného záznamu přes asciiframe.
Export událostí do klipu ovlivňují následující parametry:
$ asciiframe.sh clip ascii.cast [LINE|TIME [INTERVAL]]
První parametr LINE je číslo řádku události v záznamu
ascii.cast, která bude první událostí budoucího
klipu. Při exportu je vždy lepší použít číslo řádku události
než čas, protože lze tímto způsobem rozdělit i série událostí, které
mají v záznamu stejný čas zobrazení. Takovou sérii si můžete sami
vyrobit viz postup Jak vyrobit
poster.
U delších záznamů, nebo záznamů, kde nějaká mikrosekunda nehraje
roli, se dá použít i TIME,
přibližný čas ve formátu HH:MM:SS, např. 1:25, který se
převede na celé sekundy. Ale pokud chcete vyrobit klip, do kterého již
nebude nutné zasahovat, je lepší použít čas v sekundách a
mikrosekundách, protože čas 1:25 se interně převede na čas
85.000000 a ten nemusí ideálně vyjít.
Teoreticky nezáleží na tom, která událost bude u klipu ta první, ale prakticky je lepší, pokud klip začíná novým řádkem, nebo překreslením terminálové obrazovky. Takové události se dají i v rámci dlouhého záznamu velice rychle přes akci find – nejlépe přes uživatelské jméno, nebo název adresáře zobrazovaný v #promptu, protože to jsou řetězce, co se zobrazují tak rychle, že je #asciinema do záznamu uloží do jedné události. Ale záznam je možné rozdělit i v místě, kde bylo během nahrávání zaznamenáno zalomení řádku (vyhledáte předáním řetězce 'newline'), nebo překreslení terminálového okna (vyhledáte předáním řetězce 'clear')
Druhý parametr INTERVAL určuje jak velký blok událostí se bude exportovat. I ten může být předán jako čas HH:MM:SS, které se převede na sekundy a připočítá k času výchozí události. Stejně, jako když se předá rovnou v sekundách. Výsledkem bude vždy čas, který se použije k vyhledání události, kterou bude klip končit a pracovat se bude s jejím číslem řádku. Ale jak už bylo zmíněno. Problém může nastat u záznamu, který obsahuje série událostí co mají stejný čas, proto je i v tomto případě lepší rovnou uvést číslo řádku události, kterou má klip končit.
Klip vytvoříte tím, že výstup zapíšete do souboru. Viz
ukázkový příklad, kdy se ze souboru ascii.cast vyexportují
všechny události, od události na řádku 21, co se zobrazí v následujícím
časovém intervalu o délce 1:30
$ asciiframe.sh clip ascii.cast 21 1:30 > clip.json
Pokud by nebyl uveden žádný INTERVAL, vyexportovaly by se do
klipu clip.json všechny následující události, až
do konce souboru. Výchozí čas spuštění první události exportované do
klipu začíná vždy na čase 1.0
$ asciiframe.sh clip ascii.cast clip.json [TIME|LINE [PAUSE]]
Na importu klipu clip.json dojde jen pokud bude předán přesný čas v sekundách, nebo číslo řádku události, která se po vložení posune za poslední událost importovaného klipu'. V takovém případě se automaticky nastaví interval zobrazení za poslední událostíklipu'' stejný jako měla posunutá událost.
Čas první události klipu, který je rovněž souborem typu asciicast se nastaví na uvedený čas TIME, nebo čas, který měla původní událost na řádku LINE, a podle toho se přepočítají i časy všech následujících událostí. Tedy i těch, co budou následovat za vloženým klipem.
Za poslední událostí asciicastu (tedy i klipu) ale není nic, z čeho by se dal vypočítat časový interval jejího zobrazení. Proto je možné, přes parametr PAUSE, předat časový interval, kterým lze upravit hodnotu intervalu zobrazení, která by jinak byla stejná jakou měla událost, před kterou byl klip vložen, protože ta se použije, pokud parametr PAUSE chybí. A protože původní interval zobrazení může být i delší než je žádoucí, je možné předáním záporného času ten interval i zkrátit. Ovšem jen pokud bude PAUZE v sekundách!
Při posunech, exportu, a importu dochází vždy na změnu času zobrazení události.
Čas událostí exportovaných do klipu tak už nikdy nebude
stejný, jako byl ve výchozím souboru ascii.cast, protože
ten se změní ihned poté, co předně ně vložíte nějaký další
klip, nebo nebo když zkrátíte, či naopak prodloužíte interval
zobrazení některé události, která jim bude předcházet. Proto většinou
nejde zjišťovat rozdíly mezi dvěma asciicasty jednoduše tím, že je rovnou předhodíte
nějaké aplikaci. To se dá jen dokud nedojde na změnu časování.
Přesto můžete i poté zjistit, ze kterého záznamu klip pochází, pokud použijete asciiframe jako tzv. wrapper a místo TIME, nebo LINE předáte příkaz diff a jméno aplikace, kterou chete ke zobrazení rozdílů použít. Bez toho se použije obyčejný diff.
$ asciiframe.sh clip ascii.cast clip.json diff [meld|...]
Čísla řádků událostí budou stejná, jenom místo hlavičky se zobrazí výchozí hodnoty pro nastavení velikosti terminálu. Pokud bude u obou záznamů stejná bude hned na prvním řádku shoda. a shody u následujících událostí neomylně ukážou, jestli záznamy mají stejný obsah, nebo ne. Viz následující ukázka, u které je vidět, jak vypadá rozdíl událostí, po aplikaci příkazu split
"80", "24" "80", "24"
"o", "\u001b" | "o", "\u001b[?2004h
...
"o", "T" <
"o", "\r\n" "o", "\r\n"
"o", "\u001b[?2004l\r\u001b[?2004h> " | "o", "\u001b[?2004l\r\u001b[?2004h> Tohle je text komentáře k
"o", "T" <
"o", "o" <
"o", "h" <
"o", "l" <
"o", "e" <
"o", " " <
"o", "j" <
"o", "e" <
"o", " " <
...
Akce clip umožňuje zkrátit, nebo naopak prodloužit interval zobrazení. Každá událost má svůj INTERVAL – čas, který uplyne mezi zobrazením události předchozí, jejím zobrazením a zobrazením události následující.
Její vlastní zobrazení dělí tenhle INTERVAL na dvě části:
Jaké má tyhle intervaly událost si můžeme zobrazit příkazem context, při akci view. A nebuďte zaskočeni, pokud se vrátí samé nuly:
$ asciiframe view ascii.cast context 10
0.000000 (0.000000 - 0.000000)
Událost, která má nulový kontext je totiž součástí série událostí, co mají stejný čas zobrazení, což se ukáže, když při akci view, použijete krátký interval:
$ asciiframe view comment_00.cast 10 0.1
10: 9: [0.051663, "o", "\u001b"]
...
61: 60: [0.051663, "o", " "]
$ asciiframe view ascii.cast context 61
2.190601 (0.000000 - 2.190601)
Z kontextu poslední události oné série na 61 řádce se dozvíte, že na zobrazení další události, dojde zhruba za 2 sekundy.
Použijete-li záporný interval, dozvíte se, kterou událostí ona série začíná. Ta je, jak je vidět na níže uvedeném ukázkovém výpisu na řádku 2. Což znamená, že se celá série těchto událostí zobrazí ihned po spuštění přehrávání záznamu.
$ asciiframe view comment_00.cast 10 -0.1
2: 1: [0.051663, "o", "\u001b"]
...
10: 9: [0.051663, "o", "\u001b"]
Pokud bychom chtěli, aby se tento poster zobrazoval déle, můžeme následující události velice jednoduše posunout následujícím příkazem o dalších 5 sekund:
$ asciiframe.sh clip ascii.cast -- 62 5.0
Syntaxe je stejná jako při importu klipu. Rozdíl je pouze v tom, že se místo souboru s klipem předají jen dvě pomlčky.
Při uvedení záporné hodnoty intervalu, se čas zobrazení naopak zkrátí, což se může hodit obzvláště poté, co příkazem decolor vyrobíte odbarvenou, monochromatickou verzi záznamu, která bude mít všechny jednoznakové události sloučené. U takového záznamu je zbytečné, mít dlouhé čekací intervaly, protože se dá přehrávat po jednotlivých událostech.
echo """
asciiframe clip ascii.cast -- TIME|LINE [PAUSE]
If not clip file, do only [1mshift events timing in ascii.cast[0m
If get clip.cast file, do [1mimport events into ascii.cast[0m to
TIME or before event from LINE. PAUSE used after last imported event.
asciiframe clip ascii.cast clip.cast TIME|LINE [PAUSE]
If get LINE, TIME or RANG, do [1mexport events from ascii.cast[0m
If TIME or LINE event, print all from this events to end of file. If get
RANG, print events only by select.
asciiframe clip ascii.cast INTERVAL [COMMAND]
[4mCOMMANDS[0m:
clip - By default print to output all events, only set time events
before selected TIME or LINE time 0.0. Command 'clip' off print
this events
play - Asciinema by default don't play record by time select. This
command is workaround for it
""" >> /dev/stderr
;;
clip_diff) echo """
[4mHow to show differences between ascii.cast and clip.cast[0m
$0 clip ascii.cast clip.cast diff
or show by another diff app (meld for example)
$0 clip ascii.cast clip.cast diff meld
"""
;;
clip_float) echo """
Akce 'clip' pracuje [1mpouze[0m s čísly typu [3mfloat[3m
Chceme-li tedy zkrátit, nebo prodloužit interval zobrazení události o
níž víme že je na řádku č. 56, musíme nejprve zjistit, její čas
./asciiframe.sh clip ./prelozit1.json -- 0.3
"""
;;
clip_import)
echo """[4mImport klipu[0m
Při importu se u všech událostí co následují za časem na který se vloží
obsah klipu posune časování. Jako lze použít libovolný záznam
typu asciicast, nebo jeho kus vyexportovaný jako klip.
$0 clip POSITION [SHIFT]
POSITION je čas, kladné číslo s tečkou, typu float, na který se má
klip vložit. Časování následujících událostí se posune podle jeho
délky trvání.
SHIFT je kladné, ale může být i záporné číslo typu float, které udává
o kolik se má posunout čas za poslední událostí klipu.
Je-li záporné, záleží na velikosti prodlevy za poslední zobrazenou
událostí klipu.
Při vkládání klipu je doporučeno používat raději kladný SHIT a délku
zobrazení poslední události následně upravit posunem časování, než
aplikovat rovnou záporný SHIFT
""" && help clip_move
;;
clip_move) echo """
asciiframe clip ascii.cast -- EVENT SHIFT
The 'clip' action without the clip file, only with two chars '--', apply
SHIFT to all events since the EVENT identified by LINE (the event line
in the ascii.cast file) or TIME and thus changes their timing.
All parameters are mandatory. Application changing the duration and set
new timestamp. It's reason why writes the output to stdout and not into
the ascii.cast file!
Negative SHIFT do shortening the waiting interval for the EVENT. And
positive value extend display time after is showed, so the wiewer has
enough time to read.
""" >> /dev/stderr
;;
find)
Akce zavolá funkci findline(), předá jí parametry a ta se pokusí pomocí nástroje grep v asciicastu podle nich něco najít. Jenže záznam aplikace asciinema není obyčejný textový soubor. Jsou to sekvence znaků zachycené v čase, tak jak se vypisovaly na terminál, takže mohou vypadat různě. Problém je např. s vyhledáním příkazu, který se psal v okně terminálu přes klávesnici. Přes find se ho nepodaří najít, protože je každé písmeno zaznamenáno jako samostatná událost. Ale existuje způsob, jak to vyřešit – dekomponovat záznam přes split a sloučit textové řetězce přesmerge. Časování událostí se tím nezmění, takže se dá výsledný asciicast (u kterého zmizí efekt přímého psaní na příkazové řádce terminálu) využít k tomu, abychom zjistili, jaký čas nastavit pro vložení značky do výchozího záznamu.
$ asciiframe.sh find ascii.cast STRING|KEY [PARAM]
Pokud se najde STRING, vypíše se událost příslušného řádku stejně jako při akci view a nalezený řetězec bude od odstatního textu barevně odlišen (červeně).
Pro vyhledání řetězců, co obsahují řídící znaky terminálu, nebo
mezery je potřeba využít zápisu přes hexa sekvence. Takže místo
'The Book' se použije..
$ asciiframe.sh find ascii.cast 'The\x20Book'
Podobným způsobem se dají vyhledat i speciální sekvence znaků, jimiž se řídí terminál, ale pro zjednodušení se využívají zástupná klíčová slova, která nabízí funkce get_string():
Sekvence pro práci s kurzorem se zaznamenávají během událostí, kdy kurzor (místo kde se vpisuje text) mění v rámci terminálového okna svou pozici.
A toto jsou reálné příklady jejich využití.
Pokud chcete vyhledat veškeré události, při kterých terminál dostal příkaz, aby pokračoval ve výpisu na dalším řádku, můžete použít
$ asciiframe.sh find ascii.cast breakline
Tahle událost však obvykle nebývá osamocená. Pokud jí předcházel stisk klávesy Enter, objevila se v záznamu události také speciální sekvence, kterou lze najít přes zástupné klíčové slovo enter. Události, které ji obsahují, jsou ideálním bodem, od kterého lze následující události exportovat do samostatného klipu, nebo před který lze vložit příkazem marker bod, na který se dá – pokud to podporuje přehrávač asciicastu – během přehrávání přeskočit.
echo """
asciiframe find ascii.cast STRING
If found STRING in the asciicast record, colorize it and show event with
a number line and order similary as 'view' action. To search combination
strings with spaces, replace space by string '\x20'
asciiframe find KEY [PARAM]
PARAM is combination of numbers and semicolons. Unknown value can be
replaced by a dot. Option '-h' show help for KEY with example of using.
[4mKEYS[0m:
backchar [cursor|H] cursor-show
[backline|K] [cursor-up|A] cursor-save
blink [cursor-down|B] cursor-restore
breakline [cursor-fore|C] enter
[clear|J] [cursor-back|D] newline
color cursor-hide return
""" >> /dev/stderr
;;
Vkládá sekvenci \b, která posouvá
aktuální pozici kurzoru o jeden znak nazpět.
Při vyhledání sekvence 'backchar' můžeme narazit na událost, při níž se kurzor posunul o několik těchto sekvencí najednou - je to neklamná známka toho, že byla stisknuta klávesa 'Home'
Typicky se objeví taková událost při úpravě příkazu vytáhnutého z historie, nebo při nakopírování obsahu schránky, protože se změní zvýraznění nakopírovaného textu. Viz příklad.
$ asciiframe.sh find ascii.cast backchar
7:[0.437473, "o", "\u0007\b\b\b\b\b\b\b\b\bcd pokus/"]
$ asciiframe.sh view ascii.cast 7 -1
6: 5: [0.356846, "o", "\u001b[7mcd pokus/\u001b[27m"]
7: 6: [0.437473, "o", "\u0007\b\b\b\b\b\b\b\b\bcd pokus/"]
Z vypsaného kontextu je zřejmé, že text nezměnil, jenom zmizely sekvence barev:
6: 5: [0.356846, "o", "\u001b[7mcd pokus/\u001b[27m"]
Pokud tedy změníme text události z řádku 6 následujícím způsobem, můžeme událost z řádku 7 s klidnou duší odstranit.
Nejprve změníme typ události z řádku 7 na m
(marker)
$ asciiframe.sh set ascii.cast line 6 m
...
$ asciiframe view prelozit1.json m
6: 1: [0.356846, "m", "\u001b[7mcd trans/\u001b[27m"]
...
$ asciiframe set prelozit1.json marker 1 'cd trans/'
...
$ asciiframe view prelozit1.json 6 1
6: 1: [0.356846, "m", "cd trans/"]
7: 5: [0.437473, "o", "\u0007\b\b\b\b\b\b\b\b\bcd trans/"]
$ asciiframe.sh set ascii.cast line 6 o
$ asciiframe view prelozit1.json 6 1
6: 1: [0.356846, "o", "cd trans/"]
7: 5: [0.437473, "o", "\u0007\b\b\b\b\b\b\b\b\bcd trans/"]
$ asciiframe.sh event ascii.cast delete 7
Pokud ale chceme, aby v tutoriálu figuroval jiný adresář než
~/pokus, záleží kolik bude mít znaků a podle toho
je potřeba upravit obě události. Tak aby počet
„umáznutí” odpovídal počtem znaků délce nového řetězce.
find_backchar)
echo """
backchar
""" >> /dev/stderr
;;
Vkládá sekvenci, která smaže obsah řádku, od místa kde se aktuálně nalézá kurzor, aniž by se při tom změnila pozice kurzoru
Není-li žádný parametr, je to stejné jako by byla parametrem
0 - umaže se obsah řádku od aktuální pozice kurzoru,
směrem ke konci řádku. Pokud tedy přidáte přes tuhle událost událost,
kde bude sekvence \b (#set_backchar), která posune
kurzor směrem zpět ku začátku řádky, bude výsledkem vymazání části
obsahu terminálové řádky, zobrazené v průběhu předchozích událostí.
Parametrem 1 se mění směr, takže se vymaže naopak obsah před kurzorem a pokud je předána hodnota 2, vymaže se řádek celý a následující události ze budou zobrazovat od aktuální pozice kurzoru.
Pomocí zástupných slov 'backchar' a 'backline' lze vyhledat události, při kterých byla použita klávesa Backspace. Obvykle se jedná o překlepy, které mohou působit rušivě, pokud chceme záznam využít jako tutoriál. Použijeme-li tedy
$ asciiframe.sh find ascii.cast backline
nebo též
$ asciiframe.sh find ascii.cast K
A vrátí se nám v záznamu něco podobného..
24:[13.094431, "o", "\b\u001b[K"]
25:[13.275424, "o", "\b\u001b[K"]
26:[13.637253, "o", "\b\u001b[K"]
..půjde s velkou pravděpodobností o překlep, který můžeme ze záznamu
odstranit. Protože každá sekvence '\b' představuje jeden
umáznutý znak z řetězce zobrazeného předchozí událostí (která nemusí být
jen jedna). Sekvence jakou najde backline totiž vzniká obvykle při psaní, kdy poté,
co zjistíme svůj omyl, umázneme text a napíšeme nový. Každé stisknutí
klávesy se uloží samostatná událost. Takže pokud odstraníme řádky 24–26
i řádky 21–23, odstraníme překlep ze záznamu.
Ale než to uděláme, pro jistotu si zobrazíme události těchto řádků přes view:
$ asciiframe.sh view ascii.cast 21 26
21:[8.664646, "o", "1"]
22:[9.736504, "o", "6"]
23:[10.604407, "o", "9"]
24:[11.128128, "o", "."]
25:[13.094431, "o", "\b\u001b[K"]
26:[13.275424, "o", "\b\u001b[K"]
27:[13.637253, "o", "\b\u001b[K"]
A protože je tomu vskutku tak, Odstraníme tyhle události příkazem
$ asciiframe.sh event ascii.cast delete 21 26
find_K|\
find_backline)
echo """
asciiframe find ascii.cast K [DIGIT]
Search sequences EL (Erase in Line), which erases part of the line. If
DIGIT is 0 or missing, clear line from cursor to end of line. If
DIGIT is 1, clear line from cursor position to beginning of the line.
DIGIT is 2 clear entire line. Cursor position does not change.
[4mExample of search[0m [1m[K[0m
$ asciiframe find ascii.cast K
This sequence commonly exists with a combination with a new line,
backchar or return sequence, can be used next string for search, whereis
'n' (newline) may be replaced by 'b' (backchar) or 'r' (return):
$ asciiframe find ascii.cast '\\n\\u001b[K'
""" >> /dev/stderr
;;
find_J|\
find_clear)
echo '''
asciiframe find ascii.cast J [DIGIT]
Search sequences ED (Erase in Display), which clears part of screen. If
DIGIT is 0 or missing, clear screen from cursor to end of screeen. If
DIGIT is 1, clear screen from cursor position to beginning of the screen.
DIGIT is 2 clear entire screen, and move cursor to upper left corner. If
DIGIT is 3 clear entire screen and empty the scrollback buffer.
[4mExample of search[0m [1m[2J[0m
asciiframe find ascii.cast J 2
''' >> /dev/stderr
;;
find_blink)
echo '''
blink - not alternative to color
''' >> /dev/stderr
;;
find_colon) help_line
echo """
Parameter keys of find not accept colon (:), only semicolon (;)
""" >> /dev/stderr
;;
find_color)
echo """
asciiframe find ascii.cast color [STRING]
Search sequences for change font properties. If not STRING, show events
where string [1m[m[0m return font properties to defaults. Its same
effect as sequence [1m[0m[0m, which can be founded by
$ asciiframe find ascii.cast color 0
1 - set bold text
3 - set underline text
4 - set cursive text
More complexity strings can be founded as combination DIGITS & semicolon
$ asciiframe find ascii.cast color '..;.'
Found strings as [1m[31;1m[0m, [1m[32;3m[0m & etc., because dot substitute unknown
DIGIT. Color can be set by RGB over sequence [1m38;2;RED;GREEN;BLUE[0m where
RED, GREEN and BLUE values specify foreground color.
""" >> /dev/stderr
;;
find_A|\
find_B|\
find_C|\
find_D|\
find_H|\
find_cursor|\
find_cursor-fore|\
find_cursor-back|\
find_cursor-up|\
find_cursor-down)
echo '''
Terminal sequence move the cursor in screen area by PARAM and prepare
situation to be rewrite this part of terminal window by new content
Command A, or cursor-up N rows
Command B, or cursor-down N rows
Command C, or cursor-fore N columns
Command D, or cursor-back N columns
Command H, or cursor accept one or two values: LINES COLUMNS. If actually
size of terminal, is 80x24, find next command sequence set cursor to left
bottom corner: [1m[80;1H[0m, but for search events where the cursor
changed position is better use dots:
$ asciiframe find ascii.cast cursor '..;.'
''' >> /dev/stderr
;;
find_cursor-show|\
find_cursor-hide)
echo '''
$0 find ascii.cast cursor-show 1
5 flash on/of
25 VT220
1002
1006
1004 enable/disable reporting status
1049 enable/disable screen buffer
2004 turn on/off paste mode
$0 find ascii.cast cursor-show 2...
''' >> /dev/stderr
;;
Asciinema zaznamenává všechny události, co se dějí v terminálovém okně do souboru, zvaného asciicast, kde má každá událost samostatný řádek ve formátu JSON.
U starší verze 1 je to jinak. U něj je každá událost součástí pole
'stdout'. Proto doporučujeme pro záznamy používat příponu
.cast. Starší verze záznamů používaly příponu
.json
Obě verze formátu mají na prvním řádku souboru hlavičku. Takto vypadá záhlaví verze 2:
{
"verze": 2,
"šířka": 80,
"výška": 24,
"časové razítko": 1725352572,
"env": {
"SHELL": "/bin/bash",
"TERM": "xterm-256color"
}
}
Novější verze formátu podporuje události typu resize která umožňuje velikost okna za běhu změnit a marker, což jsou značky na které lze rovnou během přehrávání přeskočit.
file)
echo """
[1mDescription of asciinema format[0m
In asciinema format v2 is every terminal event recorder in the asciicast
file a independent row as JSON formated. Older format version 1, is
different - every event is part of array 'stdout'.
For v2 record is [1m.cast[0m suffix recommend. Early versions of
records v1 commonly [1m.json[0m use.
First line in both versions is for HEADER. Its example header of v2:
"""'''
{
"version": 2,
"width": 80,
"height": 24,
"timestamp": 1725352572,
"env": {
"SHELL": "/bin/bash",
"TERM": "xterm-256color"
}
}
'''"""
NOTE: Old format version 1 don't use 'timestamp' - time of record.
[4mAttributes [1mrequired[0m:
version - Verze asciicastu je důležitá pro přehrávač. CLI přehrávač
podporuje pouze formát v1 a v2. Formát v3 se kterým pracuje
javascriptový přehrávač ignoruje. Ovšem ignoruje také položky
ITEM jiného typu, takže s nimi můžeme v rámci v2 formátu pracovat.
width & height - Určují výchozí velikost terminálu. Verze v3
který se má použít. Tyhle hodnoty se dají za běhu změnit
pomocí rámce 'r', který obsahuje např. '60x30'
[4mAtributy [1mvolitelné[0m:
timestamp - Obsahuje časové razítko okamžiku, kdy byl záznam pořízen
duration - Celkový čas záznamu (float) obvykle chybí, ale může být doplněn.
idle_time_limit - Čas prodlevy mezi položkami. Zadává se při spouštění záznamu.
Podle toho se pak zaznamenávají události.
commad -
rec
play
cat
upload
auth
title - Pojmenování záznamu. Zadává se obvykle při začátku nahrávání, ale
je možné ho pojmenovat dodatečně, přes akci 'title'. U klipů se
nastavuje title automaticky, podle položky MARKER, nebo výchozího
času při akci 'move'
Při akci 'clip', se naopak použije tenhle parametr pro vytvoření
ITEM položky typu MARKER
theme - Dovoluje změnit výchozí nastavení barev terminálu."""'''
"theme": {
"fg": "#d0d0d0",
"bg": "#212121",
"palette": "#151515:#ac4142:#7e8e50:..."
}
'''"""
fg - výchozí barva písma
bg - výchozí barva pozadí
pallete - seznam hexakódů (#rrggbb) 8 nebo 16 barev, oddělených
dvojtečkou
env - nastavuje shell a jeho barevné schéma
[4mUdálosti ITEMS[0m
* Při záznamu se ukládá pouze výstup terminálu (out), jako FRAME 'o'
* Dodatečně lze záznam rozdělit pomocí události MARKER 'm' tak, aby bylo
možné v javascriptovém přehrávači přeskakovat na konkrétní časy
* Kromě toho se může vyskytnout také událost typu 'r', kdy dojde ke změně
výchozích rozměrů okna terminálu. Ovšem při přehrávání záznamu na
konzoli se to ignoruje.
""" >> /dev/stderr
;;
TODO
event_merge)
echo """
Při akci merge musí být předány minimálně dva argumenty, Identifikátor
zobrazované události (čas, nebo číslo řádku) a čas, nebo počet rámců,
kterým se vymezí rozsah zobrazovaných zpráv, jaký se má sloučit.
$0 merge <ORDER|TIME> <INTERVAL|COUNT>
""" >> /dev/stderr
;;
U záznamů, které obsahují rozsáhlé výpisy a hodně barev je výhodnější rozdělit události jenom tam, kde chceme udělat střih, nebo něco zvýraznit. Optimalizace celého záznamu přes akce split a ''merge totiž může být velmi zdlouhavá, a výsledný soubor velmi velký, protože se pro každý zobrazovaný znak generuje samostatná událost.
Následující příklad, kde je tučným písmem zvýrazněno použití příkazu split demonstruje jak to funguje:
$ asciiframe view ascii.cast 2
2: 1: [0.051663, "o", "\u001b[?2004h\u001b]0;user@stroj:
| \u0007user@stroj:~$ "]
$ asciiframe event ascii.cast split 2
$ asciiframe view ascii.cast context 2
0.051663 (0.051663 - 0.000000)
$ asciiframe view ascii.cast 10 0.1
2: 1: [0.051663, "o", "\u001b[?2004h"]
3: 2: [0.051663, "o", "\u001b]0;"]
4: 3: [0.051663, "o", "u"]
5: 4: [0.051663, "o", "s"]
...
32: 31: [0.051663, "o", " "]
$ asciiframe view ascii.cast frame
0.051663:2-32
...
Na řádku 2 má každý asciicast první událost, která začíná sekvencemi
\u001b[?2004h (ukaž kurzor) a
\u001b]0; (vyresetuj původní obsah terminálu) po které se
začíná psát do terminálu.
Při záznamu asciinema ukládá dění na terminálu do tzv. událostí, realizovaných v určitý konkrétní čas.
V případě tzv. zobrazitelných událostí, se obsah události v uvedený čas vypíše na standardní terminálový výstup. Může to být jeden znak, ale i celá sekvence znaků a pomocí akce split, lze tuhle událost rozdělit na sérii dílčích akcí, což ve výsledku vyvolá dojem, jako by nám onu sekvenci někdo právě psal.
Takovým způsobem lze rozdělovat pouze zobrazitelné události. Ten se liší tím, že aplikuje funkci text jen na omezený rozsah řádků a výsledek zapisuje do originálního souboru. Více viz dokumentace příkazu split.
event_split)
echo """Nápověda pro split
$0 clip START [INTERVAL]
START - identifikuje zobrazitelnou událost, která se má rozdělit na dílčí
události. Událost lze identifikovat časem, ve kterém dojde na její
zobrazení, nebo je v intervalu, který jí předchází.
Nebo číslem řádky na které se nachází.
INTERVAL - je kladné či záporné číslo typu float. V případě, že jde o číslo
kladné, použije se jako výchozí interval zobrazení.
Bude-li číslo záporné, vyjde se z délky stávajícího intervalu zobrazení
tedy času který uplyne, než dojde na zobrazení následující události,
od kterého se předaný čas odečte.
To znamená, že interval zobrazení mezi znaky bude takový, aby zůstal
zachován čas zobrazení předaný coby záporný interval.
""" >> /dev/stderr
;;
merge)
echo """
TODO help merge
""" >> /dev/stderr
;;
play)
Spustí přehrávání záznamu v novém terminálovém okně, jehož velikost přizpůsobí velikosti terminálu uvedené v asciicastu
Současný kód preferuje xfce4-terminal, protože umožňuje měnit za běhu velikost fontu, což umožňuje přehrávání záznamů ze strojů s velkým rozlišením displeje
echo """
Open new terminal window, sized by size of the ascii.cast and start play
$0 play ascii.cast
""" >> /dev/stderr
;;
'set')
Pokud nahráváte své asciicasty jen proto, abyste si mohli dodatečně přehrát, co se dělo, nepotřebujete do nich zasahovat, ale pokud chcete aby si ho přehrál někdo jiný, v přehrávači který podporuje přeskoky na události typu MARKER, určitě využijete příkaz marker, Přes který lze na konkrétní čas vložit do záznamu událost, na kterou lze během přehrávání asciicastu přeskočit.
To, zda nějaké události tohoto typu v záznamu existují, zjistíte přes
akci view, když se jí
předá parametr m, ale změnit zobrazovaný text, můžete
jedině tak, že přidáte příkazem marker na stejný čas značku s novým obsahem.
Interně se pak zavolá příkaz delete, který patří mezi příkazy akce event.
Akce set umožňuje vkládat do záznamu i sekvence s příkazy pro terminál, které umožní například nějakým způsobem zvýraznit určitý řetězec.Např. příkazem color lze přidat před zobrazovanou událost, kterou se zapne barevné (či jiné) a příkazem asciicastu do tutoriálu, se mohou hodit i další příkazy:
echo '''
asciiframe [-h] set ascii.cast COMMAND [TIME|ORDER] [CONTENT]
Add new events into the asciicast. For COMMAND help try option '-h':
[4mCOMMANDS[0m:
'comment' - add new line for comment effect
'commentline' - add new line for comment effect
'clear' - command clear effect
'duration' - set this attribute to header
'root' - add a new prompt sequence as generic root
'user' - add a new prompt sequence as generic root
'line' - change event type (o|m)
'marker' - add new event as MARKER
'newline' - return newline effect
'resize' - add new event as RESIZE
'reset' - add terminal command for set to default color scheme
'size' - set default size to header
'time' * set same time for events scope
'title' - set new title of asciicast
Note: If not ORDER or TIME, be event added as a last event of the record.
* If used ORDER, be actualized event by ORDER. ORDER is not LINE!
* If used TIME, be actualized only first event from range events with
the same time.
$0 set ascii.cast marker 10 'Go to logo'
Add new marker with text 'Go to logo' before event from line 10.
$0 set ascii.cast resize -10 160x48
Add resize event after event, which started on line 10
$0 set ascii.cast delete -10 160x48
''' >> /dev/stderr
;;
set_line)
echo '''
$0 event ascii.cast line 10 TYPE
[4mExample[0m
Value TYPE 'o' change to visible event and value 'm' change to event
marker. Next example demonstrate how-to colorize interval from 0:02 to
0:15 sec.
$0 set ascii.cast marker 0:O2 '\u001b[1:38m'
$0 set ascii.cast marker 0:15 '\u001b[0m'
$0 view ascii.cast m
8: 1: [2.0, "m", "\u001b[1;38;m"]
...
150: 2: [15.0, "m", "\u001b[0m"]
$0 set ascii.cast line 8 o
$0 set ascii.cast line 150 o
''' >> /dev/stderr
;;
set_newline) echo '''
asciiframe set ascii.cast newline [LINE|TIME] [TYPE]
Add new line sequence, by TIME or LINE. TYPE is string, which add before
if exists.
[4mExamples[0m
$ asciiframe set ascii.cast newline 0.21345
Add new line sequence, with time 0.21345
''' >> /dev/stderr
;;
set_marker)
echo """[4mExample[0m
$0 event ascii.cast marker 12.25 ['# Text of the marker']
Exactly or by using of the „human” time to 12.000000:
$0 event ascii.cast marker 0:12 ['# Example text of the marker']
[4mHow to add marker before event LINE?[0m
$0 event ascii.cast marker 10 'Text of marker'
""" >> /dev/stderr
;;
set_resize)
echo """
The resize event 'r' change default size terminal.
$0 set ascii.cast resize [LINE|TIME] WIDTHxHEIGHT
[4mExample[0m
$0 set ascii.cast resize 12.25 80x24
If event 'r' (resize) for time 12.250000 unexists, create new event for
change from the actually size of terminal to 80 columns and 24 rows.
$0 set ascii.cast resize 80x24
If not TIME or LINE, add this event to end of the record and use time of
the last event for it.
""" >> /dev/stderr
;;
split)
Tuto akci lze využít v kombinaci s akcí merge k optimalizaci asciicastu. Během záznamu asciinema neřeší sekvence, které jsou příkazem pro terminál, a tak je klidně rozdělí mezi dvě události. Během přehrávání se ty znaky pošlou na terminál tak jako tak. Jenže rozdělení takových sekvencí komplikuje střih záznamu. Proto je lepší pracovat s optimalizovaný záznamem, který lze jednoduše vyrobit následujícími příkazy:
$ asciiframe split ascii.cast > range.cast
$ asciiframe merge range.cast range > optimalized.cast
Připravte se ale na to, že zpracování záznamu, pokud byl pořízen na
konzoli ve full HD rozlišení, může zabrat poměrně hodně času. Jen pro
ilustraci, zpracování testovacího záznamu ascii.cast
pořízeného v prostředí terminálu o velikosti do souboru
rang.cast trvalo cca 2 hodiny a výsledný soubor je 14x
větší
$ wc ascii.cast
3579 53089 1969615 ascii.cast
do
$ wc rang.cast
1129853 3899292 27750081 rang.cast
trvalo cca 2 hodiny. Chcete-li si tedy pouze zjednodušit střih, nebo zvýraznit nějakou sekvenci textu, použijte místo téhle akce příkaz split, který se volá při akci event. Ten zpracuje pouze události v úseku, kde budete chtít záznam „rozstřihnout”.
Tahle akce volá stejnou funkci jako příkaz split, jenom bez parametrů, takže se postupně
zpracují události celého záznamu a výsledek se průběžně vypisuje na
'stdout, takže se dá rovnou přehrávat, ale který lze zapsat do
souboru range.cast. Výsledek se během přehrávání zdá na
první pohled identický, ale pokud se do něj podíváte a srovnáte obsah s
originálním záznamem, zjistíte, že byly rozděleny všechny zobrazované
události do jednoznakových událostí se stejným časem. A protože mají
stejný čas zobrazení, zobrazují se stejně jako kdyby byly součástí jedné
události, proto během přehrávání rozdíl nepoznáte.
split a merge. Ovšem to jsou operace, které upraví obsah původního asciicastu, kdežto akcefind'' nic nemění. Takže pokud se řetězec v záznamu nevyskytne v rámci jedné události, nevypíše nic.
echo """
TODO action split
$0 play ascii.cast
""" >> /dev/stderr
;;
view)
Umožňuje prohlížet jednotlivé události asciicastu podle času, čísla řádku a typu. Parametry zpracovává funkce get_view(), která událost obarví podle typu a předá k výpisu na stdout funkci lineblock(), která ji zalomí podle aktuální šířky terminálového okna
Obarvení událostí zajišťují funkce:
m (marker) červeněo (output)r (resize) zeleněNejsou-li předány žádné parametru, vypíšou se postupně všechny události.
Jednu konkrétní událost si lze zobrazit buď na základě času, který spadá do jejího intervalu zobrazení, nebo čísla řádku LINE na kterém se v asciicastu vyskytuje:
$ asciiframe.sh view ascii.cast 123
123: 122: [134.754764, "o", "\b\u001b[K"]
Tato akce má k dispozici také příkazy:
Z výpisu jedné události si ale jen těžko uděláme představu o kontextu. Proto lze výpis rozšířit druhým parametrem CONTEXT, který zobrazí událost v širším kontextu.
Takto si vypíšeme všechny události, co se zobrazí během jedné sekundy před zobrazením události z řádku 123
$ asciiframe view ascii.cast 123 -1.0
119: 118: [133.256164, "o", "s"]
120: 119: [133.415933, "o", " "]
121: 120: [134.158507, "o", "/"]
122: 121: [134.261533, "o", "/"]
123: 122: [134.754764, "o", "\b\u001b[K"]
Když použijeme místo LINE čas TIME, bude výsledek jiný, protože na základě času se nevylistuje událost jejíž čas odpovídá předané hodnotě, ale událost, která se během přehrávání spustí jako poslední, než dojde na uvedený čas, takže i když čas předaný v následujícím ukázkovém příkladu odpovídá času události z řádku č. 123, bude zobrazena událost z řádku 122, které v ten čas skončí interval zobrazení
$ asciiframe.sh view ascii.cast 134.754764
122: 121: [134.261533, "o", "/"]
Čas lze předat také v „human-ready” formátu [HH:]MM:SS, což dává smysl pokud si chceme doplnit značky do delšího asciicastu, staženého ze serveru asciinema.org Ten nevkládá značky do uploadovaných souborů, takže je potřeba najít to správné místo podle času a dostatečně velkého časového intervalu.
scopePokud se v vyskytují série událostí, spouštěných ve stejný čas vylistuje jejich rozsahy. Tj. čas a pořadové číslo první a poslední řádky této série.
~$ asciiframe view ascii.cast scope [RANGE|LINE|TIME]
Rámec je sada událostí, která se zobrazí ve stejném čase. Příkaz předá funkci set_frame(), rozsah řádků a pak bude uživatel dotázán, jestli se má zapsat do asciicastu jako FRAME Pokud ne, vytiskne se obsah přes fold na stdout.
Vyhledání události přes čas zobrazení má svůj význam, také pokud si chceme zobrazit v kontextu události, které jsou v sérii událostí zobrazovaných ve stejný čas. K tomu, zda-li taková série v záznamu existuje lze využít příkaz scope
$ asciiframe.sh view ascii.cast scope
0.100000:4-5
$ asciiframe.sh view ascii.cast 0.1 -0.0001
3: 2: [0.001000, "o", "\r\n# this is a comment; we are @ playtime
| 1:03 right now; this is line 2345 of recording\r\n"]
4: 3: [0.100000, "o", "\u001b[?2004h\u001b]0;user@stroj:
| ~\u0007user@stroj:~$ "]
5: 4: [0.100000, "o", "error "]
Pamatujte, že je vždy lepší použít číslo řádku LINE. Nejenom proto, že se zpracuje nejrychleji, ale vyhnete se i riziku, že omylem zrušíte jinou událost, než jste chtěli.
Akce view je velice užitečná také při práci se značkami a protože se při jejich úpravách přes marker, nebo resize pracuje s jejich pořadím, jsou k dispozici proviewčtyři příkazy, kterými lze vypisovat tyhle události také podle pořadí ORDER, v rámci příslušného typu. Viz ukázkový příklad u příkazu line (akceset'')
echo '''
asiiframe view ascii.cast LINE|TIME [LIMIT]
Show lines from ascii.cast with a order number line and order number in
scope of type. TIME, passed as float number, will be interpreted as time
than will displayed next event. LIMIT specificated by count or time show
context of event. A negative value show scope of previous events, and
positive scope of next events. For COMMAND help, add option '-h'.
$0 view [-h] ascii.cast COMMAND [ORDER|LINE]
[4mCOMMANDS[0m:
context - event context of the event, the entire interval, time before
show & time elapse to next event
info - show info about ascii.cast file in human form
h - show header
o - show visible event by order
m - show marker event by order
r - show resize event by order
scope - show the ranges of events, with the same time
''' >> /dev/stderr
;;
view_context) echo '''
[4mExample use:[0m [1mcontext[0m ([3mview[0m)
asciiframe view ascii.cast context LINE
Command show context event. Example of result for event from line 23:
$ asciiframe view ascii.cast context 23
0.673373 (0.662924 - 0.010449)
| | \
| | Interval to show next event from line 24
| \
\ Wait elapse from show event from line 22
INTERVAL, from previous event on 22 line to show next event from
line 24
''' >> /dev/stderr
;;
view_h) echo '''
[4mExample use:[0m [1mh[0m ([3mview[0m)
asciiframe view ascii.cast h
Show header of the ascii.cast - see example of result:
{
"version": 2, <- format version of the asciicast
"width": 80, <- original width of terminal in columns
"height": 24, <- original height of terminal in rows
"timestamp": 1725352572, <- record time of the original asciicast
"title": "Asciiframe", <- title of record
"duration": 9.373257, <- the duration of the entire record
"env": {
"SHELL": "/bin/bash", <- recorded shell
"TERM": "xterm-256color" <- colors scheme of shell
}
}
''' >> /dev/stderr
;;
view_m) echo '''
[4mExample use:[0m [1mm[0m ([3mview[0m)
asciiframe view ascii.cast m [ORDER]
Show markers events of the ascii.cast, example demonstrate ORDER using:
$ asciiframe view ascii.cast m 2
30: 2: [14.200000, "m", "# „On-the-fly“ translation example"]
\
Order marker in ascii.cast file
''' >> /dev/stderr
;;
view_o) echo '''
[4mExample use:[0m [1mo[0m ([3mview[0m)
asciiframe view ascii.cast o [ORDER]
Show visible events of the ascii.cast, example demonstrate ORDER using:
$ asciiframe view ascii.cast o 14
15: 14: [3.956926, "o", "\r\n\u001b[?2004l\r"]
\
Order visible event in ascii.cast file
''' >> /dev/stderr
;;
view_r) echo '''
[4mExample use:[0m [1mr[0m ([3mview[0m)
asciiframe view ascii.cast r [ORDER]
If used [4mwithout[0m ORDER, show all [3mresize[0m events in the
ascii.cast, if exists. See example of result:
$ asciiframe view ascii.cast r
18: 1: [5.100000, "r", "60x40"]
21: 2: [8.100000, "r", "80x24"]
| \
\ Ordering events resize type
LINE
''' >> /dev/stderr
;;
view_scope) echo '''
[4mExample use:[0m [1mscope[0m ([3mview[0m)
asciiframe view ascii.cast scope [LINE|TIME|RANG]
Show rangs of events in the ascii.cast, if exists. If LINE used, show
only rang where is. See example:
$ asciiframe view ascii.cast scope 112
40.404066:111-121
\
Line in scope from line 111 to 121.
For TIME matter if in seconds or if „human-ready” format [HH:]MM:SS. For
„human-ready” time is found first event after - for it scope return
range, but for TIME in seconds is result only if the time is the same.
$ asciiframe view ascii.cast scope 0:40
40.404066:111-121
$ asciiframe view ascii.cast scope 40.404
$
$ asciiframe view ascii.cast scope 40.404066
40.404066:111-121
''' >> /dev/stderr
;;
*)generic help
echo """
[1mAsciiframe[0m, asciicast editing tool for the CLI, can be used to
additional post-processing the asciinema records. Distributed
by the ascii.cast – see egg for action [3msplit[0m
asciiframe [-d|-h|-x|-v] [-f] ACTION ascii.cast [PARAMS]
[4mOPTIONS[0m:
-f - commands realize without questions
-h - help for action
-d - verbose output (usable only for script debugging)
-v - print logo & version
-x - print history or eggs with action name
[4mACTIONS[0m:
clip - clip export and import clip
event - add or remove events from asciicast
find - show strings from asciicast
play - (only X window) open a new xterm window and play ascii.cast
set - header, size and resize and size events into asciicast
split - optimize ascii.cast to stdout
merge - merge ranges of events
view - show colorized asciicast
""" >> /dev/stderr && return
;;
esac
}
Jednoduchá potvrzovací funkce. Na dotaz, předaný v parametru
$1 musí být jednoznačná odpověď ano-ne. V případě, že
bude nastavena proměnná ${FORCE} s hodnotou yes,
se automaticky vrací 0
V případě záporné odpovědi se vrací 1.
anone () {
if [ -n "${1}" ] ; then
if [ "${FORCE}" != "yes" ] ; then
echo "$1" >> /dev/stderr
while true ; do
read -p "(ANO/NE) : " yn
read -er yn
case $yn in
[AaYyZz]*)A - Ano, A - Add, Y<=>Z - yes
break
;;
[NnQqCc]*)N - no, Q - quit, C - cancel, S - storno
return 1
;;
*) echo "YES or NOT?" >> /dev/stderr ;;
esac
done
fi
return 0
else
log warn "anone() Not value"
return 1
fi
}
echo -n "╔" && echo -n $(for i in $(seq 1 78); do printf "═"; done) && echo "╗" echo -n "║" && printf "║ %.76s ║\n" && echo "║" echo -n "╚" && echo -n $(for i in $(seq 1 78); do printf "═"; done) && echo "╝" Je pomocná funkce, pro zvýraznění nápovědy
help_line () {
printf "%0.s." $(seq 1 ${COLUMNS}) >> /dev/stderr
return
}
Tahle funkce, kterou volá funkce get_view(), vypisuje
asciiplayer () {
[ ${DEBUG} ] && \
echo "INFO: asciiplayer() - ${TERMINAL}" >> /dev/stderr
[ ! -x "${ASCIINEMA}" ] && exit 40
local RETEZEC
local WF
local WH
case ${XDG_SESSION_TYPE} in
x11) if [ ${TERMINAL} ] ; then
TERMINAL=rxvt asciiframe ...
TERMINAL=${TERMINAL##*/}
elif [ ${RXVT} ] ; then
rxvt
TERMINAL=${RXVT##*/}
elif [ ${XFCE4TERM} ] ; then
xfce-terminal
TERMINAL=${XFCE4TERM##*/}
elif [ ${TERMINATOR} ] ; then
terminator
TERMINAL=${TERMINATOR##*/}
elif [ ${XTERM} ] ; then
xterm
TERMINAL=${XTERM##*/}
fi
;;
*) TERMINAL=none
;;
esac
ROURA="${TEMP_DIR}/pipe.cast"
[ -p ${ROURA} ] || mkfifo ${ROURA}
if [ -z "${1}" ] ; then
( [ -p ${ROURA} ] && cat ${RECORDFILE} >> ${ROURA} || echo "ERR: asciiplayer(): FIFO ${ROURA} no exists" >> /dev/stderr &)
elif [ -f "${1}" ] ; then
( [ -p ${ROURA} ] && cat ${1} >> ${ROURA} || echo "ERR: asciiplayer(): FIFO ${ROURA} no exists" >> /dev/stderr &)
else
echo "TODO: asciiplayer()" >> /dev/stderr
return
fi
sleep 1
echo "INFO: asciiplayer() ${1} ${TERMINAL} ${PLAYER} ${PLAY}" >> /dev/stderr
( HA=$(head -1 ${TEMP_DIR}/height)
WA=$(head -1 ${TEMP_DIR}/width)
case ${TERMINAL} in
rxvt|rxvt-unicode)
[ ${DEBUG} ] && \
${TERMINAL} -T asciinema-player -geometry "${WA}x${HA}" -c "resize ; sleep 1 ; ${ASCIINEMA} play ${ROURA}" >> /dev/stderr
${TERMINAL} -T asciinema-player -geometry "${WA}x${HA}" -e bash -c "${ASCIINEMA} ${PLAY} play ${ROURA}"
;;
terminator) if [ ${WA} -gt 190 ] ; then
199x145 - 7.03 - 199 14.2 - 145
WF="7.03"
HF="14.2"
elif [ ${WA} -gt 150 ] ; then
WF="7.06"
HF="14.8"
else
80x24 – 7.095 15.0
WF="7.095"
HF="15.0"
fi
TW="$((${WA} * $(float_to_string ${WF}) / 1000000))"
TH="$((${HA} * $(float_to_string ${HF}) / 1000000))"
[ ${DEBUG} ] && \
echo "${TERMINAL} -T asciinema-player \
--geometry \"${TW}x${TH}+30+30\" \
-e \"resize ; sleep ; ${ASCIINEMA} play ${ROURA}\"" \
>> /dev/stderr
${TERMINAL} -T asciinema-player \
--geometry "${TW}x${TH}+30+30" \
-e "resize ; ${ASCIINEMA} play ${ROURA}"
;;
xterm)
[ ${DEBUG} ] && \
${TERMINAL} -T asciinema-player -geometry "${WA}x${HA}" -e "resize ; sleep 1 ; ${ASCIINEMA} play ${ROURA}" >> /dev/stderr
${TERMINAL} -T asciinema-player -geometry "${WA}x${HA}" -e "${ASCIINEMA} ${PLAY} play ${ROURA}"
;;
xfce4-terminal)Pro 1600x900 je-li ${}u běžné velikosti 10x 2 14x
if [ ${WA} -gt "140" ] ; then
ZOOM="-7"
elif [ ${WA} -gt "150" ] ; then
ZOOM="-2"
else
ZOOM="0"
fi
[ ${DEBUG} ] && \
${TERMINAL} -T asciinema-player --zoom ${ZOOM} --geometry "${WA}x${HA}+0+0" -e "resize ; sleep 1 ; ${ASCIINEMA} play ${PLAY} ${ROURA}" >> /dev/stderr
${TERMINAL} --disable-server -T asciinema-player --zoom ${ZOOM} --geometry "${WA}x${HA}+0+0" --dynamic-title-mode=replace -e"${ASCIINEMA} play ${PLAY} ${ROURA}"
;;
*) x=6
for i in $(seq 5) ; do
[ $x -le 1 ] && \
break || \
x=$(($x - 1))
printf "\rPlaying start after %d sec." "$x"
sleep 1
done
set default colors
echo -e "\x1B"$(get_string color 0)
H - goto top-left
echo -e "\x1B"$(get_string H)
clear screen
echo -e "\x1B"$(get_string J 3)
${ASCIINEMA} play ${PLAY} ${ROURA}
return
;;
esac
)
return
}
Tato funkce, volaná příkazem text zpracovává proud událostí a slučuje jednoznakové události tak, aby vytvořily souvislé řetězece, na které lze aplikovat příkaz grep
Výstup je formátován tak, aby mohl být jednoduše rozdělen na pozici znaku 21. Pro ukázkový příklad použití viz dokumentace příkazu text
events_merge () {
M=0
MAX=0
while read line ; do
CONTENT="${line##*,\ \"}"
ITEM="${CONTENT::-2}"
#event_plain "${SLOUCIT}${line##*,\ \"}"
case ${#ITEM} in
1) MESSAGE="${MESSAGE::-2}${ITEM}\"]"
[ ! -z ${DEBUG} ] && echo "${MESSAGE}" >> /dev/stderr
MAX=${#MESSAGE}
;;
*) if [ "${line}" == "${line/\r/}" ] ; then
FROM="${MESSAGE%%,*}"
TO="${line%%,*}"
if [ "${TO:0:1}" == '[' ] ; then
OD=$(float_to_string ${FROM:1})
DO=$(float_to_string ${TO:1})
[ "${FROM:0:1}" != '[' ] && OD="0"
LINE=$(get_line ${FROM:1})
[ -z "${LINE}" ] && LINE=1
TIME=$((${DO} - ${OD}))
[ ${TIME} -gt 1 ] && TIME=$(float_from_string ${TIME}) || TIME=$(float_from_string ${OD})
printf " %5s %10s : %s\n" "${LINE%%:*}" "${TIME}" "${MESSAGE}"
fi
MESSAGE="${line}"
else
BEFORE="${ITEM%%\ \\r*}"
if [ "${ITEM#${BEFORE}\ }" == "\r" ] ; then
MESSAGE="${MESSAGE::-2}${BEFORE}\"]"
else
FROM="${MESSAGE%%,*}"
TO="${line%%,*}"
if [ "${TO:0:1}" == '[' ] ; then
OD=$(float_to_string ${FROM:1})
DO=$(float_to_string ${TO:1})
[ "${FROM:0:1}" != '[' ] && OD="0"
LINE=$(get_line ${FROM:1})
TIME=$((${DO} - ${OD}))
[ ${TIME} -gt 1 ] && TIME=$(float_from_string ${TIME}) || TIME='1.0'
printf " %5s %10s : %s\n" "${LINE%%:*}" "${TIME}" "${MESSAGE}"
fi
MESSAGE="${line}"
fi
fi
;;
esac
done
}
Zpracuje předaný rozsah tak, že volá funkci sm(), která rozděluje událost na jednoznakové události, které odbarvuje přes funkci event_plain() a sestavuje zpět pomocí ???
event_decolor () {
[ ${DEBUG} ] && \
echo "INFO: event_decolor() - LINE ${LINE} ENDLINE ${ENDLINE} ${TIME}" >> /dev/stderr
echo "decolorize rozsah událostí"
echo TODO-event-decolor
echo "Volaná funkce, zpracuje rozsah $LINE $ENDLINE tak, že volá funkci sm(), která události rozkládá na znaky, z výsledné série událostí odfiltruje vše co se týče obarvení textu a sekvence pomocí
merge opět sloučí" >> /dev/stderr
echo "TODO split & merge!" >> /dev/stderr
return
if [ -z "${ENDLINE}" ] ; then
for i in $(sm $(sed -n ${LINE}p ${RECORDFILE}) |\
sed 's/\\u001b\[[0-9;]\+m//g') ; do
case ${i} in
'˙') STRING="${STRING}." ;;
' ') STRING="${STRING} " ;;
*) STRING="${STRING}${i}" ;;
esac
done
tady už se pracuje se zpracovanou událostí
echo ${STRING}
return
[ ${DEBUG} ] && \
echo "INFO: same_time() ${1} line ${LINE}" >> /dev/stderr
(
${ED} "${RECORDFILE}" -q -s 2>/dev/null <<-EOF
${LINE}p
q
EOF
) >> ${TEMP_DIR}/same_event
jiná událost, jiný soubor
EVENT=$(head -1 ${TEMP_DIR}/same_event)
echo ${EVENT} >> /dev/stderr
anone "[1mChange time of this event from line ${LINE} to ${TIME}? For continue push 'y' key[0m"
[ $? -eq 1 ] && \
return
change time
${ED} "${RECORDFILE}" -s 2>/dev/null <<-EOF
${LINE}
.s/[^,]*\,/[${TIME},/1
w
q
EOF
else
[ ${DEBUG} ] && \
echo "INFO: same_time() ${1} by scope from ${LINE} line to ${ENDLINE}" >> /dev/stderr
[ ${ENDLINE} -lt $((${LINE} + 1)) ] && \
echo "ERROR: same_time() - Invalid scope for ${1} (${LINE} - ${ENDLINE})" >> /dev/stderr && \
return 1
tady se zpracují události jedna po druhé..
return
sed -n ${LINE},${ENDLINE}p ${RECORDFILE}
anone "[1mSet for this scope ${LINE}-${ENDLINE} time to ${TIME}? For continue push 'y' key.[0m" >> /dev/stderr
[ $? -eq 1 ] && \
return
${ED} "${RECORDFILE}" 2>/dev/null <<-EOF
${LINE},${ENDLINE}s/[^,]*\,/[${TIME},/1
w
q
EOF
fi
return
}
Funkce z řetězce události $1 odstraňuje barvy a jiné zvýraznění textu. Problém je, pokud je řetězec, kterým se nastavuje zvýraznění rozdělen přes dvě události, např.
... \u001b"]
[0.403087, "o", "[38;5;76m "]
kde řetězec zobrazované události končí znakem \u0001b,
po kterém by měly následovat další znaky, které jsou již součástí
následující událost. Nebo pokud řetězec zobrazované události končí
neukončenou sekvencí pro nastavení barvy
... \u001b[38;5;"]
[0.43567, "o", "66m ...
Řešením je pouze sloučení událostí, jako to dělá funkce print_monochrome() a opětové zavolání funkce event_plain()
event_plain () {
sed 's/\\u001b\[[0-9;]\+m//g' <<< "${1}"
return
}
Tato funkce přijme jako parametr $1 číslo řádky na které se vyskytuje událost typu 'r', obsahující informaci o změně standarní velikosti terminálu. Zracuje její obsah a vrátí ve formě třech řetězců: TIME, WSIZE a HSIZE
get_resize () {
[ ${DEBUG} ] && \
echo "INFO: get_resize() – ${1}" >> /dev/stderr
local CONTENT
local HEIGHT
local LINE
local WIDTH
LINE=$(sed -n ${1}p ${RECORDFILE} | grep '"r"')
CONTENT="${LINE#*\[}"
#/ echo "${CONTENT}" >> /dev/stderr
WIDTH="${CONTENT%x*}"
HEIGHT="${CONTENT##*x}"
printf "%s %s %s" $(float_to_string ${CONTENT%%,*}) "${WIDTH##*\"}" "${HEIGHT%\"*}"
return
}
Funkce sestaví hlavičku pro nový klip
header_print () {
[ ${DEBUG} ] && \
echo "CALL: $FUNCNAME()" >> /dev/stderr
printf "{\"version\": %d," $(head -1 ${TEMP_DIR}/version)
printf " \"width\": %d," $(head -1 ${TEMP_DIR}/width)
printf " \"height\": %d," $(head -1 ${TEMP_DIR}/height)
printf " \"timestamp\": %d," $(head -1 ${TEMP_DIR}/timestamp)
TEST=$(head -1 ${TEMP_DIR}/title)
[ "${TEST}" != null ] && \
printf " \"title\": \"$(head -1 ${TEMP_DIR}/title)\","
TEST=$(head -1 ${TEMP_DIR}/duration)
if [ -z "${TEST}" ] || [ "${TEST}" == "null" ] ; then
echo > /dev/null
else
printf " \"duration\": ${TEST},"
fi
TEST=$(head -1 ${TEMP_DIR}/idle)
if [ -z "${TEST}" ] || [ "${TEST}" == "null" ] ; then
echo > /dev/null
else
printf " \"idle_time_limit\": ${TEST},"
fi
if [ ! -z ${FG} ] || \
[ ! -z ${BG} ] || \
[ ! -z ${PALLETE} ] ; then
echo theme >> /dev/null
#ORIGFG
#ORIGBG
#ORIGPALLETE
fi
printf " \"env\": {"
printf "\"SHELL\": \"$(head -1 ${TEMP_DIR}/shell)\","
printf " \"TERM\": \"$(head -1 ${TEMP_DIR}/term)\"}"
echo "}"
return
}
Tahle funkce začleňuje do asciicastu klip, nebo jiný asciicastový soubor, pokud ho dostane jako parametr $3.
V případě, že parametr $3 chybí, nebo funkce value() vrátí jinou hodnotu než
file, dojde pouze na úpravu časování stávajících událostí
dle v parametru $2, a to od události, která následuje
po čase, co byl v parametru $1 – oba předané parametry
tedy musí být typu float (číslo s tečkou).
Bude-li mít parametr $1 hodnotu 0.0,
vloží se události klipu před stávající obsah asciicastu. Je-li předán jiný čas, vypíšou se
všechny události až do onoho času, aniž by se měnilo jejich časování, a
to bez ohledu na to bude-li se vkládat nějaký klip (či jiný asciicastový
soubor), nebo ne.
Při vložení se z hlavičky klipu (resp. jiného asciicastu) vygeneruje událost typu m
(marker), jejíž obsah bude vycházet z atributu 'title' –
bude-li existovat. Nebude-li existovat, použije se místo něj jméno asciicastového souboru a jeho
časové razítko. A za ní, pokud má klip v hlavičce jinou velikost
terminálu než záznam, do kterého se klip vkládá bude následovat událost
typu r resize.
Parametr $2, který může mít hodnotu kladnou i zápornou, nastavuje časový interval o který se má posunout výchozí časování klipu. Bude-li mít zápornou hodnotu, může být dlouhý maximálně tak, jak je dlouhý interval zobrazení předchozí události.
Časování událostí, které se posunou až za poslední událost vloženého klipu, se upraví podle toho, jak vyjde časování poslední události vloženého klipu.
Čerstvě pořízený asciicast
obvykle zobrazením poslední události přehrávání ukončí, takže jeho
celkový čas odpovídá času, jejího zobrazení, ale při exportu klipu do
hlavičky generuje funkce clip_print() také atribut 'duration', jehož hodnota
se při vložení natáhne do proměnné ${CLIPDURATION} – ta určí, jak dlouhý
bude interval zobrazení za poslední událostí klipu.
U čerstvě pořízeného asciicastu ale atribut 'duration' v hlavičce chybí, proto se časování událostí za vloženým souborem automaticky posouvá o stejný interval, jaký události předcházel před vložením klipu. A to lze pomocí parametru $2 měnit.
V případě, že se žádný jiný asciicast, nebo klip nevkládá, lze pomocí této funkce upravit stávající časování událostí.
To se hodí například pokud chceme přes akci event do záznamu doplnit komentář a následně posunout časy zobrazení událostí tak, aby se dal přečíst, aniž by bylo nutné změnit rychlost přehrávání nebo ho pozastavit.
Záporná hodnota parametru $2 naopak pomůže, pokud během nahrávání asciicastu bylo nutné udělat i jiné, neplánované operace. Ty se dají „vystřihnout” přes akci event příkazem 'delete' a takto vytvořená prodleva se dá redukovat právě pomocí akce clip. To jakou hodnotu pro $2 lze použít, se dá nejlépe zjistit pomocí akce view.
include_clip () {
echo "INFO: include_clip() START ${1} SHIFT ${2} CLIP ${3}" >> /dev/stderr
local BEFORE
local CLIP
local LINE
local END
local ENDLINE
local START
local TEST
local SHIFT
local TIME
if [ -f ${3} ] ; then
CLIP=${3}
fi
case $(value ${2}) in
flow) SHIFT=$(float_to_string ${2})
;;
nflo) SHIFT="-$(float_to_string ${2:1})"
;;
*) echo "INFO: As shift time must be used float number as -0.123 or 0.123" >> /dev/stderr
;;
esac
case $(value ${1}) in
flow|time) if [ ${1/:/} == ${1} ] ; then
TIME=${1}
else
TIME=$(print_seconds ${1})
fi
LINE=$(line_by_time ${TIME})
if [ $? -eq 1 ] ; then
LINE="${#LINES[@]}"
echo "INFO: include_clip() - add clip to TIME ${TIME} as LINE ${LINE}" >> /dev/stderr
else
TEST=$(get_view context ${LINE})
TEST=${TEST#*\(}
BEFORE=$(float_to_string ${TEST%% *})
fi
;;
inte) if [ "${1}" -eq "1" ] || [ "${1}" -eq "2" ] ; then
LINE="2"
TEST=$(get_view context ${LINE})
TEST=${TEST#*\(}
BEFORE=$(float_to_string ${TEST%% *})
echo "INFO: include_clip() - before first event to LINE ${LINE}, use TIME ${TIME} and default SHIFT ${SHIFT}" >> /dev/stderr
elif [ "${1}" -le $((${#LINES[@]} +1 )) ] ; then
LINE="${1}"
TEST=$(get_view context ${LINE})
TEST=${TEST#*\(}
BEFORE=$(float_to_string ${TEST%% *})
if [ $((${BEFORE} + ${SHIFT})) -ge 0 ] ; then
echo "INFO: include_clip() - add clip as line ${LINE} TIME ${TIME} with default SHIFT" >> /dev/stderr
else
echo "ERROR: include_clip - max negative SHIFT value: -$(float_from_string ${BEFORE})" >> /dev/stderr
return 1
fi
else
echo "ERROR: include_clip - Out of rang parameter ${#LINES[@]} ${1}" >> /dev/stderr
return 1
fi
;;
stri) echo "Příkaz diff"
return
;;
esac
if [ -f "${CLIP}" ] ; then
CLIPTEST=$(head -1 ${CLIP})
if [ ${#CLIPTEST} -eq 1 ] ; then
# struct JSOM
CLIPFILE="${TEMP_DIR}/json.clip"
${JQ} . -c -M ${3} > ${CLIPFILE}
CLIPHEADER=$(head -1 ${CLIPFILE})
elif [ ${#CLIPTEST} -eq 8 ] ; then
struct colorized JSON
CLIPFILE="${TEMP_DIR}/json.clip"
cat ${CLIP} | decolor | ${JQ} . -c -M > ${CLIPFILE}
else
CLIPFILE=${CLIP}
fi
clip_load
[ -z ${CLIPLINES} ] && return 1
echo ${CLIPLINES[@]} >> /dev/stderr
if [ -z "${TIME}" ] ; then
START=$(float_from_string ${LINES[$((${LINE} - 2))]:1})
(
printf '[%s, "m", "%s"]\n' "${START}" "$(head -1 ${TEMP_DIR}/ctitle) - $(head -1 ${TEMP_DIR}/ctimestamp)"
printf '[%s, "r", "%s"]\n' "${START}" "$(head -1 ${TEMP_DIR}/cwidth)x$(head -1 ${TEMP_DIR}/cheight)"
) > ${TEMP_DIR}/clip.marker
echo null > ${TEMP_DIR}/duration
else
(
printf '[%s, "m", "%s"]\n' "${TIME}" "$(head -1 ${TEMP_DIR}/ctitle) - $(head -1 ${TEMP_DIR}/ctimestamp)"
printf '[%s, "r", "%s"]\n' "${TIME}" "$(head -1 ${TEMP_DIR}/cwidth)x$(head -1 ${TEMP_DIR}/cheight)"
) > ${TEMP_DIR}/clip.marker
echo null > ${TEMP_DIR}/duration
fi
new timestamp
echo $(date +%s) > ${TEMP_DIR}/timestamp
header_print
x=0
${SED} 's/\\/\\\\/g' ${RECORDFILE} | while read line ; do
if [ ${x} -eq $((${LINE} - 1)) ] ; then
if [ ${LINE} -le 2 ] ; then
to front
[ ${START} ] && POSUN=$(float_to_string ${START}) || POSUN=$(float_to_string ${TIME})
echo "---- VVV ${CLIPFILE} add before first event of the original ascii.cast VVV ----" >> /dev/stderr
add new marker
cat ${TEMP_DIR}/clip.marker
y=0
TEST=0
${SED} 's/\\/\\\\/g' ${CLIPFILE} | while read clipline ; do
if [ $y -eq 0 ] ; then
[ ${DEBUG} ] && \
echo "CLIP HEAD ${line}" >> /dev/stderr
elif [ $y -ge 1 ] ; then
TEST=$((${CLIPLINES[$(($y - 1))]:1} + ${POSUN}))
echo ${TEST} > ${TEMP_DIR}/last_time
echo ${clipline} | ${SED} "s/:digit:\+.:digit:\+/$(float_from_string ${TEST})/"
fi
y=$(($y + 1))
done
echo "---- AAA ${CLIPFILE} add before first event of the original ascii.cast AAA ----" >> /dev/stderr
shift time the first event of the original ascii.cast
POSUN=$(($(head -1 ${TEMP_DIR}/last_time) - ${POSUN} + ${BEFORE} + ${SHIFT}))
[ $(head -1 ${TEMP_DIR}/last_time) -gt ${POSUN} ] && POSUN=$(head -1 ${TEMP_DIR}/last_time)
echo ${line} | ${SED} "s/:digit:\+.:digit:\+/$(float_from_string ${POSUN})/"
elif [ ${LINE} -eq $((${#LINES[@]} + 1)) ] ; then
Add after last event of the original ascii.cast
echo ${line}
[ ${START} ] && POSUN=$(float_to_string ${START}) || POSUN=$(float_to_string ${TIME})
echo "---- VVV ${CLIPFILE} append after last event of the original ascii.cast VVV ----" >> /dev/stderr
add new marker
cat ${TEMP_DIR}/clip.marker
y=0
TEST=0
${SED} 's/\\/\\\\/g' ${CLIPFILE} | while read clipline ; do
if [ $y -eq 0 ] ; then
[ ${DEBUG} ] && \
echo "CLIP HEAD ${line}" >> /dev/stderr
elif [ $y -ge 1 ] ; then
TEST=$((${CLIPLINES[$(($y - 1))]:1} + ${POSUN}))
echo ${TEST} > ${TEMP_DIR}/last_time
echo ${clipline} | ${SED} "s/:digit:\+.:digit:\+/$(float_from_string ${TEST})/"
fi
y=$(($y + 1))
done
POSUN=$(($(head -1 ${TEMP_DIR}/last_time) - ${POSUN} + ${BEFORE} + ${SHIFT}))
echo "---- AAA ${CLIPFILE} append after last event of the original ascii.cast AAA ----" >> /dev/stderr
else
to middle
[ ${START} ] && POSUN=$(float_to_string ${START}) || POSUN=$(float_to_string ${TIME})
echo "---- VVV Include ${CLIPFILE} to LINE ${LINE} with TIME ${TIME} VVV ----" >> /dev/stderr
add new marker
cat ${TEMP_DIR}/clip.marker
y=0
TEST=0
${SED} 's/\\/\\\\/g' ${CLIPFILE} | while read clipline ; do
if [ $y -eq 0 ] ; then
[ ${DEBUG} ] && \
echo "CLIP HEAD ${line}" >> /dev/stderr
elif [ $y -ge 1 ] ; then
TEST=$((${CLIPLINES[$(($y - 1))]:1} + ${POSUN}))
echo ${TEST} > ${TEMP_DIR}/last_time
echo ${clipline} | ${SED} "s/:digit:\+.:digit:\+/$(float_from_string ${TEST})/"
fi
y=$(($y + 1))
done
echo "---- AAA Include ${CLIPFILE} to LINE ${LINE} with TIME ${TIME} AAA ----" >> /dev/stderr
POSUN=$(($(head -1 ${TEMP_DIR}/last_time) - ${LINES[$(($x - 1))]:1} + ${BEFORE} + ${SHIFT}))
TIME=$((${LINES[$(($x - 1))]:1} + ${POSUN}))
[ ${TIME} -lt $(head -1 ${TEMP_DIR}/last_time) ] && \
POSUN=$(($(head -1 ${TEMP_DIR}/last_time) - ${LINES[$(($x - 1))]:1})) && \
TIME=$(head -1 ${TEMP_DIR}/last_time)
echo ${line} | ${SED} "s/:digit:\+.:digit:\+/$(float_from_string ${TIME})/"
fi
elif [ ${x} -gt $((${LINE} - 1)) ] ; then
echo vkládám zbytek původního ascii.cast souboru
TIME=$((${LINES[$(($x - 1))]:1} + ${POSUN}))
echo ${line} | ${SED} "s/:digit:\+.:digit:\+/$(float_from_string ${TIME})/"
elif [ ${x} -eq 0 ] ; then
only for debug info
[ ${DEBUG} ] && \
echo "ORIGINAL HEAD ${line}" >> /dev/stderr
else
don't touch time of event
echo ${line}
fi
x=$(($x + 1))
done
else
new duration
float_from_string $((${LINES[-1]:1} + ${SHIFT} )) > ${TEMP_DIR}/duration
new timestamp
echo $(date +%s) > ${TEMP_DIR}/timestamp
header_print
x=0
${SED} 's/\\/\\\\/g' ${RECORDFILE} | while read line ; do
if [ ${x} -ge $((${LINE} - 1)) ] ; then
TIME=$((${LINES[$(($x - 1))]:1} + ${SHIFT}))
echo ${line} | ${SED} "s/:digit:\+.:digit:\+/$(float_from_string ${TIME})/"
elif [ ${x} -eq 0 ] ; then
only for debug info
[ ${DEBUG} ] && \
echo "ORIGINAL HEAD ${line}" >> /dev/stderr
else
don't touch time of event
echo ${line}
fi
x=$(($x + 1))
done
if [ ${SHIFT/-/} == ${SHIFT} ] ; then
echo "INFO: include_clip() - Waiting to show event from line ${LINE} was extended. Next events time was shifted by $(float_from_string ${SHIFT}) seconds forward." >> /dev/stderr
else
echo "INFO: include_clip() - The waiting interval to show event from line ${LINE} has been shortened. The next events timing has been shifted by -$(float_from_string ${SHIFT:1}) seconds to back." >> /dev/stderr
fi
fi
return
}
Tahle funkce exportuje klipy, které se dají samostatně přehrávat, protože jsou také v asciicast formátu, jako výchozí záznam.
Akceptuje tyto parametry:
$ asciiframe clip ascii.cast 1:00 -0.5
Výše uvedený příkaz vyexportuje do klipu veškeré události od času 60.000000 sec. až před poslední půlsekundu před koncem záznamu.
Proto, chcete-li vymezit časový interval, můžete použí čas ve formátu HH:MM:SS. Viz následující příklad, kterým se doklipu vyexportují události od času 60.000000 sec. po čas 90.0000000 sec. od startu:
$ asciiframe clip ascii.cast 1:00 1:30
Akci clip lze využít i přehrání učitého časového úseku záznamu. Aplikace asciinema totiž sama o sobě přehrávat záznam od určitého času neumí. Následující ukázka demonstruje jak na to.
1. možnost je vyexportovat úsek, který nás zajímá do souboru
clip.cast. Přesměrování je dležité, protože bez něj bychom
ztratili přehled o kontextu předchozích událostí. Ty se totiž posílají
při exportu klipu do /dev/null resp. na
stderr s časem spuštění 0.0
$ asciiframe clip ascii.cast 1.23 1:00 > clip.cast 2>&1 $ asciinema play clip.cast
2. možností, kterou máte je přesměrování výstupu rovnou do přehrávače asciinema, jenže ten má poměrně omezné možnosti. $ asciiframe clip ascii.cast 1.23 1:00 > /dev/stdout 2>&1 | asciinema play -
3. proto mají uživatelé linuxových desktopů k dispozici ještě třetí možnost – přesměrování na přehráč spuštěný přes asciiframe:
$ asciiframe clip ascii.cast 1.23 1:00 play 0:05
Čas uvedený za příkazem play je velikost pauzy, mezi zobrazením předcházejících zpráv a a událostmi klipu.
Ǔčelem této pauzay je identifikace událostí, které mají být součástí úvodního „screenshotu” – pokud ho budete chtít udělat, aplikujte na příslušný úsek nejprve akci split a když zjistíte kterou událostí musí „screenshot” aplikujte akci merge s číslem řádku události, kterou screenshot začíná a číslem řádky události, která ho ukončí.
Důležitá poznámka
Akce merge operuje přímo s obsahem výchozího souboru, zatímco coclip exportuje události na stdout, který lze přesměrovat do samostatného klipu a ten naimportovat až po případných úpravách, což výrazně snižuje riziko poškození výchozího souboru.
TODO - je třeba ošetřit parametr ${2} (INTERVAL), který může být předán ve formátu HH:MM:SS, nebo jako kladné či záporné číslo v sekundách
clip_print () {
[ ${DEBUG} ] && \
echo "INFO: clip_print() – $1 ; $2 ; $3" >> /dev/stderr
local LINE
local END
local ENDLINE
local START
local TIME
case $(value ${1}) in
flow) START=$(line_by_time ${1})
[ $? -eq 1 ] && \
echo "ERROR: clip_print() - Time ${1} is out of the rang events in ${RECORDFILE}" >> /dev/stderr && \
return 1
[ ${DEBUG} ] && \
echo "Pro události do času $1 se nastaví čas 0" >> dev/stderr
;;
inte) LINE=$(line_by_inte ${1})
[ $? -eq 1 ] && \
echo "ERROR: clip_print() - Value ${1} is out of the rang events of ${RECORDFILE}" >> /dev/stderr && \
return 1
START=$((${LINE} - 1))
[ ${DEBUG} ] && \
echo "Pro události do řádku ${START} se nastaví čas 0.0" >> /dev/stderr
;;
rang) TEST=(${1/-/ })
LINE=$(line_by_inte ${TEST[0]})
[ $? -eq 1 ] && \
echo ${LINE} && \
return 1
ENDLINE=$(line_by_inte ${TEST[1]})
[ $? -eq 1 ] && \
echo ${ENDLINE} && \
return 1
[ ${DEBUG} ] && \
echo "INFO: do_event() ${1} by scope from ${LINE} to ${ENDLINE}" >> /dev/stderr
START=$((${LINE} - 1))
END=${ENDLINE}
;;
time) START=$(line_by_time $(print_seconds ${1}))
[ $? -eq 1 ] && \
echo "ERROR: clip_print() - Time ${1} is out of the rang events in ${RECORDFILE}" >> /dev/stderr && \
return 1
[ ${DEBUG} ] && \
echo "Pro události do času $1 se nastaví čas 0" >> dev/stderr
;;
esac
SHIFT=$(( $(time_by_line ${START}) - 1100000 ))
OUT="/dev/stdout"
PAUSE=1
echo "Parameter ${2} SHIFT ${SHIFT}" >> /dev/stderr
if [ ${2} ] ; then
case $(value ${2}) in
flow) [ ${DEBUG} ] && \
echo "INFO: clip_print() - End of rang is set by shift time ${2} from actually time of line ${START}" >> dev/stderr
;;
inte) [ ${DEBUG} ] && \
echo "INFO: clip_print() - End of rang is set by count of lines from actually line ${START}" >> /dev/stderr
;;
nint) [ ${DEBUG} ] && \
echo "INFO: clip_print() - End of rang is set by count of lines (${2}) from end of file" >> /dev/stderr
;;
nflo) [ ${DEBUG} ] && \
echo "INFO: clip_print() - End of rang is set by time end ${2} seconds before end of file" >> /dev/stderr
;;
stri) case ${2} in
play) [ ${DEBUG} ] && \
echo "INFO: clip_print() - Play from START line ${START} to end of file" >> /dev/stderr
PLAY=yes
[ -z ${3} ] && PAUSE=3000000 || PAUSE=$(print_seconds ${3})
OUT=${TEMP_DIR}/clip.cast
;;
clip) CLIP=yes
;;
esac
;;
esac
fi
[ -z ${END} ] && \
END=$(( ${#LINES[@]} + 1 ))
header_print >> ${OUT}
x=0
cat ${RECORDFILE} | sed 's/\\/\\\\/g' | while read line ; do
((x++))
#echo ${x} >> /dev/stderr
if [ ${x} -eq 1 ] ; then
echo "${line}" >> /dev/null
elif [ ${x} -le ${START} ] ; then
#echo "sypu jeden čas" >> /dev/stderr
[ ${CLIP} ] && echo ${line} >> /dev/null || echo ${line} | sed 's/^\[[0-9.]\+,/\[0.0,/' >> ${OUT}
elif [ ${x} -gt ${END} ] ; then
echo "sypu do" >> /dev/null
else
#echo "recount čas" >> /dev/stderr
TIME=$(float_strip $(float_from_string $((${LINES[$(($x-2))]:1} - ${SHIFT} + ${PAUSE}))))
#echo "${line}" >> /dev/stderr
echo ${line} | sed 's/^\[[0-9.]\+,/\['${TIME}',/' >> ${OUT}
fi
done
echo "INFO: clip_print() - Clip from START ${START} to END ${END} used SHIFT ${SHIFT}" >> /dev/stderr
[ ${PLAY} ] && asciiframe play ${OUT}
return
}
Funkce zobrazuje kontext jedné události z řádku $1 Při zpracování volá pomocné funkce:
context_show () {
[ ${DEBUG} ] && \
TEST=$(line_by_inte "${1}") || \
TEST=$(line_by_inte "${1}" 2>/dev/null)
[ $? -eq 1 ] && \
echo "ERROR: $FUNCNAME() - line ${1} is out of events the ${RECORDFILE} file" >> /dev/stderr && \
return 1
[ ${TEST} -eq 1 ] && \
echo "ERROR: $FUNCNAME() - line ${TEST} is the header of the ${RECORDFILE} file" >> /dev/stderr && \
return 1
CONTEXT=$(get_context ${TEST})
[ -z ${CONTEXT} ] && \
echo "ERROR: $FUNCNAME() - No context for ${1}. Probably is out of rang events from ${RECORDFILE}" >> /dev/stderr && \
return 1
EVENT=(${CONTEXT//\;/ })
PREV=(${EVENT[0]//\:/ })
ITEM=(${EVENT[1]//\:/ })
NEXT=(${EVENT[2]//\:/ })
[ ${DEBUG} ] && \
echo "INFO: $FUNCNAME() 'context': PREV ${PREV[@]}" >> /dev/stderr
[ ${DEBUG} ] && \
echo "INFO: $FUNCNAME() 'context': ITEM ${ITEM[@]}" >> /dev/stderr
[ ${DEBUG} ] && \
echo "INFO: $FUNCNAME() 'context': NEXT ${NEXT[@]}" >> /dev/stderr
ITEM[2]=$((${ITEM[1]} - ${PREV[1]}))
#echo ${ITEM[2]} >> /dev/stderr
if [ ${ITEM[2]} -eq ${ITEM[1]} ] ; then
[ ${DEBUG} ] && \
echo "INFO: $FUNCNAME() 'context' - Event from line ${NEXT[0]} and ${ITEM[0]} showed in same time" >> /dev/stderr
elif [ ${ITEM[2]} -lt 1 ] ; then
[ ${DEBUG} ] && \
echo "WARN: $FUNCNAME() 'context' - Line ${NEXT[0]}: $(float_from_string ${NEXT[1]}) started before then event from line ${ITEM[0]}: $(float_from_string ${ITEM[1]})" >> /dev/stderr
fi
if [ ${NEXT[0]} -eq 0 ] ; then
printf "%s (%s - %s)\n" \
"$(float_from_string ${ITEM[2]})" \
"$(float_from_string ${ITEM[2]})" \
"$(float_from_string 0)"
return
else
if [ ${NEXT[1]} -gt 0 ] ; then
NEXT[2]=$((${NEXT[1]} - ${ITEM[1]}))
if [ ${NEXT[2]} -lt 0 ] ; then
echo "WARN: $FUNCNAME() 'context': Line ${NEXT[0]}: $(float_from_string ${NEXT[1]}) started before then event from line ${ITEM[0]}: $(float_from_string ${ITEM[1]})" >> /dev/stderr
return 1
else
printf "%s (%s - %s)\n" \
"$(float_from_string $((${ITEM[2]} + ${NEXT[2]})))" \
"$(float_from_string ${ITEM[2]})" \
"$(float_from_string ${NEXT[2]})"
fi
else
předposlední event
PREV[2]=$((${ITEM[1]} - ${PREV[1]}))
if [ ${PREV[2]} -lt 0 ] ; then
echo "WARN: $FUNCNAME() 'context': Line ${ITEM[0]}: $(float_from_string ${ITEM[1]}) started before then event from line ${PREV[0]}: $(float_from_string ${PREV[1]})" >> /dev/stderr
return 1
else
printf "%s (%s - %s)\n" \
"$(float_from_string ${ITEM[2]})" \
"$(float_from_string ${ITEM[2]})" \
"$(float_from_string 0)"
fi
fi
fi
return
}
Funkce vrací kontext jedné události na řádku $1
get_context () {
[ ${DEBUG} ] && \
echo "CALL: $FUNCNAME() - ${1} - ${2}" >> /dev/stderr
local BEFORELINE
local BEFORETIME
local NEXTLINE
local NEXTTIME
local LINE
local TIME
local TEST
case $(value ${1}) in
flow|nflo|time) TEST=$(line_by_time ${1})
[ $? -eq 1 ] && \
echo "ERROR: $FUNCNAME() - Time ${1} out of rang events ${RECORDFILE}" >> /dev/stderr && \
return 1
;;
inte|nint) TEST=$(line_by_inte ${1})
[ $? -eq 1 ] && \
echo "ERROR: $FUNCNAME() - Line ${1} out of rang events ${RECORDFILE}" >> /dev/stderr && \
return 1
;;
esac
if [ ${TEST} -eq 1 ] ; then
BEFORELINE=0
BEFORETIME=0
LINE=1
TIME=0
NEXTLINE=2
NEXTTIME=${LINES[0]:1}
elif [ ${TEST} -eq 2 ] ; then
BEFORELINE=1
BEFORETIME=0
LINE=2
TIME=${LINES[0]:1}
NEXTLINE=3
NEXTTIME=${LINES[1]:1}
else
LINE=${TEST}
TIME=${LINES[$((${TEST}-2))]:1}
BEFORELINE=$((${TEST}-1))
BEFORETIME=${LINES[$((${TEST}-3))]:1}
NEXTLINE=$((${TEST}+1))
NEXTTIME=${LINES[$((${TEST}-1))]:1}
[ ${DEBUG} ] && \
echo "DEBUG: $FUNCNAME() LINE ${LINE} TIME ${TIME}" >> /dev/stderr
[ ${DEBUG} ] && \
echo "DEBUG: $FUNCNAME() BEFORELINE ${BEFORELINE} BEFORETIME ${BEFORETIME}" >> /dev/stderr
[ ${DEBUG} ] && \
echo "DEBUG: $FUNCNAME() NEXTLINE ${NEXTLINE} NEXTTIME ${NEXTTIME}" >> /dev/stderr
if [ -z ${NEXTTIME} ] ; then
NEXTLINE=0
NEXTTIME=0
fi
fi
[ ${DEBUG} ] && \
echo "RETURN: $FUNCNAME() - $BEFORELINE:$BEFORETIME;$LINE:$TIME;${NEXTLINE}:${NEXTTIME}" >> /dev/stderr
echo "$BEFORELINE:$BEFORETIME;$LINE:${TIME};${NEXTLINE}:${NEXTTIME}"
return
}
Funkce převádí textový řetězec, předaný v parametru $1 na číslo typu float. Tedy číslo, které používá jako desetinnou čárku tečku.
Předpokládá, že jde o kladné celé číslo typu integer, co
udává délku časového intervalu v mikrosekundách. Je-li předaná hodnota
0, vrátí se řetězec 0.000000
Kdybychom to počítali přec bc, trvalo by zpracování jednoho čísla 11-14 tisícin sekundy, kdežto této funkci to trvá jen 5-6 tisícin sekundy
float_from_string () {
[ ${DEBUG} ] && \
echo "CALL: $FUNCNAME() - ${1}" >> /dev/stderr
local FLOAT
local LENGTH
local STRING
STRING=${1}
case $(value ${STRING}) in
inte)
;;
*) echo "ERROR: float_from_string() not accept $(value ${1}) ${1}" >> /dev/stderr
return 1
;;
esac
LENGTH=${#STRING}
case ${#STRING} in
1) FLOAT="0.00000${STRING}";;
2) FLOAT="0.0000${STRING}" ;;
3) FLOAT="0.000${STRING}" ;;
4) FLOAT="0.00${STRING}" ;;
5) FLOAT="0.0${STRING}" ;;
6) FLOAT="0.${STRING}" ;;
*) FLOAT="${STRING:0:$(( ${LENGTH} - 6 ))}.${STRING:$(( ${LENGTH} - 6 )):6}"
;;
esac
[ ${DEBUG} ] && \
echo "RETURN: $FUNCNAME()- FLOAT ${FLOAT} (send to strip)" >> /dev/stderr
echo $(float_strip ${FLOAT})
return
}
Funkce vychází z toho, že textový řetězec, předaný v parametru $1 je číslo typu float. Tedy číslo, které používá jako desetinnou čárku tečku. Za ní může následovat blíže neznámý počet desetiných míst, ale během přehrávání asciicastového záznamu nehraje roli je-li interval mezi událostmi kratší než 1 mikrosekunda – proto stačí zpracovat jen následujících 6 desetiných míst.
Maximální počet mikrosekund limitován není, ale délka vašeho záznamu
pravděpodobně nikdy nebude větší než 9999999999
mikrosekund, tedy 2 hodiny 46 minut a 40 sekund.
float_to_string () {
[ ${DEBUG} ] && \
echo "CALL: $FUNCNAME() - ${1}" >> /dev/stderr
local FLOAT
local SUFF
local INT
local ZATECKOU
[ "${1//0/}" == "." ] && echo "0" || FLOAT=${1}
SUFF=${FLOAT#*.}
[ ${#SUFF} -gt 6 ] && SUFF=${SUFF:0:6}
INT=${FLOAT%.*}
ZATECKOU=${#SUFF}
if [ "${INT}" == "0" ] ; then
INT=""
if [ "${SUFF:0:5}" -eq "00000" ] ; then
SUFF=${SUFF:5}
elif [ "${SUFF:0:4}" -eq "0000" ] ; then
SUFF=${SUFF:4}
elif [ "${SUFF:0:3}" -eq "000" ] ; then
SUFF=${SUFF:3}
elif [ "${SUFF:0:2}" -eq "00" ] ; then
SUFF=${SUFF:2}
elif [ "${SUFF:0:1}" -eq "0" ] ; then
SUFF=${SUFF:1}
fi
fi
case "${ZATECKOU}" in
1) FLOAT="${INT}${SUFF}00000";;
2) FLOAT="${INT}${SUFF}0000" ;;
3) FLOAT="${INT}${SUFF}000" ;;
4) FLOAT="${INT}${SUFF}00" ;;
5) FLOAT="${INT}${SUFF}0" ;;
6) FLOAT="${INT}${SUFF}" ;;
esac
[ ${DEBUG} ] && \
echo "RETURN: $FUNCNAME() - FLOAT ${FLOAT}" >> /dev/stderr
echo ${FLOAT}
return
}
Ořezává nadbytečné nuly. Takže místo 0.000000 vrátí jen 0.0
float_strip () {
[ ${DEBUG} ] && \
echo "CALL: $FUNCNAME() - ${1}" >> /dev/stderr
local MODULO
local FLOAT
MODULO="${1#*.}"
case "${MODULO}" in
[0-9][0-9][0-9][0-9][0-9][1-9]) FLOAT="${1}" ;;
[0-9][0-9][0-9][0-9][1-9]0) FLOAT="${1%.*}.${MODULO:0:5}" ;;
[0-9][0-9][0-9][1-9]00) FLOAT="${1%.*}.${MODULO:0:4}" ;;
[0-9][0-9][1-9]000) FLOAT="${1%.*}.${MODULO:0:3}" ;;
[0-9][1-9]0000) FLOAT="${1%.*}.${MODULO:0:2}" ;;
[0-9]00000) FLOAT="${1%.*}.${MODULO:0:1}" ;;
esac
if [ ${FLOAT} ] ; then
[ ${DEBUG} ] && \
echo "RETURN: $FUNCNAME() - FLOAT ${FLOAT}" >> /dev/stderr
echo ${FLOAT}
else
[ ${DEBUG} ] && \
echo "RETURN: $FUNCNAME() - original value ${1}" >> /dev/stderr
echo ${1}
fi
return
}
Přijme číslo typu float (čas), převede ho na řetězec a pomocí funkce float_strip() ořeže nadbytečné nuly.
Takže místo čísla 1.000000 vrátí jen 1.0, čas zapsaný jako 1.2300 upraví na 1.23
Funkce je nezbytná pokud se zapisuje čas do asciicastu. Tomu sice nadbytečné nuly nevadí, ale komplikuje to aktualizace značek
float_unify () {
[ ${DEBUG} ] && \
echo "INFO: float_unify() ${1}" >> /dev/stderr
float_strip $(float_from_string $(float_to_string ${1}))
return
}
Pokud číslo typu integer, může být řádkem události, vrátí se zpět. V opačném případě je návratový kód 1.
Je-li číslo záporné, předpokládá se, že jde o řádek události od konce záznamu. Tedy za předpokladu, že má záznam 15 událostí, vrátí
line_by_inte -5
hodnotu 11 = (15 + 1 hlavička) - 5.
line_by_inte -0
Vrátí 16 = (15 + 1 hlavička), ale
line_by_inte 0
nebo
line_by_inte -16
skončí chybou, protože nic jako řádek 0 neexistuje a celkový počet
událostí s řádkem na kterém je hlavička je 16
(16 - 16 = 0)
line_by_inte () {
[ ${DEBUG} ] &&
echo "CALL: $FUNCNAME() - ${1}" >> /dev/stderr
local TEST
local ENDLINE
ENDLINE=$((${#LINES[@]} + 1))
case $(value ${1}) in
nflo|flow|time) echo "ERROR: line_by_inte() not accept flow. You must use line_by_time()" >> /dev/stderr
return 1
;;
inte) [ "${1}" == "0" ] || [ "${1}" -gt "${ENDLINE}" ] && \
echo "ERROR: line_by_inte() - Line ${1} is out of file, line ${ENDLINE} is the last event" && \
return 1
echo "${1}"
;;
nint) TEST=$((${ENDLINE} + ${1}))
[ "${TEST}" -lt 1 ] && echo "ERR: Parameter ${1} out of range" && return 1
echo "${TEST}"
;;
*) echo "ERROR: line_by_inte() - Allowed type is only inte or nint. ${1} is type $(value ${1})." >> /dev/stderr
return 1
;;
esac
}
Funkce by se měla použít všude tam, kde je potřeba na základě času,
ať již v „human-ready” formě (23:45:01) nebo v sekundách
(0.987654) zjistit číslo řádku události, která se spustí
po tomto čase (je-li číslo kladné, typu
float). Nebo události, která se spustí určitý čas před koncem
záznamu – bude-li číslo typu float záporné. Akceptovány jsou
tedy následující varianty:
line_by_time 1:23 # vrátí řádek události co se spustí v čase 83.000000
line_by_time 1.23 # vrátí řádek události co se spustí v čase 1.230000
nebo
line_by_time -0.000010
..která vrátí událost, co se spustí během posledních 10 mikrosekund před koncem záznamu
line_by_time () {
[ ${DEBUG} ] &&
echo "INFO: line_by_time(): \$1:\"$1\"" >> /dev/stderr
local i
local TEST
local INTERVAL
local ENDLINE
local ENDTIME
ENDLINE=$((${#LINES[@]} + 1))
ENDTIME="${LINES[$((${ENDLINE}-2))]:1}"
case $(value ${1}) in
inte|nint) echo "ERROR: line_by_time() - For ínteger use line_by_inte()" >> /dev/stderr
return 1
;;
rang) echo "ERROR: line_by_time() - For rang use lines_by_rang()" >> /dev/stderr
return 1
;;
flow|time) if [ "${1//[0-9]/}" == ":" ] ; then
1:23 => 83.000
INTERVAL=$(float_to_string $(print_seconds ${1}))
else
INTERVAL=$(float_to_string ${1})
fi
[ ${DEBUG} ] && \
echo "INFO: line_by_time() : Get event which started after ${INTERVAL} sec. from start" >> /dev/stderr
i=${ENDLINE}
[ ${DEBUG} ] && \
echo "INFO: line_by_time() : TEST ${ENDTIME} - ${INTERVAL}" >> /dev/stderr
Pokud odpovídá interval času poslední události, zahrnuje celý soubor
[ "${ENDTIME}" == "${INTERVAL}" ] && \
echo "${ENDLINE}" && \
return
TEST=$((${ENDTIME} - ${INTERVAL}))
Záporný výsledek indikuje že byl předán interval větší než je celkový čas
[ ! -z "${TEST//[0-9]/}" ] && \
echo "ERROR: line_by_time(): Value ${1} is out of range. Last event on line ${ENDLINE} start in $(float_to_string ${ENDTIME}) sec." && \
return 1
[ ${DEBUG} ] && \
echo "INFO: line_by_time(): "${LINES[@]} |\
${SED} 's/\x20/\n/g' |\
tac >> /dev/stderr
i=${ENDLINE}
echo "${LINES[@]}" |\
${SED} 's/\x20/\n/g' |\
tac |\
while read line ; do
[ ${DEBUG} ] && \
echo "INFO: line_by_time(): ${1} - ${i} - ${INTERVAL} - ${line}" >> /dev/stderr
[ "${line:1}" -le "${INTERVAL}" ] && \
echo "${i}" && \
return || \
((i-=1))
done
return
;;
nflo) INTERVAL=$(float_to_string ${1:1})
[ ${DEBUG} ] && \
echo "INFO: line_by_time() : Get event which started ${INTERVAL} sec. before last event time" >> /dev/stderr
i=${ENDLINE}
[ ${DEBUG} ] && \
echo "INFO: line_by_time() : ${1} : ${ENDTIME} - ${INTERVAL}" >> /dev/stderr
Pokud odpovídá interval času poslední události, zahrnuje celý soubor
[ "${ENDTIME}" == "${INTERVAL}" ] && \
echo "1" && \
return
TEST=$((${ENDTIME} - ${INTERVAL}))
Záporný výsledek indikuje že byl předán interval větší než je celkový čas
[ ! -z "${TEST//[0-9]/}" ] && \
echo "ERROR: line_by_time(): Value ${1} is out of range. Last event on line ${ENDLINE} start in $(float_to_string ${ENDTIME}) sec." && \
return 1
[ ${DEBUG} ] && \
echo "${LINES[@]}" |\
${SED} 's/\x20/\n/g' |\
tac >> /dev/stderr
echo "${LINES[@]}" |\
${SED} 's/\x20/\n/g' |\
tac |\
while read line ; do
[ "${line:1}" -lt "${TEST}" ] && \
[ "${line:1}" -eq "${TEST}" ] && \
echo "${i}" && \
return
((i-=1))
[ ${DEBUG} ] && \
echo "${line:1} - ${i}" >> /dev/stderr
[ "${TEST}" -gt "${line:1}" ] && \
echo "${i}" && \
return
done
echo "${i}"
return
;;
*) echo "ERROR: line_by_time() not accept type $(value ${1}): flow,nflo and time allowed" >> /dev/stderr
return 1
;;
esac
}
Tahle funkce, na rozdíl od funkcí line_by_inte() a line_by_time() může vracet více než jednu hodnotu. Podle toho, jak vypadá předaný rozsah, ale rozsah, u kterého bude druhá hodnota nižší než první se vždy vyhodnotí jako chybný!
Syntaxe rozsahu je:
[TIME:]START-END[:INTERVAL]
TIME, čas spuštění události, ani čas INTERVALu v něm přítomny být
nemusí, ale znak mínus (-) mezi celými čísly typu
integer, které odpovídají řádkům, musí být přítomen
vždy!. START musí být menší, nebo stejné číslo jako END, větší
než 0.
5-4 # neplatný rozsah
0-2 # neplatný rozsah
4-5 # OK, vrátí hodnoty 4 a 5
4-4 # OK, vrátí hodnotu 4
Rozsah může obsahovat také časy, oddělené dvojtečkami:
1.23:4-5:6.78
Jako první se uvádí TIME - čas spuštění události. V takovém případě funkce ověří, zda-li je uvedený čas pro daný rozsah platný. Rozsahy se totiž používají pouze pro série řádků se stejným časem. Vypomůže si funkcí #line_by_time, by měla vrátit číslo řádku, kterým rozsah začíná.
Naopak druhá hodnota udává čas, kdy má následovat událost, která už není součástí intervalu. Zjistí podle času 1.23 číslo řádku události a pokud bude
1.23:4-5
TODO
line_by_rang () {
[ ${DEBUG} ] &&
echo "INFO: line_by_rang(): \$1:\"$1\"" >> /dev/stderr
local RANG
if [ "${1//:/}" == "${1}" ] ; then
echo "plain rang"
echo "4-5" >> /dev/stderr
echo TODO
case "$(value ${1})" in
time|flow) echo "INFO: line_by_rang() return lines if rang START on TIME ${i} sec."
echo TODO
;;
nflo|nint) echo "ERROR: line_by_rang() - Invalid rang ${1}" >> /dev/stderr
return 1
;;
inte) echo "INFO: line_by_rang() - Return rang lines if ${1} line is from RANG" >> /dev/stderr
;;
rang)
echo "INFO: line_by_rang() - Return rang lines 4-5 without check time" >> /dev/stderr
echo TODO
;;
*) echo "ERROR: line_by_rang() not accept type $(value ${1}). Allowed only rang" >> /dev/stderr
return 1
;;
esac
else
RANG=(${1//:/ })
case "${#RANG[@]}" in
1) echo "ERROR: line_by_rang() - For test value $(value ${1}) use line_by_time()" >> /dev/stderr
return 1
;;
2) echo "INFO: line_by_rang() - Test time ${RANG[@]}" >> /dev/stderr
echo "1.23:4 nebo 1.23:4-5 nebo 4-5:6.75 nebo 5:6.75" >> /dev/stderr
echo TODO
;;
3) echo "INFO: line_by_rang() - Full test ${RANG[@]}" >> /dev/stderr
echo "1.23:4-5:6.75" >> /dev/stderr
echo TODO
;;
*) echo "ERROR: line_by_rang() ${RANG[@]} not accept type $(value ${1}). Allowed only rang" >> /dev/stderr
return 1
;;
esac
fi
}
Zalamuje dlouhé řádky do bloku, podle aktuální velikosti terminálu
lineblock()
{
[ ${DEBUG} ] && \
echo "INFO: lineblock() ${1}" >> /dev/null
if [ -z "${VIEW}" ] ; then
sed -n ${1}p ${RECORDFILE}
else
sed -n ${1}p ${RECORDFILE} | fold -s -w $((${COLUMNS} - 20)) | sed '/^[^[{]/s/^/\t\|\t/'
fi
}
Vypíše na standardní výstup obsah řádku, na kterém je zobrazovaná
událost, typu o (out)
line_o () {
[ ! -z "${VIEW}" ] && \
printf "[1;39m%4d:[0m[1;30m%4d:[0m " ${1} ${2}
lineblock ${1}
[ ! -z "${VIEW}" ] && \
printf "[0m"
return
}
Vypíše na standardní výstup obsah hlavičku asciicastu
line_h () {
if [ -z "${VIEW}" ] ; then
H={"version": 2
H=${H},"width":${ORIGWIDTH}
H=${H},"height":${ORIGHEIGHT}
H=${H},"timestamp":${ORIGTIME}
H=${H},"title": "
if [ -z ${TITLE} ] ; then
H=${H}"${ORIGTITLE}"
else
H=${H}"${TITLE}"
fi
H=${H}","duration":$(float_from_string ${DURATION})
[ ! -z ${IDLE} ] && \
H=${H},"idle_time_limit":${IDLE}
if [ ! -z ${FG} ] || \
[ ! -z ${BG} ] || \
[ ! -z ${PALLETE} ] ; then
echo theme >> /dev/null
#ORIGFG
#ORIGBG
#ORIGPALLETE
fi
H=${H},"env": {"SHELL":${ORIGSHELL}
H=${H},"TERM":${ORIGTERM}
H=${H}}}
echo ${H}
else
H=[1;39m{[0m\n\t\t[1;34m"version"[0m:\t2
H=${H},\n\t\t[1;34m"width"[0m:\t[0;33m${ORIGWIDTH}
H=${H}[0m,\n\t\t[1;34m"height"[0m:\t[0;33m${ORIGHEIGHT}
H=${H}[0m,\n\t\t[1;34m"timestamp"[0m:\t[0;33m$(date +%s)
H=${H}[0m,\n\t\t[1;35m"title"[0m:\t"[0;33m
if [ -z ${TITLE} ] ; then
H=${H}"${RECORDFILE##*/}"
else
H=${H}"${TITLE}"
fi
H=${H}[0m",\n\t\t[1;35m"duration"[0m:\t[0;33m$(float_from_string ${DURATION})
[ ! -z ${IDLE} ] && \
H=${H}[0m,\n\t\t"idle_time_limit":\t${IDLE}
if [ ! -z ${FG} ] || \
[ ! -z ${BG} ] || \
[ ! -z ${PALLETE} ] ; then
echo theme >> /dev/null
#ORIGFG
#ORIGBG
#ORIGPALLETE
fi
H=${H}[0m,\n\t\t"env": [1;39m{[0m\n\t\t\t[1;34m"SHELL"[0m: [0;32m${ORIGSHELL}
H=${H}[0m,\n\t\t\t[1;34m"TERM"[0m: [0;32m${ORIGTERM}
H=${H}[0m\n\t\t[1;39m}[0m\n\t[1;39m}[0m
printf "[1;39m%4d:[0m[1;33m%4d:[0m " 1 1
echo -e ${H}
printf "[0m"
fi
return
}
Vypíše na standardní výstup obsah řádku, na kterém je událost typu
m (značka - marker)
line_m () {
[ ! -z "${VIEW}" ] && \
printf "[1;39m%4d:[0m[1;31m%4d: " ${1} ${2}
lineblock ${1}
[ ! -z "${VIEW}" ] && \
echo -n "[0m" >> /dev/stderr
return
}
Vypíše na standardní výstup obsah řádku, na kterém je událost typu
r (resize)
line_r () {
[ ! -z "${VIEW}" ] && printf "[1;39m%4d:[0m[1;32m%4d: " ${1} ${2}
lineblock ${1}
[ ! -z "${VIEW}" ] && echo -n "[0m"
return
}
Vypíše série událostí, se stejným časem, ve formátu rang
0.051663:0-60
^ | \
| \ řádek poslední události
\ řádek první události
čas událostí
get_scope () {
[ ${DEBUG} ] && echo "INFO: get_scope()" >> /dev/stderr
for i in ${!LINES[@]} ; do
TIME="${LINES[${i}]:1}"
NEXT="${LINES[$((${i}+1))]:1}"
[ -z ${NEXT} ] && return
if [ "${TIME}" -eq "${NEXT}" ] ; then
[ -z "${FRAME}" ] && FRAME="$(float_from_string ${TIME}):$((${i}+2))"
else
[ ${FRAME} ] && echo "${FRAME}-$((${i}+2))" && FRAME=''
fi
done
return
}
Vypíše číslo řádku na kterém je uveden přesný čas
get_line () {
[ ! -z "${VIEW}" ] && printf "[1;39m%4d:[0m[1;32m%4d: " ${1} ${2}
lineblock ${1}
[ ! -z "${VIEW}" ] && echo -n "[0m"
grep -n -m 1 "^\[${1},\ " ${RECORDFILE}
return
}
Tato funkce, volaná při akci play není plnohodnotným přehrávačem asciicastu, protože viditelná změna je pozorovatelná až u časového intervalu na úrovni čtvrtého řádu (stovky mikrosekund).
Jak dlouhé jsou intervaly u vašeho záznamu, můžete velice snadno zjistit, použijete na vybranou sekvenci událostí ve smyčce příkaz context:
~$ for i in {34..69} ;\
do asciiframe view comment.cast context $i \
done 2>/dev/null | sort -n
0.241604 (0.110383 - 0.131221)
0.272310 (0.094456 - 0.177854)
...
Po setřídění dle hodnoty intervalu, který události předcházel (první číslo v závorce):
...
done 2>/dev/null | awk -F\( '{print $2}' | awk '{print $1}' | sort -n
0.091274
...
1.695180
Zjistíte, že se pohybuje od 0.1 do 0.3 sekundy. Kratší, nebo naopak delší interval už signalizuje menší zaváhání pisatele.
Načítá postupně jednotlivé položky z pole [[#$LINES|${LINES[@]}]] a výstup posílá na PTS terminály adresované parametrem $1 a $2'
playcast () {
echo '''
[layouts]
default
[window0]
type = Window
parent = ""
[child1]
type = Terminal
parent = window0
profile = minisize
''' > ${TEMP_DIR}/2-rows.json
#${TERMINATOR} -g ${TEMP_DIR}/2-rows.json &>/dev/null
TIME=0
for i in ${!LINES[@]} ; do
if [ $i -eq 0 ] ; then
echo >> /dev/null
else
LINE=$(sed -n $((i+1))p ${RECORDFILE})
TEXT="${LINE##*\", \"}"
FRAME="${TEXT%\"*}"
#echo "${FRAME}" >> /dev/stderr
#STRING=$((${#TEXT} - 2))
#CONTENT="${LINE:${#FRAME}:${STRING}}"
case ${LINES[${i}]:0:1} in
m)MARKER
printf "%s %12s – [1;39m%12s[0m\r" m $(float_from_string ${LINES[${i}]:1}) $(float_from_string ${LINES[$((${i} + 1))]:1}) > /dev/pts/${1}
;;
o)OUT
printf "%s %12s – [1;39m%12s[0m\r" o $(float_from_string ${LINES[${i}]:1}) $(float_from_string ${LINES[$((${i} + 1))]:1}) > /dev/pts/${1}
printf "${FRAME}" > /dev/pts/${2}
;;
r)RESIZE
printf "%s %12s – [1;39m%12s[0m\r" o $(float_from_string ${LINES[${i}]:1}) $(float_from_string ${LINES[$((${i} + 1))]:1})> /dev/pts/${1}
;;
esac
sleep $(float_from_string $((${LINES[${i}]:1} - ${TIME})))
TIME=${LINES[${i}]:1}
fi
done
return
}
Tahle funkce, kterou volá funkce get_view(), vypisuje na standardní výstup události, obarvené podle typu a doplněné kromě čísla řádku také pořadovým číslem.
m, o nebo
r a nepoviný parametr $2 může být
pořadové číslo události. Viz příklad:
print_event r 2
V tomto případě funkce vrátí řádek události r
(resize) která je v rámci souboru druhá v pořadí.
O samotný tisk událostí se starají funkce:
print_event () {
local PRINT
case ${1} in
m|o|r) PRINT="line_${1}" ;;
*) return
esac
z=0
for i in ${!LINES[@]} ; do
case ${LINES[$i]:0:1} in
${1}) ((z=z+1))
if [ -z ${2} ] ; then
VIEW="yes"
${PRINT} $(($i+2)) $z
else
if [ $z -eq ${2} ] ; then
VIEW="yes"
${PRINT} $(($i+2)) $z
break
fi
fi
;;
esac
done
return
}
TODO
print_monochrome () {
tac ${RECORDFILE} | sed 's/\\/\\\\/g' | while read line ; do
LINE=$(event_plain "${line}")
ITEM=${LINE::-2}
if [ "${ITEM}" == "${ITEM%\\u001b[K}" ] ; then
if [ ${BACK-0} -eq 0 ] ; then
echo "${LINE}"
else
TEST="${ITEM##*\"}"
case ${#TEST} in
1) BACK=$(($BACK - 1))
;;
*) echo BACKOVER >> /dev/stderr
echo ${TEST} >> /dev/stderr
;;
esac
fi
else
TEST="${ITEM%\\u001b[K}"
if [ "${TEST##*\"}" == '\b' ] ; then
BACK=$((${BACK} + 1))
else
echo "${LINE}"
fi
fi
done | tac | sed 's/\\/\\\\/g' | while read line ; do
Opakovaný cyklus je nutný z toho důvodu, že některé
speciální sekvence mohou být rozdělené přes dvě události,
takže nemusí být během prvního cyklu odchyceny
[ ! -z "${DEBUG}" ] && echo "${line}"
ITEM="${line::-2}"
if [ "${ITEM}" != "${ITEM%u001b}" ] ; then
SLOUCIT="${ITEM}"
[ ! -z "${DEBUG}" ] && echo "SPECIAL CHAR on END LINE" >> /dev/stderr
elif [ "${ITEM}" != "${ITEM%[0-9];}" ] ; then
SLOUCIT="${ITEM}"
[ ! -z "${DEBUG}" ] && echo "PROBABLY SPECIAL CHAR on END LINE" >> /dev/stderr
else
if [ -n "${SLOUCIT}" ] ; then
[ ! -z "${DEBUG}" ] && echo "CONCATE" >> /dev/stderr
event_plain "${SLOUCIT}${line##*,\ \"}"
[ ! -z "${DEBUG}" ] && echo "CONCATED" >> /dev/stderr
SLOUCIT=''
else
echo "${line}"
fi
fi
done
}
Funkce přebere hodnotu parametru $1, kterou zpracuje jako textový řetězec a na záklatě toho vrátí některou níže uvedených řetězcových hodnot.
Každá má délku 4 znaků a vychází z toho, jakému typu
$1 odpovídá. Čísla, protože mohou mít i negativní
variantu, mohou vracet také varianty začínající znakem
n, za který následují první tři znaky
odpovídající typu čísla: flo nebo int.
flow je kladné číslo s plovoucí čárkou,
které zastupuje časový interval v sekundách. Ve skriptu se veškerá čísla
převádí na řetězcovou hodnotu kladného celého čísla, takže místo čísla
0.000001 (float) se pracuje s číslem
1 (integer)
file se vrací v případě, že je
parametrem existující soubor.
help - `obsolete`
inte je kladné, celé číslo, jako je
například číslo 12. Obvykle jde o číslo řádku, pořadí
události. Podle typu akce.
nflo je záporné číslo s plovoucí čárkou
typu float např. -12.345678
nint je záporné celé číslo typu
integer, např. -12. S takovými čísly pracuje
hlavně akce view, kdy
znaménko mínus signalizuje že jde o řádky co předcházejí
rang je řetězec, který může kromě čísel
a tečky obsahovat také pomlčku. Lze takto v jednom řetězci vymezit
rozsah (range), ale v současné chvíli se s takovým typem
řetězců nepracuje.
stri zastupuje řetězcovou hodnotu.
Obvykle název příkazu, akce, nebo zástupného řetězce pro speciální
sekvence.
time zastupuje čas předaný ve tvaru
HH:MM:SS, který je přirozenější pro naše vnímání času. Interně se tenhle
typ řetězce převádí na flow (čas v sekundách)
value () {
if [ -z "${1}" ] ; then
return
elif [ ${1:0:1} == "-" ] ; then
NUM=${1:1}
if [ -z "${NUM//:digit:/}" ] ; then
echo nint
elif [ "${NUM//:digit:/}" == "." ] ; then
echo nflo
else
echo help
fi
elif [ -z "${1//:digit:/}" ] ; then
echo inte
elif [ "${1//:digit:/}" == ":" ] ; then
echo time
elif [ "${1//:alnum:/}" == "${1//:digit:/}" ] ; then
if [ "${1//-/}" == "${1}" ] ; then
echo flow
else
echo rang
fi
else
if [ -f "${1}" ] ; then
echo file
else
echo stri
fi
fi
return
}
Funkce si nejprve ověří, zda-li existuje soubor ${CLIPFILE}. Pokud
ano, nakrmí pomocí aplikace ${JQ} pole ${CLIPLINES[@]}, položkami, které jsou
stejně jako u pole ${LINES[@]} kombinací typu události a
času, konvertovaného z typu float na řetězec.
clip_load () {
[ ${DEBUG} ] && \
echo "INFO: clip_load() ${CLIPFILE} " >> /dev/stderr
[ ! -f ${CLIPFILE} ] && return 1
local HEADER
local TEST
TEST=$(head -1 ${CLIPFILE})
HEADER="${TEMP_DIR}/clipheader"
${JQ} '.width, .height, .timestamp, .title' <<< ${TEST} > ${HEADER}
width
head -1 ${HEADER} | tail -1 | sed 's/\"//g' > ${TEMP_DIR}/cwidth
height
head -2 ${HEADER} | tail -1 | sed 's/\"//g' > ${TEMP_DIR}/cheight
timestamp
head -3 ${HEADER} | tail -1 > ${TEMP_DIR}/ctimestamp
title (nepovinný string)
head -4 ${HEADER} | tail -1 | sed 's/\"//g' > ${TEMP_DIR}/ctitle
[ ${DEBUG} ] && \
echo "INFO: clip_load() load ${CLIPFILE} events" >> /dev/stderr
TEST=($(jq 'if [.[] ] | .[1] == ("m", "o" , "r") then "\(.[1])\(.[0] * 1000000 | floor | tostring)" else null end' ${CLIPFILE}))
CLIPLINES=(${TEST[@]//[nul\"]/})
return
}
Natahuje hlavičku asciicastu, před načtením událostí
echo $TEST | jq '.version, ... , .env.THERM'
nebo
jq '.version, ... , .env.THERM' <(echo ${TEST})
header_load () {
[ ${DEBUG} ] && \
echo "CALL: $FUNCNAME() - ${1}" >> /dev/stderr
local HEADER
local TEST
TEST=$(head -1 ${1})
HEADER="${TEMP_DIR}/header"
if [ ${#TEST} -eq 1 ] ; then
RECORDFILE="${TEMP_DIR}/json_to_ascii.cast"
[ ${DEBUG} ] && \
echo "WARN: $FUNCNAME() convert JSON ${1} into asciicast ${RECORDFILE}" >> /dev/stderr
${JQ} . -c -M ${1} > ${RECORDFILE}
${JQ} '.version, .width, .height, .timestamp, .title, .duration, .idle_time_limit, .env.SHELL, .env.TERM, .theme.fg, .theme.bg, .theme.pallete' <(head -1 ${RECORDFILE}) > ${HEADER}
else
RECORDFILE=${1}
${JQ} '.version, .width, .height, .timestamp, .title, .duration, .idle_time_limit, .env.SHELL, .env.TERM, .theme.fg, .theme.bg, .theme.pallete' <<< ${TEST} > ${HEADER}
fi
version
head -1 ${HEADER} | tail -1 > ${TEMP_DIR}/version
width
head -2 ${HEADER} | tail -1 | sed 's/\"//g' > ${TEMP_DIR}/width
height
head -3 ${HEADER} | tail -1 | sed 's/\"//g' > ${TEMP_DIR}/height
timestamp
head -4 ${HEADER} | tail -1 > ${TEMP_DIR}/timestamp
title (nepovinný string)
head -5 ${HEADER} | tail -1 | sed 's/\"//g' > ${TEMP_DIR}/title
duration (nepovinný float)
head -6 ${HEADER} | tail -1 > ${TEMP_DIR}/duration
idle (nepovinný float)
head -7 ${HEADER} | tail -1 > ${TEMP_DIR}/idle
shell string
head -8 ${HEADER} | tail -1 | sed 's/\"//g' > ${TEMP_DIR}/shell
term string
head -9 ${HEADER} | tail -1 | sed 's/\"//g' > ${TEMP_DIR}/term
barva textu string
head -10 ${HEADER} | tail -1 | sed 's/\"//g' > ${TEMP_DIR}/fg
barva pozadí string
head -11 ${HEADER} | tail -1 | sed 's/\"//g' > ${TEMP_DIR}/bg
paleta barev string
head -12 ${HEADER} | tail -1 | sed 's/\"//g' > ${TEMP_DIR}/pallete
[ ${DEBUG} ] && \
cat ${HEADER} >> /dev/stderr && \
ls -al ${TEMP_DIR} >> /dev/stderr && \
echo "RETURN: $FUNCNAME() DONE!" >> /dev/stderr
return
}
Tato funkce načítá události ze souboru ${RECORDFILE} pomocí aplikace ${JQ} do pole ${LINES[@]}.
${LINES[@]}, obsahuje řetězce,
které jsou kombinací typu události a času, převedeného z typu
float na celé číslo typu integer což je čas v
mikrosekundách. Počet položek v tomto poli poodpovídá počtu událostí
(řádků) v asciicastu.
Funkce umožňuje také konverzi asciicastu ve formátu v1 na novější formát v2
cast_load () {
[ ${DEBUG} ] && \
echo "CALL: $FUNCNAME() - RECORDFILE ${RECORDFILE}" >> /dev/stderr
local JSON
local TEST
case $(head -1 ${TEMP_DIR}/version) in
1) echo "WARN: Original is v1 format (JSON), wait to conversion into v2 format. Export output into a new clip is recommend." >> /dev/stderr
echo "2" > ${TEMP_DIR}/version
TITLE="$(head -1 ${TEMP_DIR}/title) - converted from v1 asciicast format"
new timestamp
echo $(date +%s) > ${TEMP_DIR}/timestamp
${JQ} . -M ${RECORDFILE} | ${SED} 's/\\/\\\\/g' | while read line ; do
#echo "${line}" >> /dev/stderr
if [ "${line}" == '"stdout": [' ] ; then
JSON=yes
TEST=0
header_print
elif [ ${JSON} ] ; then
case ${line} in
\[)
;;
[0-9]*) TEST=$(( ${TEST} + $(float_to_string ${line// ,/}) ))
;;
\"*) echo "[$(float_from_string ${TEST}), \"o\", ${line}]"
;;
esac
fi
done > ${TEMP_DIR}/ascii.cast
RECORDFILE=${TEMP_DIR}/ascii.cast
TEMPORARY=($(jq 'if [.[] ] | .[1] == ("m", "o" , "r") then "\(.[1])\(.[0] * 1000000 | floor | tostring)" else null end' ${RECORDFILE}))
LINES=(${TEMPORARY[@]//[nul\"]/})
[ ${DEBUG} ] && \
echo "RETURN: $FUNCNAME() array LINES (${#LINES[@]}) ${LINES[@]}" >> /dev/stderr
;;
2) TEMPORARY=($(jq 'if [.[] ] | .[1] == ("m", "o" , "r") then "\(.[1])\(.[0] * 1000000 | floor | tostring)" else null end' ${RECORDFILE}))
LINES=(${TEMPORARY[@]//[nul\"]/})
[ ${DEBUG} ] && \
echo "RETURN: $FUNCNAME() array LINES (${#LINES[@]}) ${LINES[@]}" >> /dev/stderr
;;
*) echo "WARN: This version of the asciicast format CLI player don't support" >> /dev/stderr
;;
esac
return
}
Vrací řetězce, které se mohou vyskytovat v asciicastu. Jejich seznam je uložen v poli?
https://tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html
get_string () {
[ ${DEBUG} ] && \
echo "INFO: get_string() ${1} - ${2}" >> /dev/stderr
local STRING
[ ${2} ] && STRING=${2}
case ${1} in
backline|\
K)Používá se, při nahrazení zobrazeného řetězce jiným
Kurzor se vrátí a následující události původní řetězec
přepíšou
echo "[${STRING}K"
;;
backchar)Používá se, při nahrazení zobrazeného řetězce jiným
Kurzor se vrátí a následující události původní řetězec
přepíšou
echo "\b"
;;
J)clear screen - STRING 1 (before) - 2 (after)
echo "[${STRING}J"
;;
clear)variant: blank 2
echo '\u001b[2J'
;;
breakline) echo "\u0007"
;;
color)color (0 - off) STRING i.e. 1;37
echo "[${STRING}m"
;;
select)variant of: color 7
echo '\\\u001b\[7m'
;;
blink) echo 'Sekvence skryje výplň kurzoru' >> /dev/stderr
echo '='
;;
enter)start new line for command
echo "]0"
;;
A|\
cursor-up)move cursor up
echo "[${STRING}A"
;;
B|\
cursor-down)move cursor down
echo "[${STRING}B"
;;
C|\
cursor-fore)move cursor forward
echo "[${STRING}C"
;;
D|\
cursor-back)move cursor back
echo "[${STRING}D"
;;
H|\
cursor)skip to start line STRING (target row to skip)
echo "[${STRING}H"
;;
cursor-restore)save actual cursor position
echo "[u"
;;
cursor-hide)enter 1 2004 (number may be replaced by dot)
echo "[?${STRING}l"
;;
cursor-save)save actual cursor position
echo "[s"
;;
cursor-show)enter 1 2004 (number may be replaced by dot)
echo "[?${STRING}h"
;;
newline)'\r\n'
echo "\r\n"
;;
return) echo "\r"
;;
*) [ ${DEBUG} ] && \
echo "find – ${1}" >> /dev/stderr
printf "${1}"
;;
esac
return
}
Vstupním parametrem $1 je číslo řádku. Funkce vrací přesný čas události na tomto řádku v mikrosekundách (kladné číslo typu integer'). Při záporné hodnotě, vrací čas události předchozí
time_by_line () {
[ ${DEBUG} ] && \
echo "INFO: time_by_line() : ${1}" >> /dev/stderr
local INDEX
case $(value ${1}) in
inte) INDEX=$((${1} - 1))
;;
nint) INDEX=$((${1:1} - 2))
;;
esac
echo ${LINES[${INDEX}]:1}
return
}
Parametrem $1 je čas, který byl předán ve tvaru 00:00:01 Teoreticky je možné předat čas delší než 02:45:60 ale tak dlouhý asciicastový záznam by stejně nikdo nesledoval – většina ze 75 tisíc veřejných záznamů na serveru https://asciinema.org netrvá déle než minutu.
print_seconds () {
[ ${DEBUG} ] && \
echo "INFO: print_seconds() : ${1}" >> /dev/stderr
case ${#1} in
1) printf "%d.000000\n" $(date "+%s" --date="1970-01-01 00:00:0${1} UTC");;
2) printf "%d.000000\n" $(date "+%s" --date="1970-01-01 00:00:${1} UTC")
;;
4) printf "%d.000000\n" $(date "+%s" --date="1970-01-01 00:0${1} UTC") ;;
5) printf "%d.000000\n" $(date "+%s" --date="1970-01-01 00:${1} UTC")
;;
7) printf "%d.000000\n" $(date "+%s" --date="1970-01-01 0${1} UTC") ;;
8) printf "%d.000000\n" $(date "+%s" --date="1970-01-01 ${1} UTC")
;;
esac
return
}
Parametrem $1 je čas v mikrosekundách, předaný buď kladné celé číslo typu integer, nebo jako číslo typu float, s tečkou místo čárky – např. 0.0 a výsledkem čas zaokrouhlený na celé sekundy, ve tvaru "00:00:00" (hodiny, minuty a sekundy). K převodu se využívá date.
print_time () {
[ ${DEBUG} ] && \
echo "INFO: print_time() : ${1}" >> /dev/stderr
case $(value ${1}) in
inte) date -ud "@$(float_from_string ${1})" +%T
;;
flow) date -ud "@${1}" +%T
;;
esac
return
}
Parametrem $1 je pořadové číslo výchozího řádku, který se zobrazí jako první, které nemůže být vyšší než je aktuální počet řádků v asciicastu, které musí být předáno vždy
Parametr $2, kladné celé číslo, určuje, kolik se má zobrazit řádků po něm následujících. Je nepovinný. Není-li uveden, zobrazí se pouze výchozí řádek
infoZobrazí informace o asciicastu
scopePokud asciicast obsahuje série událostí, co se zobrazují ve stejný čas, zobrazí jejich RANG
contextO jak velký časový interval je potřeba události posunout, se dá zjistit přes kontext události:
~$ asciiframe.sh view ascii.cast context 21
2.997355 (2.334431 - 0.662924)
První číslo je délka intervalu mezi zobrazením předchozí a zobrazením následující události – to je pracovní prostor pro akci event. První číslo v závorce, je časový interval, od spuštění předchozí události, která je v asciicastu na řádku č. 20 a druhé číslo je čas, který uplyne, než dojde na událost, co je na řádku č. 22
O jaké události jde, zjistíte velice snadno. Stačí si zobrazit přes akci view řádek č. 20 + 2 řádky následující:
~$ asciiframe.sh view ascii.cast 20 2
...
nebo naopak řádek č. 22 + 2 řádky předchozí:
~$ asciiframe.sh view ascii.cast 20 -2
...
V obou případech bude výsledek stejný. Příkaz context, předaný akci ''view nám umožní zjistit optimální prodlení pokud budeme chtít obsah zobrazované události z řádku č. 21 rozdělit.
get_view () {
[ ${DEBUG} ] && \
echo "CALL: $FUNCNAME() - ${1} - ${2}" >> /dev/stderr
local CONTEXT
local EVENT
local NEXT
local ITEM
local PREV
local TEST
local TIME
case ${1} in
-0|0) echo "ERROR: Line ordering started from 1, not ${1}" && return 1
;;
context)context
case $(value ${2}) in
flow|time) if [ "${2/:/}" == "${2}" ] ; then
TIME=$(float_unify ${2})
LINE=$(line_by_time ${TIME})
else
LINE=$(line_by_time $(print_seconds ${2}))
fi
[ $? -eq 1 ] && \
echo ${LINE} && \
return 1
context_show ${LINE}
;;
inte|rang) FROM=(${2/-/ })
if [ -z "${FROM[0]#*:}" ] ; then
[ -z "${FROM[1]%:*}" ] && return 1 || FROM[0]=${FROM[1]%:*}
elif [ -z "${FROM[1]%:*}" ] ; then
FROM[1]=${FROM[0]#*:}
fi
for i in $(seq ${FROM[0]#*:} ${FROM[1]%:*}) ; do
printf "%d: %s\n" "$i" "$(context_show ${i})"
done
;;
esac
return
;;
info)show title, time of record (timestamp), time last change
TEST=$(head -1 ${TEMP_DIR}/title)
[ "${TEST}" == 'null' ] && TEST='not set'
printf "%18s : %s\n" 'title' "${TEST}"
TEST=$(head -1 ${TEMP_DIR}/timestamp)
printf "%18s : %s (%d)\n" 'record date' $(date --date="@${TEST}") "${TEST}"
TEST="${#LINES[@]}"
printf "%18s : [1m%s[0m\n" 'events count' "${TEST}"
TEST=$(stat -c %s ${RECORDFILE})
printf "%18s : %s\n" 'size in bytes' "${TEST}"
TEST=$(head -1 ${TEMP_DIR}/duration)
[ "${TEST}" == 'null' ] && TEST='not set'
printf "%18s : %s\n" 'duration' "${TEST}"
TEST=$(float_from_string ${LINES[-1]:1})
printf "%18s : %s sec. (%s)\n" 'time of last event' "${TEST}" $(do_event time ${TEST})
TEST=$(stat -c %w ${RECORDFILE})
printf "%18s : %s\n" 'created' "${TEST}"
TEST=$(stat -c %y ${RECORDFILE})
printf "%18s : %s\n" 'last change' "${TEST}"
unset TEST
return
;;
scope)scope
case $(value ${2}) in
flow|time) if [ "${2/:/}" == "${2}" ] ; then
TIME=$(float_unify ${2})
else
LINE=$(line_by_time $(print_seconds ${2}))
[ $? -eq 1 ] && \
echo ${LINE} && \
return 1
echo ${LINE} >> /dev/stderr
TIME=$(float_from_string $(time_by_line ${LINE}))
fi
get_scope | while read line ; do
if [ "$(float_strip ${line%%:*})" == "${TIME}" ] ; then
echo ${line}
break
fi
done
;;
nflo) echo "ERROR: get_view() ${1} not accept $(value ${2}) ${2}"
return 1
;;
inte|rang) FROM=(${2/-/ })
if [ -z "${FROM[0]#*:}" ] ; then
[ -z "${FROM[1]%:*}" ] && return 1 || FROM[0]=${FROM[1]%:*}
elif [ -z "${FROM[1]%:*}" ] ; then
FROM[1]=${FROM[0]#*:}
fi
get_scope | while read line ; do
RANG=(${line/-/ })
START=${RANG[0]#*:}
END=${RANG[1]%:*}
for i in $(seq ${START} ${END}) ; do
for y in $(seq ${FROM[0]#*:} ${FROM[1]%:*}) ; do
[ $i -eq $y ] && echo ${line} && return
done
done
done
;;
*) get_scope
;;
esac
return
;;
h)header
jq --slurp '.[0]' ${RECORDFILE}
return
;;
m)marker
print_event m ${2}
return $?
;;
o)out
print_event o ${2}
return $?
;;
r)resizes
print_event r ${2}
return $?
;;
*) case $(value ${1}) in
nflo) [ ${DEBUG} ] && \
echo "INFO: get_view() negative time START" >> /dev/stderr
TEST=$((${LINES[$((${#LINES[@]}-1))]:1} - $(float_to_string ${1:1})))
[ ${TEST:0:1} -le 1 ] && echo "ERR: get_view() Parameter ${1} out of range" && return 1
#echo "Spočítat řádek" >> /dev/stderr
x=''
for i in ${!LINES[@]} ; do
if [ ${LINES[$i]:1} -ge ${TEST} ] ; then
x=$i;
break
fi
done
if [ -z ${x} ] ; then
echo "INFO: get_view() not match for value ${1}" >> /dev/stderr
else
BEGIN=$((${x}+1)) >> /dev/stderr
shift
fi
;;
flow) [ ${DEBUG} ] && \
echo "ERR: get_view() flow positive $(line_by_time ${1}) time START" >> /dev/stderr
TEST=$((${LINES[$((${#LINES[@]}-1))]:1} - $(float_to_string ${1})))
[ ${TEST:0:1} -le 1 ] && echo "ERR: get_view() flow - Parameter ${1} out of range" && return 1
#echo "Spočítat řádek" >> /dev/stderr
TEST=$(float_to_string ${1})
x=''
for i in ${!LINES[@]} ; do
if [ ${LINES[$i]:1} -ge ${TEST} ] ; then
x=$i;
break
fi
done
if [ -z ${x} ] ; then
[ ${DEBUG} ] && \
echo "ERR: get_view() flow - Not match for value ${1}" >> /dev/stderr
else
BEGIN=$((${x}+1)) >> /dev/stderr
shift
fi
;;
inte) #echo "# positive START" >> /dev/stderr
[ ${1} -gt $((${#LINES[@]}+1)) ] && \
echo "ERR: get_view() inte - Parameter ${1} out of range" && return 1
BEGIN=${1}
shift
;;
nint) [ ${DEBUG} ] && \
echo "INFO: get_view()negative START" >> /dev/stderr
BEGIN=$((${#LINES[@]} + 1 ${1}))
[ ${BEGIN} -lt 1 ] && echo "ERR: get_view() nint - Parameter ${1} out of range" && return 1
shift
;;
*) echo "ERROR: get_view() invalid value $(value ${1}) - ${1}" >> /dev/stderr
return 1
;;
esac
esac
if [ -z ${DURATION} ] ; then
DURATION=${LINES[$((${#LINES[@]}-1))]:1}
fi
echo "DURATION ${DURATION}" >> /dev/stderr
echo "BEGIN ${BEGIN}" >> /dev/stderr
if [ -z ${1} ] ; then
#echo "Není jiný parametr" >> /dev/stderr
FROM=${BEGIN}
TO=${BEGIN}
else
#echo "Je další parametr" >> /dev/stderr
case $(value ${1}) in
nflo) TEST=$((${LINES[$((${BEGIN}-2))]:1} - $(float_to_string ${1:1})))
if [ "${TEST}" -le 1 ] ; then
FROM=2
else
x=''
for i in ${!LINES[@]} ; do
if [ ${LINES[$i]:1} -le ${TEST} ] ; then
((x=x+1))
if [ ${LINES[$i]:1} -ge ${LINES[$((${BEGIN}-2))]:1} ] ; then
break
fi
fi
done
FROM=$x
fi
TO=${BEGIN}
nutné
unset TEST
;;
flow) #echo "Od ${BEGIN} flow ${1}" >> /dev/stderr
FROM=${BEGIN}
if [ ${FROM} -le 1 ] ; then
TEST=$(float_to_string ${1})
else
TEST=$((${LINES[$((${BEGIN}-1))]:1} + $(float_to_string ${1})))
fi
for i in ${!LINES[@]} ; do
if [ ${LINES[$i]:1} -ge ${TEST} ] ; then
x=$i;
break
fi
done
if [ -z ${x} ] ; then
TO=${#LINES[@]}
else
TO=$((${x}+1))
fi
nutné
unset TEST
;;
inte) #echo "BEGIN ${BEGIN} inte ${1}" >> /dev/stderr
FROM=${BEGIN}
TO=$((${BEGIN} + ${1}))
;;
nint) #echo "BEGIN ${BEGIN} nint ${1}" >> /dev/stderr
FROM=$((${BEGIN} - ${1:1}))
TO=${BEGIN}
;;
*) END=${BEGIN}
;;
esac
fi
#echo "FROM ${FROM} - TO ${TO}" >> /dev/stderr
[ ${FROM} -eq 1 ] && VIEW="yes"
k=1
for ((z=0;z<${TO};z++)) ; do
[ ${k} -ge ${FROM} ] && [ ${k} -le ${TO} ] && VIEW="yes" || unset VIEW
case $z in
0) [ ! -z ${VIEW} ] && line_h
;;
*)
case ${LINES[${l=0}]:0:1} in
m) ((n=n+1))
[ ! -z ${VIEW} ] && line_m $(($l+2)) $n
;;
o) ((p=p+1))
[ ! -z ${VIEW} ] && line_o $(($l+2)) $p
;;
r) ((q=q+1))
[ ! -z ${VIEW} ] && line_r $(($l+2)) $q
;;
esac
((l=l+1))
esac
((k=k+1))
done
return
}
Akceptuje jeden až dva parametry. První parametr $1
je vždy textový řetězec. Může to být zástupné jméno speciální sekvence,
kterou lze dále parametrizovat parametrem $2, ale ne
každá speciální sekvence ho vyžaduje. Např. sekvence \b,
kterou lze vyhledat přes zástupné jméno backchar žádný
další parametr nepotřebuje.
Naopak zástupné jméno color bez parametru
$2 vyhledá pouze sekvenci [m. Pokud se má
vyhledat sekvence [0m, kterou se vrací nastavení zvýraznění
textu na výchozí hodnotum, nebo jiný typ zvýraznění textu, je potřeba
použít ještě jeden parametr.
Pokud neznáme parametry barevného zvýraznění, které hledáme, můžeme
si vypomoci tečkou. A pokud chceme vyhledat dva řetězce oddělené
mezerou, musíme místo mezery použít zástupnou sekvenci
\x20. Více viz akce find
findline () {
[ ${DEBUG} ] && \
echo "INFO: findline() - ${1} - ${2}" >> /dev/stderr
local STRING
STRING=$(get_string ${1} ${2} | ${SED} 's/\\/\\\\/g' | ${SED} 's/\[/\\\[/g')
grep --color -n "${STRING}" ${RECORDFILE}
[ ${DEBUG} ] && \
echo "INFO: ${STRING}" >> /dev/stderr
return
}
Funkce odbarvuje proud dat
decolor () {
sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g"
return
}
Funkce maže řádky. Parametr $1 umožňuje dodatečnou korekci
delete_line () {
[ ${DEBUG} ] && \
echo "INFO: delete_line() ${LINE} - ${ENDLINE} correction ${1}" >> /dev/stderr
case $(value ${1}) in
inte|nint)correction by count lines
if [ -z "${ENDLINE}" ] ; then
ENDLINE=$((${LINE} + ${1}))
else
ENDLINE=$((${ENDLINE} + ${1}))
fi
;;
flow)add correction by time interval
if [ -z "${ENDLINE}" ] ; then
TIME=$(time_by_line ${LINE})
[ ${DEBUG} ] && \
echo "INFO: delete_line() LINE time ${TIME} corrected by time ${1}" >> /dev/stderr
ENDLINE=$(line_by_time $(float_from_string $((${TIME} + $(float_to_string ${1})))))
[ $? -eq 1 ] && \
echo ${ENDLINE} && \
return 1
else
TIME=$(time_by_line ${ENDLINE})
[ ${DEBUG} ] && \
echo "INFO: delete_line() ENDLINE time ${TIME} corrected by time ${1}" >> /dev/stderr
ENDLINE=$(line_by_time $(float_from_string $((${TIME} + $(float_to_string ${1})))))
[ $? -eq 1 ] && \
echo ${ENDLINE} && \
return 1
fi
;;
nflow)correction start interval selected by range
if [ -z "${ENDLINE}" ] ; then
TIME=$(time_by_line ${LINE})
ENDLINE=${LINE}
[ ${DEBUG} ] && \
echo "INFO: delete_line() range corrected from end by time ${1}" >> /dev/stderr
LINE=$(line_by_time $((${TIME} - $(float_to_string ${1:1}))))
[ $? -eq 1 ] && \
echo ${LINE} && \
return 1
else
TIME=$(time_by_line ${LINE})
[ ${DEBUG} ] && \
echo "INFO: delete_line() range corrected from LINE time by time ${1}" >> /dev/stderr
LINE=$(line_by_time $((${TIME} - $(float_to_string ${1:1}))))
[ $? -eq 1 ] && \
echo ${LINE} && \
return 1
fi
;;
esac
if [ -z "${ENDLINE}" ] ; then
[ ${DEBUG} ] && \
echo "INFO: delete_line() ${1} line ${LINE}" >> /dev/stderr
#sed -n ${LINE}p ${RECORDFILE}
${ED} "${RECORDFILE}" 2>/dev/null <<-EOF
#.n${LINE}
#q
#EOF
(
${ED} "${RECORDFILE}" -q -s 2>/dev/null <<-EOF
${LINE}p
q
EOF
) >> ${TEMP_DIR}/delete_event
jiná událost, jiný soubor
EVENT=$(head -1 ${TEMP_DIR}/delete_event)
echo ${EVENT} >> /dev/stderr
anone "[1mIts event from line ${LINE}, for delete push 'y' key[0m"
[ $? -eq 1 ] && \
return
${ED} "${RECORDFILE}" 2>/dev/null <<-EOF
${LINE}d
w
q
EOF
else
[ ${DEBUG} ] && \
echo "INFO: do_event() ${1} by scope from ${LINE} line to ${ENDLINE}" >> /dev/stderr
[ ${ENDLINE} -lt $((${LINE} + 1)) ] && \
echo "ERR: do_event() - Invalid scope for ${1} (${LINE} - ${ENDLINE})" >> /dev/stderr && \
return 1
sed -n ${LINE},${ENDLINE}p ${RECORDFILE}
anone "[1mDelete this scope ${LINE}-${ENDLINE}? For continue push 'y' key.[0m" >> /dev/stderr
[ $? -eq 1 ] && \
return
${ED} "${RECORDFILE}" 2>/dev/null <<-EOF
${LINE},${ENDLINE}d
w
q
EOF
fi
return
}
Funkce zpracovává příkazy předané akci event. Ta vykonává jen takové operace, které nevyžadují posun časování událostí, kterých se příkaz bezprostředně netýká.
delete$ asciiframe event ascii.cast delete LINE|TIME|RANG [CORRECTION]
Prvním parametrem příkazu se určuje číslo řádku, který se má smazat. Je doporučeno pracovat při odstraňování událostí s číslem řádku, protože asciicastu může obsahovat série událostí, které se spouští ve stejný čas. Nicméně u dlouhých záznamů lze použít i čas – buď v „human-ready” formátu [HH:]MM:SS, nebo v sekundách a CORRECTION.
Odstranění celého bloku řádků se totiž dělá přes rozsah (RANG), který má jednoduchý formát START-END. Následující ukázka demonstruje jak smazat události, počínaje hned tou první na řádku 2, včetně události na řádku 300:
$ asciiframe.sh event ascii.cast delete 2-300
Celkem bude odstraněno 298 řádků. Parametr CORRECTION to ale umožňuje změnit. Kladná hodnota do výběru řádky přidá, takže při následující kombinace by bylo odstraněno 308 řádků:
$ asciiframe.sh event ascii.cast delete 2-300 10
A záporná naopak ubere. Při nastavení rozsahu pořadím řádků tak nemá CORRECTION valný význam. Ovšem zcela jinak tomu bude, pokud budeme pracovat s časem.
$ asciiframe view asahi-m1.backup1 369
369: 368: [569.433513, "o", "ls /mnt/"]
$ asciiframe view asahi-m1.backup1 569.4 10
[569.433513, "o", "ls /mnt/"]
...
[572.738149, "o", "\u001b[C /mnt/\b\b\b\b\b\b"]
This is content of scope 368-378. For continue push 'y' key
TODO!!! S časem to nefunguje jak má. Bere pouze jeden řádek a na CORRECTION sere
Pokud víme, že nás z půlhodinového záznamu zajímá jenom část, která proběhla někdy 12 a 15 minutou, můžeme nejprve nahrubo odstranit vše co je mimo tento interval, protože s kratším záznamem se bude líp pracovat. A provedeme to následující sekvencí příkazů:
$ tail ascii.cast
[2993.557207, "o", "exit\r\n"]
$ asciiframe.sh event ascii.cast time 2993.557207
00:49:53
$ asciiframe.sh event ascii.cast time 15:00
900.000
$ asciiframe view asahi-m1.backup 900.0 1
549: 548: [650.729441, "o", "\r...
...
| ora:~\u0007\u001b[?2004h[root@fedora ~]# "]
550: 549: [939.064954, "o", "w"]
$ asciiframe view ascii.cast -1
3578:3577: [2993.525216, "o", "\r\n\u001b[?2004l\r"]
$ asciiframe event ascii.cast delete 550-3578
$ asciiframe find ascii.cast enter
...
543:[646.480455, "o", "\u001b]0;root@fedora:~ ...
...
$ asciiframe clip ascii.cast 543 > clip.cast
dumpPříkaz odstraňuje z proudu dat sekvence barev. Používá se ke zpracování výstupu aplikace asciinema, který vysype na stdout když se použije příkaz cat. Při tom ale zmizí hlavička, ve které jsou metadata, co obsahují informace o velikosti terminálu v době pořízení záznamu, se kterými se pracuje během „přehrávání” asciicastu.
Pokud bude rozměr terminálu stejný, jako byl během nahrávání, a jeho buffer dost velký na to aby se vše do něj vešlo, můžeme si ho poslat přes rouru na less a procházet, jako by to byl obyčejný text:
$ asciinema cat ascii.cast | less -R
Parametr -R je nutný, protože bez něj
by se zobrazily i terminálové sekvence, které řeší jeho obarvení, takže
by výstup začínal nějak takto:
ESC[0mESC[49mESC[39mESC[27mE...
Pokud z něj tedy chceme vydolovat prostý text, je potřeba odstranit terminálové sekvence, které řeší jeho obarvení. A právě na to lze využít akci event a příkaz dump. Máme-li k dispozici původní ascii.cast, stačí jenom tohle:
$ asciiframe event ascii.cast dump > monochrome.raw
Ale pokud chceme odbarvit již existující RAW výstup, který nemusí být nutně výstupem aplikace asciinema, můžeme ho předat jako parametr
$ asciiframe event ascii.cast color.raw dump > monochrome.raw
textPříkaz text lze použít dvojím způsobem. S parameterem, nebo bez něj. Je-li předán parametr, bude výsledek podobný, jako při akci find, ovšem s jedním, dost podstatným rozdílem, který lze demonstrovat jedině na praktické ukázce:
~$ asciiframe.sh find ascii.cast newline
...
287:[114.189237, "o", "\u001b[?2004l\r\r\n"]
...
~$ asciiframe.sh find ascii.cast text newline
...
287 0.600320 : [114.189237, "o", "\u001b[?2004l\r\r\nexit\r"]
...
~$ asciiframe.sh event ascii.cast text newline
...
287 0.600320 : [114.189237, "o", "\u001b[?2004l\r\r\nexit\r"]
...
Povšimněte si, že místo řetězce newline byla vyhledána
sekvence, která se používá pro zalomení řádky, ale výsledek, zpracovaný
přes akci event
vrací poněkud jiný obsah události a také, kromě čísla řádku jedno číslo
navíc.
Je to proto, že příkaz text je aplikován až na monochromatickou verzi asciicastu, která má sloučené jednoznakové události do celistvých řetězců. A číslo 0.600320 udává délku sloučeného intervalu. Chceme-li se tedy podívat, jak vypadají události v asciicastu, můžeme předat obě čísla akci ''#akce_view!view
~$ asciiframe view ascii.cast 287 0.600320
287: 286: [114.189237, "o", "\u001b[?2004l\r\r\n"]
288: 287: [114.289557, "o", "e"]
289: 288: [114.389557, "o", "x"]
290: 289: [114.489557, "o", "i"]
291: 290: [114.589557, "o", "t"]
292: 291: [114.689557, "o", "\r"]
Jak vidno, k události přibyl řetězec exit, složený z
jednoznakových událostí. Můžeme si tedy rovnou vyzkoušet co vyhledá akce
''find:
~$ asciiframe.sh find ascii.cast exit
find – exit
~$ asciiframe.sh find ascii.cast text exit
find – exit
287 0.600320 : [114.189237, "o", "\u001b[?2004l\r\r\nexit\r"]
Volání tohoto příkazu přes akci find však má své omezení – vyžaduje řetězec. Pokud ale zavoláme příkaz text přes akci event bez parametru, vypíše se celý asciicast, který můžeme prohnat následujícím filtrem a uložit jako nový, monochromatický asciicast, vhodný k vytvoření plain textového záznamu:
~$ asciiframe.sh event ascii.cast text | cut -c 21- > monochrome.cast
~$ asciiframe.sh play monochrome.cast
Z tohoto záznamu budou odstraněny veškeré vizuální efekty, překlepy, zvýraznění i barvy. Na druhou stranu lze všechny události takto vyčištěného asciicastu rozdělit příkazem split a následně podle potřeby obarvit.
splitKdyž se podíváte do asciicastu, je ihned vidět, kdy šel na terminál výstup z klávesnice a kdy byl obsah zkopírován ze schránky, nebo ho vypsala nějaká aplikace. Při psaní se totiž ukládá každý zapsaný znak jako samostatná událost, která má svůj čas. A split umožňuje takovou sérii událostí vygenerovat. Buď rozdělením stávající události, nebo rozdělením obsahu komentáře, vloženého na konkrétní čas. Při sledování záznamu bude výsledek stejný, jako by se text, který byl původně na příkazovovou řádku vložen ze schránky, zapisoval.
Rozdělení lze provést jednoduše:
~$ asciiframe.sh event ascii.cast split 20
Aby to působilo přirozeně, je ovšem potřeba zvolit optimální interval. V dokumentaci funkce playcast(), je ukázkový příklad využití příkazu context, který můžete aplikovat i na svůj vlastní asciicast, ze kterého plyne, že se rychlost psaní jednotlivých znaků pohybuje zhruba od 0.1 do 0.3 sekundy. Je tedy pouze na vás, jestli upřednostníte výchozí interval 0.1 (default), nebo použijete jinou hodnotu, ideálně v rozmezí od 0.1 do 0.3 sekundy. Viz příklad:
~$ asciiframe.sh event ascii.cast split 20 0.25
Každopádně ještě než dojde na realizaci příkazu split, budete dotázáni, zda se má příkaz opravdu realizovat, nebo ne. Tohle výchozí chování skriptu je ale možné potlačit volbou -f – do souboru asciicastu se změny zapíšou jen když se poslední událost vejde do intervalu zobrazení, který následuje za řádkem č. 20.
mergeTODO
~$ asciiframe.sh event ascii.cast merge 214 216
Všechny operace Pokud tedy chcete vložit komentář, a tedy potřeba zkrátit, nebo naopak prodloužit interval zobrazení, je potřeba nebo časo žádnou dělají žádnérealizují jednorázové operace, při kterých se nepracuje s časem událostí. kterých se to týká.
vždy a pouze jedné událost. umožňuje vložit novou, nebo naopak
odstranit stávající událost do/z asciicastového souboru
Je-li přidaná nová událost, nastavuje se její čas buď podle parametru TIME, nebo podle aktuálního času události na řádku LINE.
Přes parametr CONTENT se zadává obsah události, který je v některých
případech (jako např. při události typu resize) dokonce
povinný.
decolor ~$ asciiframe.sh event ascii.cast decolor
Vypíše na standardní výstup asciicast, ze kterého jsou odstraněny sekvence, jimiž se nastavují barvy a jiná zvýraznění.
Příkaz zavolá funkci print_monochrome(), která zpracuje asciicast ve dvou průchodech, při kterých zobrazované události „odbarvuje” funkcí event_plain(). Ty dva průchody jsou nutné, protože formát asciicastu neřeší to, zda-li jsou sekvence speciálních znaků rozdělené mezi dvě po sobě následující události, nebo ne. Jejich iterval je totiž většinou tak krátký, že ho lidské oko nepostřehne a terminálu, který pracuje s proudem dat nevadí, že je sekvence takto rozdělená.
\u0007 - zalomení řádku. Vložit jako samostatnou událost
\u001b]0; - reset řádku ideální bod pro první událost
\u001b[H\u001b[2J\u001b[3J - reset okna
dumpPříkazem lze odbarvit RAW, produkovaný aplikací asciinema
pausePříkaz mění interval zobrazení události identifikované řádkem tím, že upraví – podle předaného poměru její interval vůči intervalu zobrazení předchozí události. Nedělá tedy žádné změny v časování následujících událostí, jako to dělá akce clip.
~$ asciiframe.sh event ascii.cast pause 10 1
Nastaví čas události z řádku č. 10 tak, aby se zobrazila bezprostředně za předchozí událostí, která je na řádku č. 9. Tím se její čas zobrazení zvětší prakticky o celý její interval zobrazení. Což ničemu nevadí, protože na terminálu budou zobrazeny obě dvě. Naopak v následujícím případě se posune těsně za událost následující, což prodlouží interval, než se zobrazí.
~$ asciiframe.sh event ascii.cast pause 10 0
Pokud chceme její interval zobrazení pouze zkrátit, lze použít číslo v rozmezí 0.0001 až 0.9999. Při hodnotě 0.5, se nastaví čas tak, že budou oba intervaly stejně dlouhé.
Tímto způsobem lze postupně posunout časy všech událostí, aniž by bylo nutné použít akci clip a tím vytvořit časový prostor pro akci split, pokud chceme rozdělit pouze jednu událost.
do_event()
{
[ ${DEBUG} ] && \
echo "INFO: ${1} - ${2} - ${3}" >> /dev/stderr
local COMMAND
local CONTEXT
local EVENT
local INDEX
local ITEM
local NEXT
local PREV
local TEST
local TIME
case "${1}" in
decolor|\
delete|\
merge) COMMAND=${1}
[ ${DEBUG} ] && \
echo "INFO: do_event: ${1} - ${2} - ${3}" >> /dev/stderr
[ ${COMMAND} == "decolor" ] && \
[ -z "${2}" ] && \
print_monochrome && \
return
[ ${COMMAND} == "delete" ] && \
[ -z "${2}" ] && \
help event_delete && \
return 1
[ ${COMMAND} == "merge" ] && \
[ -z "${2}" ] && \
help event_merge && \
return 1
case $(value ${2}) in
inte|nint) LINE=$(line_by_inte ${2})
[ $? -eq 1 ] && \
echo ${LINE} && \
return 1
[ ${DEBUG} ] && \
echo "INFO: do_event() ${1} line ${LINE}" >> /dev/stderr
shift && shift
;;
flow|nflo|time) LINE=$(line_by_time ${2})
[ $? -eq 1 ] && \
echo ${LINE} && \
return 1
[ ${DEBUG} ] && \
echo "INFO: do_event() ${1} line ${LINE} by time ${2}" >> /dev/stderr
shift && shift
;;
rang) TEST=(${2/-/ })
LINE=$(line_by_inte ${TEST[0]})
[ $? -eq 1 ] && \
echo ${LINE} && \
return 1
ENDLINE=$(line_by_inte ${TEST[1]})
[ $? -eq 1 ] && \
echo ${ENDLINE} && \
return 1
[ ${DEBUG} ] && \
echo "INFO: do_event() ${1} by scope from ${LINE} to ${ENDLINE}" >> /dev/stderr
shift && shift
;;
stri) echo TODO-delete-string
return 1
;;
esac
case ${COMMAND} in
decolor) event_decolor ${1}
;;
delete) if [ -z "${1}" ] ; then
[ ${DEBUG} ] && \
echo "INFO: do_event() go to ${1}" >> /dev/stderr
delete_line
else
delete_line ${1}
fi
;;
merge) sed -n ${LINE},${ENDLINE}p ${RECORDFILE} | events_merge
echo ${MESSAGE} >> /dev/stderr
;;
esac
return
;;
dump) [ ${DEBUG} ] && \
echo "INFO: do_event: ${1} - ${2}" >> /dev/stderr
case $(value ${2}) in
file) cat ${2} | decolor
;;
*) ${ASCIINEMA} cat ${RECORDFILE} | decolor
;;
esac
;;
pause)Při hodnotě 0 posune dopředu, těsně za následující událost
Při hodnotě 1 posune dozadu, těsně za předchozí událost
case $(value ${2}) in
inte|nint) LINE=$(line_by_inte ${2})
;;
time|nflo|flow) LINE=$(line_by_time ${2})
;;
esac
if [ -z "${3}" ] ; then
get_view context ${LINE}
echo "Missing parameter. 1 move event to before item time and 0 to next. Value 0.5 set to middle" >> /dev/stderr
while true ; do
read -p "(ANO/NE) : " yn
read -er yn
case $yn in
0|1|0.[0-9]*) case $yn in
1) POMER="1000000"
;;
0) POMER="0"
;;
*) POMER=$(float_to_string ${yn})
;;
esac
break
;;
[NnQqCc]*)N - no, Q - quit, C - cancel, S - storno
return 1
;;
*) echo "For interruption write s, q or c. For continue must be value 0, 1 or float as 0.5" >> /dev/stderr ;;
esac
done
fi
case $(value ${3}) in
flow) POMER=$(float_to_string ${3})
;;
inte) case ${3} in
1) POMER="1000000"
;;
0) POMER="0"
;;
*) echo "ERROR: do_event() ${1} - Accepted 0, 1 or float as 0.5 not ${2}" >> /dev/stderr
return 1
;;
esac
;;
esac
if [ "${POMER}" -lt "999999" ] && [ "${POMER}" -gt "0" ] ; then
#echo "Dělím interval řádku ${LINE}"
INTERVAL=$(get_view context ${LINE})
INTERVAL=$(float_to_string ${INTERVAL%% *})
TIME=$(( $(time_by_line $((${LINE} - 2)) ) + ${INTERVAL} -
$((${INTERVAL} * ${POMER} / 1000000))
))
elif [ "${POMER}" -eq "1000000" ] ; then
echo "Nastavit čas těsně před předcházející událost"
TIME=$(( $(time_by_line $((${LINE} - 2))) + 1 ))
elif [ "${POMER}" -eq "0" ] ; then
echo "Nastavit čas těsně před následující událost"
TIME=$(( $(time_by_line ${LINE}) - 1 ))
else
echo "ERROR: do_event() ${1} - Value ${2} is not acceptable" >> /dev/stderr
return 1
fi
(
${ED} "${RECORDFILE}" -q -s 2>/dev/null <<-EOF
${LINE}p
q
EOF
) >> ${TEMP_DIR}/time_event
jiná událost, jiný soubor
EVENT=$(head -1 ${TEMP_DIR}/time_event)
TIME=$(float_from_string ${TIME})
echo ${EVENT} >> /dev/stderr
anone "[1mChange time this event from line ${LINE} to ${TIME}?[0m"
[ $? -eq 1 ] && \
return
change time
${ED} "${RECORDFILE}" -s 2>/dev/null <<-EOF
${LINE}
.s/[^,]*\,/[${TIME},/1
w
q
EOF
return
;;
replace)nahradí řetězec jiným
echo TODO-event-replace
;;
split)
case $(value ${2}) in
inte) split_events ${2} ${3}
;;
esac
;;
text)Pro vyhledání řetězce rozděleného mezi více událostí
je potřeba zbavit text obarvení, zalomení a podobných
blbostí, pak scelit jednoznakové sekvence.
Je taky nutné ošetřit přes sed zpětná lomítka
if [ -z "${2}" ] ; then
print_monochrome | ${SED} 's/\\/\\\\/g' | events_merge
else
print_monochrome | ${SED} 's/\\/\\\\/g' | events_merge | grep --color $(get_string ${2})
fi
return
;;
time)
if [ ${3} ] ; then
case $(value ${3}) in
inte)one event
čas může být stejný jako před za
LINE=${3}
PREV=$(time_by_line $((${3} - 1 )) )
NEXT=$(time_by_line ${3})
[ ${DEBUG} ] && \
echo "INFO: do_event() ${1} ${3} from ${PREV} to ${NEXT}" >> /dev/stderr
;;
rang)more events
TEST=(${3/-/ })
LINE=$(line_by_inte ${TEST[0]})
[ $? -eq 1 ] && \
echo ${LINE} && \
return 1
PREV=$(time_by_line $((${LINE} - 1 )) )
ENDLINE=$(line_by_inte ${TEST[1]})
[ $? -eq 1 ] && \
echo ${ENDLINE} && \
return 1
NEXT=$(time_by_line ${ENDLINE})
[ ${DEBUG} ] && \
echo "INFO: do_event() ${1} ${3} from ${PREV} to ${NEXT}" >> /dev/stderr
;;
*) echo "ERROR: do_event() ${1} require LINE or RANG" >> /dev/stderr
return 1
;;
esac
case $(value ${2}) in
inte) TIME=$(time_by_line ${2}) ;;
flow) TIME=$(float_to_string ${2}) ;;
time) TIME=$(float_to_string $(print_seconds ${2})) ;;
esac
if [ "${TIME}" -ge "${PREV}" ] && [ "${TIME}" -le "${NEXT}" ] ; then
TIME=$(float_from_string ${TIME})
výkonná funkce
same_time
else
echo "ERROR: do_event() ${1} - ${2} is out of scope $(float_from_string ${PREV}) - $(float_from_string ${NEXT})" >> /dev/stderr
return 1
fi
else
if [ "${2//:/}" == "${2}" ] ; then
print_time ${2}
else
print_seconds ${2}
fi
fi
;;
esac
[ ${DEBUG} ] && \
echo "INFO:do_event() $1:$2:$3" >> /dev/stderr
return
}
TODO
do_merge () {
projede asciicast a sloučí jednoznakové eventy se stejným časem
[ ${DEBUG} ] && \
echo "INFO: splittext() ${1} - ${2} - ${3}" >> /dev/stderr
local line
local MESS
local TIME
x=0
${JQ} -c '[.[] ] | .[2] | tostring' ${RECORDFILE} | sed 's/\\/\\\\/g' | while read line ; do
((x++))
[ $x -eq 1 ] && [ -z "${1}" ] && sed -n 1p ${RECORDFILE} && continue
if [ -z "${1}" ] ; then
echo >> /dev/null
case ${#line} in
3) if [ -z "${BEFORE}" ] ; then
BEFORE="${LINES[$((x-2))]}"
MESS="${line:1}"
else
if [ "${BEFORE}" == "${LINES[$((x-2))]}" ] ; then
MESS="${MESS::-1}${line:1}" >> /dev/stderr
else
echo "[$(float_from_string ${BEFORE:1}), \"o\", \"${MESS::-1}\"]"
BEFORE="${LINES[$((x-2))]}"
MESS="${line:1}"
fi
fi
;;
*) if [ -z "${MESS}" ] ; then
echo >> /dev/null
else
echo "[$(float_from_string ${BEFORE:1}), \"o\", \"${MESS::-1}\"]"
BEFORE=''
MESS=''
fi
case ${LINES[$((x-2))]:0:1} in
m|r) sed -n ${x}p ${RECORDFILE}
;;
*) echo "[$(float_from_string ${LINES[$((x-2))]:1}), \"o\", ${line}]"
;;
esac
;;
esac
else
echo param >> /dev/stderr
fi
done
return
}
vložení loga: print logo, které je přes více řádků... za každým je zalomit \u001b ECMAScript6 \033 POSIX (oktál) \01b (hexa) ESC[ Přepnutí x termu na alternativní screen ?1049h a skok zpátky ?1049l xterm má privátní módy 1047,1048 a 1049 Přepnutí ?1h a 1B= jsou pro VT100 pak je sekvence pro clear H-2J-3J
TODO
Vloží do asciicastu sekvenci, která zalomí text a pokračuje ve výpisu na dalším řádku. Pokud se vloží tato událost mezi jednoznakové události, které vypisují text na obrazovku, lze ji přidat bez dalšího textu. Takto:
~$ asciiframe.sh set ascii.cast breakline 120
Ale můžeme využít tuhle událost i k vložení pokračování textu komentáře, který je přes celou šířku terminálu
TODO
~$ asciiframe.sh set ascii.cast color 120 "1;39"
Vloží sekvenci, která zapne tučný bílý text
~$ asciiframe.sh set ascii.cast color 123 reset
Vypne veškeré zvýraznění
markerTODO
resizeTODO
linePříkaz umožňuje měnit typ události identifikované číslem řádku:
~$ asciiframe.sh view ascii.cast m
...
10: 4: [2.580000, "m", "# 4"]
~$ asciiframe.sh set ascii.cast line 10 o
~$ asciiframe.sh view ascii.cast 10
10: 6: [2.580000, "o", "# 4"]
Toto je ukázkový příklad, jak změnit událost typu m
(marker) na zobrazitelnou událost. Stejným způsobem lze změnit
naopak událost typu o na marker, pomocí příkazu marker jí změnit obsah a poté
přepnout, tak, aby z ní byla opět zobrazitelná událost
TODO - sjednotit syntaxi příkazů
do_set () {
projede asciicast a sloučí jednoznakové eventy se stejným časem
[ ${DEBUG} ] && \
echo "INFO: do_set() COMMAND ${1} TARGET ${2} CONTENT ${@}" >> /dev/stderr
local i
local x
local EVENTS
local INDEX
local MESSAGE
local LINE
local TIME
local TEST
case "${1}" in
breakline|color|clear|comment|commentline|root|user|newline) case $(value ${2}) in
inte) LINE=$(line_by_inte ${2})
[ $? -eq 1 ] && \
echo ${LINE} && \
return 1
TIME=$(time_by_line $((${LINE} - 1)))
TIME=$(float_from_string $(( $(( $((
$(time_by_line ${LINE}) - ${TIME}
)) / 2 )) + ${TIME} )) )
;;
time) TIME=$(float_strip $(print_seconds ${2}))
;;
flow) TIME=$(float_unify ${2})
;;
esac
sestavení řetězce vkládaného na čas ${TIME}
case ${1} in
breakline) shift ; shift
STRING="$(get_string breakline)${@}"
;;
clear) shift ; shift
clear command effect
STRING="\u001b$(get_string color 0)"
STRING="${STRING}\u001b$(get_string H)"
STRING="${STRING}\u001b$(get_string J 2)"
;;
clear) shift ; shift
set color or another change of text properties
STRING="\u001b$(get_string color ${@})"
;;
comment) shift ; shift
komentář kontroluje aktuální šířku terminálu
for ((x=0;x<LINE;x++)) ; do
[ ${LINES[$x]:0:1} == "r" ] && RESIZE=$(($x - 1))
done
if [ ${RESIZE} ] ; then
SIZE=($(get_resize ${RESIZE}))
WIDTH=${SIZE[1]}
else
WIDTH=$(head -1 ${TEMP_DIR}/width)
fi
STRING="$(get_string newline)"
STRING="${STRING}\u001b$(get_string cursor-show 2004)"
STRING="${STRING}\r"
STRING="${STRING}\u001b$(get_string cursor-hide 2004)"
zkráceno podle aktuální šířky terminálu
TEST="${@}"
STRING="${STRING}> ${TEST:0:$((${WIDTH} - 2))}"
STRING="${STRING}"
;;
commentline) shift ; shift
break a long string
TEST="${@}"
if [ -z "${TEST}" ] ; then
STRING="> "
else
komentář kontroluje aktuální šířku terminálu
for ((x=0;x<LINE;x++)) ; do
[ ${LINES[$x]:0:1} == "r" ] && RESIZE=$(($x - 1))
done
if [ ${RESIZE} ] ; then
SIZE=($(get_resize ${RESIZE}))
WIDTH=${SIZE[1]}
else
WIDTH=$(head -1 ${TEMP_DIR}/width)
fi
STRING="$(get_string breakline)${@}"
zkráceno podle aktuální šířky terminálu
STRING="$(get_string newline)> ${TEST:0:$((${WIDTH} - 2))}"
fi
;;
newline) shift ; shift
newline after event
STRING="${@}$(get_string newline)"
;;
root) shift ; shift
STRING="\u001b$(get_string enter)"
STRING="${STRING}root@${@}: ~"
STRING="${STRING}$(get_string breakline)#root@{@}:~# "
;;
user) shift ; shift
STRING="\u001b$(get_string enter)"
STRING="${STRING}${@}: ~"
STRING="${STRING}$(get_string breakline)${@}:~$ "
;;
esac
for i in ${!LINES[@]} ; do
if [ ${LINES[i]:1} -ge $(float_to_string ${TIME}) ] ; then
LINE=$((${i} + 2))
(
${ED} "${RECORDFILE}" -q -s 2>/dev/null <<-EOF
${LINE}p
q
EOF
) >> ${TEMP_DIR}/new_event
jiná událost, jiný soubor
EVENT=$(head -1 ${TEMP_DIR}/new_event)
echo ${EVENT} >> /dev/stderr
TEXT="Set new event with time ${TIME}"
TEXT="${TEXT} before this event from line ${LINE}?"
anone "[1m${TEXT}[0m"
[ $? -eq 1 ] && return
${ED} "${RECORDFILE}" 2>/dev/null <<-EOF
${LINE}p
i
[${TIME}, "o", "${STRING}"]
.
w
q
EOF
return
fi
done
if [ ${TIME} ] ; then
anone "[1mAdd as a new event to end of file?[0m"
[ $? -eq 1 ] && return
${ED} "${RECORDFILE}" 2>/dev/null <<-EOF
$p
a
[${TIME}, "o", "${STRING}"]
.
w
q
EOF
return
fi
return 1
;;
duration)Nastavení duration podle poslední události
case ${3} in
inte)microseconds 84000000
DURATION=${3}
;;
flow)time in seconds 84.0
DURATION=$(float_from_string ${3})
;;
time)human time as 1:24
DURATION=$(print_seconds ${3})
;;
*)set duration by time of the last event
DURATION=${LINES[-1]:1}
;;
esac
float_strip $(float_from_string ${DURATION}) > ${TEMP_DIR}/duration
TEST=$(header_print)
${ED} "${RECORDFILE}" -q -s 2>/dev/null <<-EOF
1i
${TEST}
.
2d
w
q
EOF
;;
marker)
case $(value ${2}) in
inte) ORDER=${2}
x=0
for i in ${!LINES[@]} ; do
[ "${LINES[$i]:0:1}" == "m" ] && \
((x++)) && \
echo "$x ${LINES[$i]}" >> /dev/stderr
[ "${LINES[$i]:0:1}" == "m" ] && \
((x+1)) && \
[ ${x} == ${ORDER} ] && \
TIME=$(float_from_string ${LINES[$i]:1}) && \
break
done
echo ${TIME} >> /dev/stderr
[ $i -eq ${#LINES[@]} ] && \
echo "Marker with order ${ORDER} not exists" && \
return 1
;;
time) TIME=$(print_seconds ${2})
;;
flow) TIME=${2}
;;
esac
[ -z "${TIME}" ] && echo "ERROR: do_set() ${1} - ${RECORDFILE} has count of markers less than ${2}" && \
return 1
shift && shift && MESSAGE="${@}"
[ ${DEBUG} ] && \
echo "INFO: do_set() call set_marker() - TIME ${TIME} - ${MESSAGE}" >> /dev/stderr
set_marker ${TIME} "${MESSAGE}"
;;
resize)
case $(value ${2}) in
inte) ORDER=${2}
x=0
for i in ${!LINES[@]} ; do
[ "${LINES[$i]:0:1}" == "r" ] && \
((x++)) && \
echo "$x ${LINES[$i]}" >> /dev/stderr
[ "${LINES[$i]:0:1}" == "m" ] && \
((x+1)) && \
[ ${x} == ${ORDER} ] && \
TIME=$(float_from_string ${LINES[$i]:1}) && \
break
done
echo ${TIME} >> /dev/stderr
[ $i -eq ${#LINES[@]} ] && \
echo "Resize with order ${ORDER} not exists" && \
return 1
;;
time) TIME=$(print_seconds ${2})
;;
flow) TIME=${2}
;;
esac
[ -z "${TIME}" ] && echo "ERROR: do_set() ${1} - ${RECORDFILE} has count of resize events less than ${2}" && \
return 1
shift && shift && MESSAGE="${@}"
if ${MESSAGE} =~ [[:digit:x:digit: ]] ; then
[ ${DEBUG} ] && \
echo "INFO: do_set() TIME ${TIME} - ${MESSAGE}" >> /dev/stderr
set_resize ${TIME} "${MESSAGE}"
else
echo "ERROR: do_set() ${1} - Invalid format ${MESSAGE}" >> /dev/stderr
return 1
fi
;;
line) case $(value ${2}) in
inte) [ ${DEBUG} ] && \
echo "INFO: do_set() ${1} set line ${2} to type ${3}" >> /dev/stderr
case ${3} in
m|o)
(
${ED} "${RECORDFILE}" -q -s 2>/dev/null <<-EOF
${2}p
q
EOF
) >> ${TEMP_DIR}/before_event
jiná událost, jiný soubor
EVENT=$(head -1 ${TEMP_DIR}/before_event)
echo ${EVENT} >> /dev/stderr
anone "[1mSet this event from line ${2} to type \"${3}\"?[0m"
[ $? -eq 1 ] && \
return
change type
${ED} "${RECORDFILE}" -s 2>/dev/null <<-EOF
${2}s/\"[mo]\"/\"${3}\"/
.
w
q
EOF
;;
esac
;;
esac
;;
size)změna výchozí velikosti terminálu
[ ${DEBUG} ] && \
echo "INFO: do_set() ${2} - ${@}" >> /dev/stderr
shift
WSIZE=${@}
if ${WSIZE} =~ [[:digit:x:digit: ]] ; then
[ ${DEBUG} ] && \
echo "INFO: do_set() default size ${@}" >> /dev/stderr
echo ${WSIZE#*x} > ${TEMP_DIR}/height
echo ${WSIZE%x*} > ${TEMP_DIR}/width
else
echo "ERROR: do_set() ${1} - Invalid size format (WIDTHxHEIGHT): ${@}" >> /dev/stderr
return 1
fi
TEST=$(header_print)
#echo ${TEST} >> /dev/stderr
${ED} "${RECORDFILE}" -q -s 2>/dev/null <<-EOF
1i
${TEST}
.
2d
w
q
EOF
return
;;
title)Editace title asciicastu
[ ${DEBUG} ] && \
echo "INFO: do_set() ${2} - ${@}" >> /dev/stderr
shift
echo "${@}" > ${TEMP_DIR}/title
TEST=$(header_print)
#echo ${TEST} >> /dev/stderr
${ED} "${RECORDFILE}" -q -s 2>/dev/null <<-EOF
1i
${TEST}
.
2d
w
q
EOF
;;
*) [ ${HELP} ] && help ${1} || help
;;
esac
return
}
Výkonná funkce příkazu time, která přebírá nastavené proměnné LINE, ENDLINE a TIME a zapisuje do souboru události, co mají nastaven stejný čas - tím pádem se zobrazí ve stejný okamžik.
Rozsah těchto událostí lze vyhledat přes scope (akce view) a změnit jejich časování lze pomocí příkazu pause
same_time () {
[ ${DEBUG} ] && \
echo "INFO: same_time() - LINE ${LINE} ENDLINE ${ENDLINE} ${TIME}" >> /dev/stderr
if [ -z "${ENDLINE}" ] ; then
[ ${DEBUG} ] && \
echo "INFO: same_time() ${1} line ${LINE}" >> /dev/stderr
(
${ED} "${RECORDFILE}" -q -s 2>/dev/null <<-EOF
${LINE}p
q
EOF
) >> ${TEMP_DIR}/same_event
jiná událost, jiný soubor
EVENT=$(head -1 ${TEMP_DIR}/same_event)
echo ${EVENT} >> /dev/stderr
anone "[1mChange time of this event from line ${LINE} to ${TIME}? For continue push 'y' key[0m"
[ $? -eq 1 ] && \
return
change time
${ED} "${RECORDFILE}" -s 2>/dev/null <<-EOF
${LINE}
.s/[^,]*\,/[${TIME},/1
w
q
EOF
else
[ ${DEBUG} ] && \
echo "INFO: same_time() ${1} by scope from ${LINE} line to ${ENDLINE}" >> /dev/stderr
[ ${ENDLINE} -lt $((${LINE} + 1)) ] && \
echo "ERROR: same_time() - Invalid scope for ${1} (${LINE} - ${ENDLINE})" >> /dev/stderr && \
return 1
sed -n ${LINE},${ENDLINE}p ${RECORDFILE}
anone "[1mSet for this scope ${LINE}-${ENDLINE} time to ${TIME}? For continue push 'y' key.[0m" >> /dev/stderr
[ $? -eq 1 ] && \
return
${ED} "${RECORDFILE}" 2>/dev/null <<-EOF
${LINE},${ENDLINE}s/[^,]*\,/[${TIME},/1
w
q
EOF
fi
return
}
Funkce přebere řetězec s obsahem zobrazované události a následně vrací na znaky a speciální sekvence. Takže místo řetězce...
"\u001b[?2004h\u001b]0;user@stroj:~$"
vrátí sérii..
"
\u001b[?2004h
\u001b]0;
u
s
e
r
@
s
t
r
o
j
:
~
$
"
Kterou lze dále zpracovat
sm() {
[ ${DEBUG} ] && echo "INFO: sm() ${#@}" >> /dev/stderr
local BARVA
local BEGIN
local END
local SPLIT
SPLIT="${@}"
BEGIN=0
END=${#SPLIT}
for ((i=BEGIN;i<END;i++)); do
case ${SPLIT:${i}:1} in
' ') echo ' '&& i=$((i+1))
;;
'*') echo '&asterisk;'&& i=$((i+1))
;;
'.') echo '˙'&& i=$((i+1))
;;
'\') case ${SPLIT:${i}:2} in
'\\'|\
'\"'|\
'\b'|\
'\n'|\
'\r'|\
'\t') echo ${SPLIT:${i}:2} && i=$((i+1))
;;
'\u') case ${SPLIT:${i}:6} in
'\u001b')
case ${SPLIT:${i}:7} in
'\u001b]') case ${SPLIT:${i}:9} in
'\u001b]'[0-9]';') echo ${SPLIT:${i}:9} && i=$((i+8))
;;
esac
;;
'\u001b[') i=$((i+7))
case ${SPLIT:${i}:1} in
'?')
i=$((i+1))
case ${SPLIT:${i}:5} in
[1-2][0-9][0-9][0-9][lhsr])
echo "\u001b[?${SPLIT:${i}:5}" && \
i=$((i+4))
;;
esac
case ${SPLIT:${i}:4} in
[0-9][0-9][0-9][lhsr])
echo "\u001b[?${SPLIT:${i}:4}" && \
i=$((i+3))
;;
esac
case ${SPLIT:${i}:3} in
[0-9][0-9][lhsr])
echo "\u001b[?${SPLIT:${i}:3}" && \
i=$((i+2))
;;
esac
case ${SPLIT:${i}:2} in
[0-9][lhsr])
echo "\u001b[?${SPLIT:${i}:2}" && \
i=$((i+1))
;;
esac
;;
[0-9])
AA - posun kurzoru nahoru o počet řádek
B - posun kurzoru dolů o počet řádek
C - posun kurzoru kupředu o N sloupců (doprava)
D - posun kurzoru zpět (doleva) o N sloupců
BARVA - test sekvence pro zvýraznění
TAB
REC
HAC - posun kurzoru na pozici L;C
JOKL
EL
HAKL
NEXT=${SPLIT:${i}}
AA="${NEXT%%A*}A"
BARVA="${NEXT%%m*}m"
TAB="${NEXT%%t*}t"
REC="${NEXT%%r*}r"
HAC="${NEXT%%H*}H"
JOKL="${NEXT%%J*}J"
EL="${NEXT%%l*}l"
HAKL="${NEXT%%h*}h"
[ -z "${AA//[0-9A]/}" ] && echo "\u001b[${AA}" && i=$((i+${#AA}-1))
[ -z "${BARVA//[0-9m\\;\[]/}" ] && echo "\u001b[${BARVA}" && i=$((i+${#BARVA}-1))
[ -z "${TAB//[0-9t\\;\[]/}" ] && echo "\u001b[${TAB}" && i=$((i+${#TAB}-1))
[ -z "${REC//[0-9r\\;\[]/}" ] && echo "\u001b[${REC}" && i=$((i+${#REC}-1))
[ -z "${HAC//[0-9H\\;\[]/}" ] && echo "\u001b[${HAC}" && i=$((i+${#HAC}-1))
[ -z "${JOKL//[0-9J\\;\[]/}" ] && echo "\u001b[${JOKL}" && i=$((i+${#JOKL}-1))
[ -z "${EL//[0-9l]/}" ] && echo "\u001b[${EL}" && i=$((i+${#EL}-1))
[ -z "${HAKL//[0-9h]/}" ] && echo "\u001b[${HAKL}" && i=$((i+${#HAKL}-1))
Rozseknutá barva
TEST="${SPLIT:${i}:-1}"
[ -z "${TEST//[0-9]\\;ub\[]/}" ] && echo "${TEST}" && i=$((i+${#TEST}-1))
;;
A|H|J|K|m) echo "\u001b[${SPLIT:${i}:1}"
;;
*)
;;
esac
;;
'\u001b(') echo ${SPLIT:${i}:8} && i=$((i+7))
;;
'\u001b"') echo ${SPLIT:${i}:6} && i=$((i+5))
;;
'\u001b'[0-9]|\
'\u001b='|\
'\u001b>') Záznam 442750.cast – https://github.com/zechris/asciinema-rec_script
obsahuje sekvence
\u001b7 (0.030350) a
\u001b8 (0.388992) obsahuje
a také rozdělenou sekvenci
pro nastavení barvy mezi
událostí spouštěnou v 0.403062 a
událostí v čase 0.403087
echo ${SPLIT:${i}:7} && i=$((i+6))
;;
esac
;;
'\u0007') echo ${SPLIT:${i}:6} && i=$((i+5))
;;
esac
;;
*) echo "Neošetřeno ${SPLIT:${i}:2}"
;;
esac
;;
*) echo "${SPLIT:${i}:1}"
;;
esac
done
return
}
Značky typu m se přidávají před
události typu o ale tahle funkce je vyhledává od začátku
souboru, takže pokud se stane, že se v souboru vyskytnout dvě (a více)
událostí tohoto typu, těsně za sebou ve stejném čase bude druhotně
editována jen značka, která je na první pozici.
Jak se to stane?
$ cat ascii.cast
...
[2.517264, "o", "t"]
[3.372158, "o", "\r\n"]
...
$ asciiframe.sh set 2.6 marker '# 1'
$ asciiframe.sh set 3.373 marker '# 2'
$ cat ascii.cast
...
[2.517264, "o", "t"]
[2.6, "m", "# 1"]
[3.372158, "o", "\r\n"]
[3.373, "m", "# 2"]
...
$ asciiframe view demo.cast m
8: 1: [2.6, "m", "# 1"]
11: 2: [3.373, "m", "# 2"]
...
$ asciiframe.sh set 2 marker '# 3'
[2.517264, "o", "t"] [2.6, "m", "# 1"] [2.6, "m", "# 2"] [2.6, "m", "# 3"] [3.372158, "o", "\r\n"]
[2.517264, "o", "t"] [2.6, "m", "# 1"] [2.6, "m", "# 2"] [2.6, "m", "# 3"] [3.372158, "o", "\r\n"]
---
TODO
set_marker () {
[ ${DEBUG} ] && \
echo "INFO: set_marker ${1} - ${2}" >> /dev/stderr
local i
local x
local LINE
local EVENT
local TEST
TIME=${1}
time format unify and strip zero's from end
TIME=$(float_unify ${TIME})
(
${ED} "${RECORDFILE}" -q -s 2>/dev/null <<-EOF
/${TIME}, "m/
.=
q
EOF
) >> ${TEMP_DIR}/event
LINE=$(tail -1 ${TEMP_DIR}/event)
EVENT=$(head -1 ${TEMP_DIR}/event)
if [ "${EVENT}" == "?" ] ; then
add new marker
x=1
TEST=$(float_to_string ${TIME})
for i in ${!LINES[@]} ; do
((x++))
[ ${DEBUG} ] && \
echo "INFO: set_marker() check $x ${LINES[$i]:1} ${TEST}" >> /dev/stderr
[ ${LINES[$i]:1} -gt ${TEST} ] && \
LINE="$x" && \
break
done
(
${ED} "${RECORDFILE}" -q -s 2>/dev/null <<-EOF
${LINE}p
q
EOF
) >> ${TEMP_DIR}/before_event
jiná událost, jiný soubor
EVENT=$(head -1 ${TEMP_DIR}/before_event)
if [ "${EVENT/\"m\"/}" == "${EVENT}" ] ; then
echo ${EVENT} >> /dev/stderr
anone "Set a new marker with the description \"${2}\" for time ${TIME} sec? Event will be set before line ${LINE} which was showed"
[ $? -eq 1 ] && \
return
add new event
${ED} "${RECORDFILE}" -s 2>/dev/null <<-EOF
${LINE}
i
[${TIME}, "m", "${2}"]
.
w
q
EOF
else
následující událost je typu marker
TIME=$(float_unify ${TIME})
echo ${EVENT} >> /dev/stderr
anone "[1mChange the time for this marker from line ${LINE} to ${TIME} sec. and set as description \"${2}\"?[0m"
[ $? -eq 1 ] && \
return
replace event
${ED} "${RECORDFILE}" -s 2>/dev/null <<-EOF
${LINE}
d
i
[${TIME}, "m", "${2}"]
.
w
q
EOF
fi
else
echo "${EVENT}" >> /dev/stderr
anone "[1mChange description of the marker to \"${2}\"?[0m"
[ $? -eq 1 ] && \
return
replace event
${ED} "${RECORDFILE}" -s 2>/dev/null <<-EOF
${LINE}
d
i
[${TIME}, "m", "${2}"]
.
w
q
EOF
fi
return
}
set_resize () {
[ ${DEBUG} ] && \
echo "INFO: set_resize ${1} - ${2}" >> /dev/stderr
local i
local x
local LINE
local EVENT
local TEST
TIME=${1}
time format unify and strip zero's from end
TIME=$(float_unify ${TIME})
(
${ED} "${RECORDFILE}" -q -s 2>/dev/null <<-EOF
/${TIME}, "r/
.=
q
EOF
) >> ${TEMP_DIR}/event
LINE=$(tail -1 ${TEMP_DIR}/event)
EVENT=$(head -1 ${TEMP_DIR}/event)
if [ "${EVENT}" == "?" ] ; then
add new resize event
x=1
TEST=$(float_to_string ${TIME})
for i in ${!LINES[@]} ; do
((x++))
[ ${DEBUG} ] && \
echo "INFO: set_resize() check $x ${LINES[$i]:1} ${TEST}" >> /dev/stderr
[ ${LINES[$i]:1} -gt ${TEST} ] && \
LINE="$x" && \
break
done
(
${ED} "${RECORDFILE}" -q -s 2>/dev/null <<-EOF
${LINE}p
q
EOF
) >> ${TEMP_DIR}/before_event
jiná událost, jiný soubor
EVENT=$(head -1 ${TEMP_DIR}/before_event)
if [ "${EVENT/\"r\"/}" == "${EVENT}" ] ; then
echo ${EVENT} >> /dev/stderr
anone "Set a new resize event \"${2}\" for time ${TIME} sec? Event will be set before line ${LINE} which was showed"
[ $? -eq 1 ] && \
return
add new resize event
${ED} "${RECORDFILE}" -s 2>/dev/null <<-EOF
${LINE}
i
[${TIME}, "r", "${2}"]
.
w
q
EOF
else
následující událost je typu resize
TIME=$(float_unify ${TIME})
echo ${EVENT} >> /dev/stderr
anone "[1mChange the time for this resize event from line ${LINE} to ${TIME} sec. and change value to \"${2}\"?[0m"
[ $? -eq 1 ] && \
return
replace event
${ED} "${RECORDFILE}" -s 2>/dev/null <<-EOF
${LINE}
d
i
[${TIME}, "r", "${2}"]
.
w
q
EOF
fi
else
echo "${EVENT}" >> /dev/stderr
anone "[1mChange value of the resize event to \"${2}\"?[0m"
[ $? -eq 1 ] && \
return
replace event
${ED} "${RECORDFILE}" -s 2>/dev/null <<-EOF
${LINE}
d
i
[${TIME}, "r", "${2}"]
.
w
q
EOF
fi
return
}
Funkce sloučí události
split_events()
{
if [ "${1}" -lt "2" ] || [ "${1}" -gt "$((${#LINES[@]}+1))" ] ; then
echo "ERROR: Line ${1} is out of range. The last line ${RECORDFILE} is $((${#LINES[@]}+1))" >> /dev/stderr
return 1
fi
rozděluje na sérii událostí
case $(value ${2}) in
flow) TEMP=${2}
;;
*) TEMP="0.1"
esac
IDLE=$(float_to_string ${TEMP})
SPLIT=($(jq --slurp '.['$((${1}-1))'] | "\(.[0] * 1000000 | floor as $bar | $bar):\(.[2] | explode | .[] as $foo | [$foo] | implode)"' ${RECORDFILE}))
#echo ${#SPLIT[@]} >> /dev/stderr
#echo "${SPLIT[@]}" >> /dev/stderr
TEST=$(get_context ${1})
echo "TEST ${#TEST[@]} ${TEST[@]}" >> /dev/stderr
FRAME=${TEST%;*}
NEXT=${TEST##*;}
TIME=${FRAME##*:}
if [ -z "${NEXT/:/}" ] ; then
poslední řádek, mohu pokračovat
#
else
if [ ${NEXT%:*} -ge ${#LINES[@]} ] ; then
anone "The interval is too short. You can do interrupt of action and do shift the nexts events first or used decreased interval of writing. Or you want want to go ahead and aplicated the same time for all events?"
[ $? -eq 1 ] && return
#
else
TEST=$((${NEXT#*:} - ${TIME}))
SHIFT="$((${#SPLIT[@]} * ${IDLE}))"
if [ "${SHIFT}" -gt "${TEST}" ] ; then
anone "The interval is too short. You can do interrupt of action and do shift the nexts events first or used decreased interval of writing. Or you want want to go ahead and aplicated the same time for all events?"
[ $? -eq 1 ] && return
Výkonný kód - stejný čas
#
else
#
fi
fi
fi
return
}
Funkce projede asciicast a zobrazované události typu o,
pomocí funkce sm() rozděluje na
jednoznakové eventy, s tím, že pro každou speciální sekvenci i znak se
vytvoří samostatná událost, se stejným časem, pokud není předán parametr
$3, kterým se nastavuje čas prodlení mezi znaky.
Tak vzniknou rozsahy událostí, zobrazovaných ve stejný čas, co se dají vyhledat přes funkci get_scope() a po úpravách opět sloučit.
Bez parametrů zpracuje tahle funkce celý obsah souboru, bez toho, že by se dělala nějaké změny v časování.
Parametrem $1 je číslo řádku události LINE, jejímž zpracováním se má začít, a parametrem $2 číslo řádku LINE, události, kterou se má zpracování ukončit.
Parametrem $3 je časový interval, který se má připočítávat k původnímu času rozdělovaných událostí.
TODO - až budou ošetřeny všechny varianty
splittext () {
[ ${DEBUG} ] && \
echo "INFO: splittext() ${1} - ${2} - ${3}" >> /dev/stderr
local line
local MESS
local TIME
x=0
${JQ} -c '[.[] ] | .[2] | tostring' ${RECORDFILE} | sed 's/\\/\\\\/g' | while read line ; do
((x++))
[ $x -eq 1 ] && [ -z "${1}" ] && sed -n 1p ${RECORDFILE} && continue
if [ -z "${1}" ] ; then
funkce je zavolaná bez parametrů
INDEX=$(($x-2))
TYPE="${LINES[${INDEX}]:0:1}"
case ${TYPE} in
r|m) sed -n ${x}p ${RECORDFILE} && continue
;;
*) TIME=$(float_from_string ${LINES[${INDEX}]:1})
MESS=($(sm "${line}"))
for i in ${!MESS[@]} ; do
case "${MESS[$i]}" in
'"')
;;
'\u001b') SPECIAL="${MESS[$i]}"
;;
[0-9]|'['|';')\u001b[38;5;
[ -z "${SPECIAL}" ] && \
echo "[${TIME}, \"o\", \"${MESS[$i]}\"]" || \
SPECIAL="${SPECIAL}${MESS[$i]}"
;;
'm') [ -z "${SPECIAL}" ] && \
echo "[${TIME}, \"o\", \"${MESS[$i]}\"]" || \
echo "[${TIME}, \"o\", \"${SPECIAL}${MESS[$i]}\"]" && SPECIAL=''
;;
'&asterisk;') echo "[${TIME}, \"o\", \"*\"]"
;;
'˙') echo "[${TIME}, \"o\", \".\"]"
;;
' ') echo "[${TIME}, \"o\", \" \"]"
;;
*) [ -z "${SPECIAL}" ] && \
echo "[${TIME}, \"o\", \"${MESS[$i]}\"]" || \
SPECIAL="${SPECIAL}${MESS[$i]}"
;;
esac
done
;;
esac
else
echo ne
funkce je zavolaná s parametry
#TEST=$(echo -e "\x5Cu001b\x5D")
#echo ${line:1:6}
case ${line:1:2} in
'\b') ##echo "Začíná znakem back... ${line:0:8}"
sm "${line}"
;;
'\r') ##echo "Začíná znakem return... ${line:0:8}"
sm "${line}"
;;
'\n') ##echo "Začíná znakem nové řádky... ${line:0:8}"
sm "${line}"
;;
'\u')
událost začíná speciálním znakem
case ${line:1:6} in
'\u001b') case ${line:7:1} in
']')echo "Nová řádka... ${line:0:8}"
sm "${line}"
;;
'[') #echo "Zvýraznění... ${line:0:8}"
sm "${line}"
;;
*) echo "Něco jiného... ${line:6:7}"
;;
esac
;;
'\u0007') ##echo "Zalomení textu... ${line:0:8}"
sm "${line}"
;;
esac
;;
\\?) ##echo "Backslash... ${line:0:8}"
sm "${line}"
;;
*)může začínat znakem [ - rozdělené zvýraznění
case ${#line} in
3)odfiltrování jednoznakových zpráv bez speciálních znaků
;;
*) echo "XXX ${LINES[$(($x-2))]:0:1} $x: ${line:1}"
;;
esac
;;
esac
echo "YYY ${LINES[$(($x-3))]:0:1} $x: ${line:1}"
fi
done
return
}
VVV Nepoužívané funkce
Funkce zapisuje události, generované při akci set
addline () {
echo "INFO: addline() LINE ${LINE} TYPE ${TYPE} CONTENT ${@}" >> /dev/stderr
return
TIME="${1}"
TYPE="${1}"
STRING="${1#*\ }"
ed "${RECORDFILE}" 2>/dev/null <<-EOF
${LINE}p
${LINE}c
${STRING}
.
w
q
EOF
echo "Line ${LINE} was replaced" >> /dev/stderr
return
}
changeline()
{
LINE="${1%%\ *}"
STRING="${1#*\ }"
ed "${RECORDFILE}" 2>/dev/null <<-EOF
${LINE}p
${LINE}c
${STRING}
.
w
q
EOF
echo "Line ${LINE} was replaced" >> /dev/stderr
return
}
event_move()
{
echo "MARKER přesun značky ORDER ${1} na čas FLOW ${3}" >> /dev/stderr
echo "Zjišťuji řádek" >> /dev/stderr
LINE=($(item_by_flow ${1} ${3}))
echo "PRED ${LINE[@]}"
AFTERFRAME=($(frame_by_order ${1} $(( $(frame_order ${1} ${LINE[1]}) - 1 )) ))
if [ "${START[0]}" -ge "${AFTERFRAME[0]}" ] ; then
echo "Přesouvám značku ${2} na čas ${3}, před rámec ${AFTERFRAME[@]}" >> /dev/stderr
echo """
${START[0]}t${AFTERFRAME[0]}
s/${START[1]}/${3}/
$((${START[0]} + 1))d
w
q
""" | ed ${1}
else
echo "Přesouvám značku ${2} na čas ${3}, před rámec ${AFTERFRAME[@]}" >> /dev/stderr
echo """
${START[0]}t${AFTERFRAME[0]}
s/${START[1]}/${3}/
${START[0]}d
w
q
""" | ed ${1}
fi
return
}
make_clip()
{
[ ${DEBUG} ] && echo """
Funkce 'make_clip' vygeneruje klip, včetně záhlaví, který lze rovnoou
předat k přehrání aplikaci asciinema.
Časy se přepočítají podle parametru \$4 bude-li uveden
1 - soubor (file)
2 - od (int)
3 - do (int)
4 - čas (flow)
5 - nepoviný (file)
""" && \
item_list && \
return
POSUN=${4=0.0}
#echo ${HEADERSTRING}
(
item_list ${1} flow | while read line ; do
FRAME=(${line})
if [ ${FRAME[0]} -ge ${2} ] && [ ${FRAME[0]} -le ${3} ] ; then
echo "${FRAME[0]},s/${FRAME[1]}/$(plustime ${FRAME[1]} ${POSUN})/"
else
echo "IGNORE ${FRAME[@]}" >> /dev/stderr
fi
done
[ -z "${5}" ] && echo """$2,$(($3 - 1))p
q
""" || echo """1,$2d
$3,$d
w ${5}
q""" ) | ed -s "${1}"
}
${FORCE} Potlačí dotazování. U
všech operací, které zasahují do záznamů, se bude postupovat jako kdyby
byla odpověď ANO.
-h|--help Vyvolá přerušení
skriptu a zobrazí dostupnou nápovědu.
-d|--debug Zapne ukecaný mód.
Vypisované zprávy jdou na stderr.
-x Volba zobrazí logo a kec v
angličtině, o tom, co vedlo ke vzniku tohoto skriptu
case ${1} in
-f|--force) FORCE=yes && shift
;;
-h|--help) HELP=yes && shift
;;
-i|--info) INFO=yes && shift
;;
-d|--debug) DEBUG=yes && shift
;;
-v|--version) echo "${VERSION} ${AUTHOR}" >> /dev/stderr && \
exit
;;
-x) if [ ${2} ] ; then
EGG=yes && shift
else
logo
exit
fi
;;
esac
Funce ověřuje, zda předaný řetězec s názvem akce skutečně odpovídá názvu implementované akce.
$ asciiframe ACTION ascii.cast [clip.cast] COMMAND [PARAM]
Současná verze podporuje tyto akce a příkazy:
clip se používá pro export událostí do klipů a import upravených klipů zpět do asciicastu. Umožňuje také zkrátit, nebo také prodlužit interval zobrazení zobrazované události tím, že upraví časování všech událostí po ní následujících. Má pouze jeden příkaz :
merge - chybí kotva
play umožňuje přehrávání klipů, protože asciinema má sama o sobě jen omezené možnosti přehrávání: neumožňuje přehrávání od určitého bodu, neumí změnit velikost terminálu podle velikosti uvedené v hlavičce asciicastu a značky interpretuje pouze jako body, kdy přehrávání pozastaví.
set je akce která umožňuje přidávat do asciicastu nové události a měnit jejich obsah, nebo typ.
split rozdělí
zobrazované události typu o předaného asciicastu, na série jednoznakových zobrazovaných
událostí. Jaký to má význam, je rozebráno v dokumentaci akce.
Pokud předaný řetězec neodpovídá žádné implementované akci, volá se funkce help() která vypíše generickou nápovědu, kde je seznam implementovaných akcí uveden.
Při akci split se
rozdělí zobrazovaná událost typu o, podle předaných
parametrů, na sérii zobrazovaných události, které během přehrávání
vyvolají dojem, jako by text na příkazové řádce právě někdo psal. Viz
příklad
$ asciiframe.sh view ascii.cast 0.123
4: [0.100000, "o", "echo"]
$ asciiframe.sh view ascii.cast 5
5: 3: [0.356846, "o", "\r\n"]
$ asciiframe.sh split ascii.cast 4 0.001
_actions() {
case ${1} in
clip|\
edit|\
event|\
find|\
play|\
split|\
merge|\
'set'|\
view) if [ ${HELP} ] ; then
case $(value ${2}) in
stri) help ${1}_${2}
;;
*) help ${1}
;;
esac
exit
elif [ ${EGG} ] ; then
case ${1} in
event) _manual_set_poster
;;
split) _manual_export_script
;;
esac
exit
fi
;;
*) help && exit
;;
esac
}
_actions ${1} ${3}
[ ${#@} -eq 1 ] && help ${1} && exit
Formát, který používá aplikace asciinema, tzv. asciicast, je popsán v nápovědě. Jde o soubor, který má data uložená v komprimovaném JSON formátu, který je ovšem možné udržovat i ve strukturované formě, která je výhodnější při nahrazování textových řetězců, např. když chceme konkrétní uživatelské jméno v záznamu nahradit nějakou obecnou formou, atp.
Proto se hned zkraje soubor otestuje a v případě potřeby konverze se vytvoří dočasný soubor, do něhož se pomocí aplikace jq uloží obsah ve standardní komprimované formě.
if [ -f ${2} ] ; then
header_load ${2}
[ -z ${RECORDFILE} ] && \
echo "ERROR: File not asciinema record file or missing!" && \
exit 1
cast_load
[ ${DEBUG} ] && \
header_print >> /dev/stderr
else
[ ${HELP} ] && \
help file || \
echo "ERROR: File not asciinema record file or missing!" && \
exit 1
fi
Přidávám další parametry
TEST="$(value ${1})$(value ${2})$(value ${3})$(value ${4})"
case "${TEST}" in
strifile)
case ${1} in
edit)Editace title asciicastu
splittext
exit 3
;;
merge) do_merge
;;
play) if [ ${HELP} ] ; then
help ${1}
else
asciiplayer
fi
;;
'set') help "${1}"
;;
split)Rozdělení událostí asciicastu
splittext
;;
view) [ ${HELP} ] && help ${1} || get_view 1 ${#LINES[@]}
;;
*)
[ ${HELP} ] && help ${1} || help
;;
esac
;;
strifilefile)
case ${1} in
clip) include_clip $(float_from_string ${LINES[-1]:1}) 0.0 ${3}
;;
esac
;;
strifilefileflow)
case ${1} in
clip) case $(value ${5}) in
flow|\
nflo) include_clip ${4} ${5} ${3}
;;
time) include_clip ${4} $(print_seconds ${5}) ${3}
;;
*) include_clip ${4} 0.0 ${3}
;;
esac
;;
esac
;;
strifilefileinte)
case ${1} in
clip) case $(value ${5}) in
flow|\
nflo) include_clip ${4} ${5} ${3}
;;
time) include_clip ${4} $(print_seconds ${5}) ${3}
;;
*) include_clip ${4} 0.0 ${3}
;;
esac
;;
esac
;;
strifilefilestri)
case ${1} in
clip) case "${4}" in
diff) DIFF=$(command -v ${5})
if [ -x "${DIFF}" ] ; then
${DIFF} <(
${JQ} '[.[] ] | ((.[1] | tostring) + " – " + (.[2] | tostring))' ${2} |\
${SED} 's/\ –\ /\", \"/') <(
${JQ} '[.[] ] | ((.[1] | tostring) + " – " + (.[2] | tostring))' ${3} |\
${SED} 's/\ –\ /\", \"/')
else
diff -y <(
${JQ} '[.[] ] | ((.[1] | tostring) + " – " + (.[2] | tostring))' ${2} |\
${SED} 's/\ –\ /\", \"/') <(
${JQ} '[.[] ] | ((.[1] | tostring) + " – " + (.[2] | tostring))' ${3} |\
${SED} 's/\ –\ /\", \"/')
fi
;;
*) help clip_diff
esac
;;
event) do_event ${4} ${3}
dump?
;;
esac
;;
strifilefiletime)
case ${1} in
clip) TIME=$(print_seconds ${4})
if [ -z "${5}" ] ; then
echo "Vkládám klip ${3} na čas ${TIME}, s nulovým posunem časování" >> /dev/stderr
include_clip ${TIME} 0.0 ${3}
else
case $(value ${5}) in
flow|nflo)
echo "Vkládám klip ${3} na čas ${TIME}, s posunem časování ${5}" >> /dev/stderr
include_clip ${TIME} ${5} ${3}
;;
time) PAUZA= $(print_seconds ${5})
echo "Vkládám klip ${3} na čas ${TIME}, a za ním bude PAUZA ${PAUZA}" >> /dev/stderr
include_clip ${TIME} ${PAUZA} ${3}
;;
esac
fi
;;
esac
;;
strifileflow)
case ${1} in
event) help {1}
;;
clip)echo "Print clip from line identified by float time $3
START=$(float_to_string ${3})
for i in ${!LINES[@]} ; do
[ ${i} -eq 0 ] && continue
if [ "${LINES[$i]:1}" -ge "${START}" ] ; then
clip_print $(($i+1)) ${#LINES[@]}
break
fi
done
;;
view) if [ "${3//:/}" == "${3}" ] ; then
get_view ${3}
else
get_view $(print_seconds ${3}) ${4}
fi
;;
play) clip_print ${3}
| xterm ...
echo TODO
;;
esac
;;
strifileflowflow)
case ${1} in
clip)Print clip from line identified by float time $3
into line identified by float time $4
START=$(float_to_string ${3})
for i in ${!LINES[@]} ; do
[ ${i} -eq 0 ] && continue
if [ "${LINES[$i]:1}" -ge "${START}" ] ; then
END=$(float_to_string ${4})
for y in ${!LINES[@]} ; do
[ ${y} -eq 0 ] && continue
if [ "${LINES[$y]:1}" -ge "${END}" ] ; then
clip_print $(($i+1)) $(($y+1))
break
fi
done
break
fi
done
;;
view) if [ "${3//:/}" == "${3}" ] ; then
get_view ${3} ${4}
else
get_view $(print_seconds ${3}) ${4}
fi
;;
esac
;;
strifileflowinte)
case ${1} in
clip)echo "Print clip from line identified by float
time $3 into line $4
START=$(float_to_string ${3})
for i in ${!LINES[@]} ; do
[ ${i} -eq 0 ] && continue
if [ "${LINES[$i]:1}" -ge "${START}" ] ; then
clip_print $(($i+1)) ${4}
break
fi
done
;;
view) if [ "${3//:/}" == "${3}" ] ; then
get_view ${3} ${4}
else
get_view $(print_seconds ${3}) ${4}
fi
;;
play) if [ ${HELP} ] ; then
help ${1}
else
START=$(float_to_string ${3})
for i in ${!LINES[@]} ; do
[ ${i} -eq 0 ] && continue
if [ "${LINES[$i]:1}" -ge "${START}" ] ; then
clip_print $(($i+1)) ${4} | asciiplayer
break
fi
done
fi
;;
esac
;;
strifileflownflo)
case ${1} in
view) if [ "${3//:/}" == "${3}" ] ; then
get_view ${3} ${4}
else
get_view $(print_seconds ${3}) ${4}
fi
;;
play) [ ${HELP} ] && help ${1} || \
clip_print ${3} {4} | asciiplayer
;;
esac
;;
strifileflownint)
case ${1} in
view) if [ "${3//:/}" == "${3}" ] ; then
get_view ${3} ${4}
else
get_view $(print_seconds ${3}) ${4}
fi
;;
play) [ ${HELP} ] && help ${1} || \
clip_print ${3} {4} | asciiplayer
;;
esac
;;
strifileflowrang)
echo TODO >> /dev/stderr
;;
strifileflowstri)
case ${1} in
clip) clip_print ${3} ${4}
;;
*)Ostatní akce nemají obvykle za identifikátorem řetězec
help ${1}
;;
esac
;;
strifilehelp)
case ${1} in
event|\
find|\
clip|\
'set') help ${1}
;;
play)parametr --speed=
[ ${HELP} ] && help ${1} || \
PLAY="${3}" asciiplayer
;;
esac
;;
strifilehelpflow)
case ${1} in
clip) case $(value ${5}) in
flow|\
nflo) include_clip ${4} ${5} --
;;
time) include_clip ${4} $(print_seconds ${5}) --
;;
*) help clip_move
;;
esac
;;
play) [ ${HELP} ] && help ${1} || \
PLAY="${@:3}" asciiplayer
;;
esac
;;
strifilehelpinte)
case ${1} in
clip) case $(value ${5}) in
flow|\
nflo) include_clip ${4} ${5} --
;;
time) include_clip ${4} $(print_seconds ${5}) --
;;
*) help clip_move
;;
esac
;;
play) [ ${HELP} ] && help ${1} || \
PLAY="${@:3}" asciiplayer
;;
esac
;;
strifileinte)
case ${1} in
event) help ${1}
;;
clip) clip_print ${3} ${#LINES[@]}
;;
view) get_view ${3}
;;
play) [ ${HELP} ] && help ${1} || \
clip_print ${3} ${#LINES[@]} 2>/dev/null | asciiplayer
;;
esac
;;
strifileintefile)
Žádná akce nemá za identifikátorem řetězec
help ${1}
;;
strifileinteflow)
case ${1} in
clip) echo "Posouvám časování od řádky ${3} o ${4} sekund kupředu" >> /dev/stderr
clip_print ${3} ${4}
;;
view) get_view ${3} ${4}
;;
play) [ ${HELP} ] && help ${1} || \
clip_print ${3} ${4} 2>/dev/null | asciiplayer
;;
esac
;;
strifileintehelp)
case ${1} in
play) [ ${HELP} ] && help ${1} || \
clip_print ${3} ${#LINES[@]} 2>/dev/null | PLAY="${@:4}" asciiplayer
;;
esac
;;
strifileinteinte)
case ${1} in
clip) clip_print ${3} ${4}
;;
play)přehraje se pouze vybraný úsek výchozího souboru
[ ${HELP} ] && help ${1} || \
playcast ${3} ${4} | asciiplayer
;;
view) get_view ${3} ${4}
;;
esac
;;
strifileintenint)
case ${1} in
clip) clip_print ${3} ${4}
;;
view) get_view ${3} ${4}
;;
esac
;;
strifileintenflo)
case ${1} in
clip) echo "Zkracuji časování od řádky ${3} o ${4} sekund" >> /dev/stderr
clip_print ${3} ${4}
;;
view) get_view ${3} ${4}
;;
esac
;;
strifileinterang)
echo TODO >> /dev/stderr
;;
strifileintestri)
case ${1} in
clip) clip_print ${3} ${4}
;;
*)Ostatní akce nemají obvykle za identifikátorem řetězec
help ${1}
;;
esac
;;
strifilenflo)
case ${1} in
event) help ${1}
;;
view) get_view ${3}
;;
esac
;;
strifilenfloflow)
echo TODO >> /dev/stderr
;;
strifilenflointe)
case ${1} in
view) get_view ${3} ${4}
;;
esac
;;
strifilenflonflo)
case ${1} in
*) help ${1}
;;
esac
;;
strifilenflonint)
case ${1} in
*) help ${1}
;;
esac
;;
strifilenflostri)
Žádná akce nemá za identifikátorem řetězec
help ${1}
;;
strifilenint)
case ${1} in
event|\
clip) help {1}
;;
view) get_view ${3}
;;
esac
;;
strifilenintflow)
case ${1} in
view) get_view ${3} ${4}
;;
esac
;;
strifilenintinte)
case ${1} in
clip) help clip
;;
view) get_view ${3} ${4}
;;
esac
;;
strifilenintnflo)
case ${1} in
view) get_view ${3} ${4}
;;
esac
;;
strifilenintnint)
case ${1} in
clip) help ${1}
;;
view) get_view ${3} ${4}
;;
esac
;;
strifilenintstri)
Žádná akce nemá za identifikátorem řetězec
help ${1}
;;
strifilerang|\
strifilerangstri)
case ${1} in
clip) clip_print ${3} ${4}
;;
*)Ostatní akce nemají obvykle za identifikátorem řetězec
help ${1}
;;
esac
;;
strifilestri)
case ${1} in
event) case ${3} in
decolor|\
delete|\
dump|\
text) do_event ${3}
;;
merge|\
split|\
time) help ${1}
;;
esac
;;
find) findline "${3}"
;;
merge) case ${3} in
range)Spojení událostí asciicastu, které odpovídají typu rang
echo TODO
;;
esac
;;
'set') case ${3} in
duration) do_set ${3}
;;
clear|\
comment|\
duration|\
enter|\
root|\
user|\
newline|\
marker|\
event|\
resize) help "set_${3}"
;;
*) help "${1}"
;;
esac
;;
view) [ ${HELP} ] && help ${1}_${3}
case ${3} in
context|info|scope|h|m|o|r) get_view ${3}
;;
struct) jq . -C ${2}
;;
compact) jq . -c -C ${2} | less -r
;;
time) VIEW="time" && get_view 1 ${#LINES[@]}
;;
*) help view
;;
esac
;;
esac
;;
strifilestriflow)
case ${1} in
event) case ${3} in
delete|\
merge|\
split|\
text|\
time) shift && shift && do_event "${@}"
;;
decolor) help ${1}_${3}
;;
esac
;;
find)sem spadnou parametry typu 1;1
findline "${3}" "${4}"
;;
'set') shift && shift && do_set "${@}"
;;
view) case ${3} in
context|info|scope|h|m|o|r) get_view ${3} ${4}
;;
*) help view
;;
esac
;;
esac
;;
strifilestristri)
case ${1} in
event) case ${3} in
delete|\
merge|\
split|\
text|\
time) shift && shift && do_event "${@}"
;;
decolor) help ${1}_${3}
;;
esac
;;
find) shift && shift && do_event "${@}"
;;
'set') echo ${ORIGTITLE} >> /dev/stderr
case ${3} in
size|\
resize|\
marker|\
title) shift && shift && do_set "${@}"
;;
*) help ${1}_${3}
;;
esac
;;
view) help ${1}
;;
esac
;;
strifilestriinte)
case ${1} in
event) case ${3} in
decolor|\
delete|\
merge|\
pause|\
split|\
text|\
time) shift && shift && do_event "${@}"
;;
esac
;;
find) findline "${3}" "${4}"
;;
'set') shift && shift && do_set "${@}"
;;
view) case ${3} in
context|info|scope|h|m|o|r) get_view ${3} ${4}
;;
*) help view
;;
esac
;;
esac
;;
strifilestrinint)
case ${1} in
event) case ${3} in
delete|\
merge|\
split|\
text|\
time) shift && shift && do_event "${@}"
;;
decolor) help ${1}_${3}
;;
esac
;;
view) help ${1}
;;
esac
;;
strifilestritime)
case ${1} in
event) case ${3} in
delete|\
marker|\
resize|\
split|\
text|\
time) shift && shift && do_event "${@}"
;;
decolor) help ${1}_${3}
;;
esac
;;
find)sem spadne omylem zadaný parametr typu 1:1
help find_colon
;;
view) case ${3} in
context|info|scope|h|m|o|r) get_view ${3} ${4}
;;
*) help view
;;
esac
;;
'set') shift && shift && do_set "${@}"
;;
esac
;;
strifilestrirang)
case ${1} in
event) case ${3} in
merge|\
delete) shift && shift && do_event "${@}"
;;
decolor) help ${1}_${3}
;;
esac
;;
view) case ${3} in
context|info|scope|h|m|o|r) get_view ${3} ${4}
;;
*) help view
;;
esac
;;
'set') case ${3} in
resize|\
marker) help ${1}_${3}
;;
esac
;;
esac
;;
strifiletime)
case ${1} in
clip)echo "Print clip from line identified by float time $3
START=$(print_seconds ${3})
clip_print ${START}
START=$(float_to_string ${3})
for i in ${!LINES[@]} ; do
[ ${i} -eq 0 ] && continue
if [ "${LINES[$i]:1}" -ge "${START}" ] ; then
clip_print $(($i+1)) ${#LINES[@]}
break
fi
done
;;
play) clip_print ${3} play
;;
view) get_view $(print_seconds ${3})
;;
esac
;;
strifiletimeflow)
case ${1} in
clip) clip_print ${3} ${4}
;;
view) get_view $(print_seconds ${3}) ${4}
;;
esac
;;
strifiletimeinte)
case ${1} in
clip) clip_print ${3} ${4}
;;
view) get_view ${3} ${4}
;;
esac
;;
strifiletimenflo)
case ${1} in
clip) [ ${DEBUG} ] && \
echo "Zkracuji časování od „human-ready” času ${3} o ${4} sekund" >> /dev/stderr
clip_print ${3} ${4}
;;
view) get_view $(print_seconds ${3}) ${4}
;;
esac
;;
strifiletimenint)
case ${1} in
clip) clip_print $(print_seconds ${3}) ${4}
;;
view) get_view $(print_seconds ${3}) ${4}
;;
esac
;;
strifiletimestri)
case ${1} in
clip) clip_print ${3} ${4}
;;
*)Ostatní akce nemají obvykle za identifikátorem řetězec
help ${1}
;;
esac
;;
*) echo "Nepodchycená kombinace parametrů ${TEST}" >> /dev/stderr
help ${1}
;;
esac
Kontrolní výpis použitých parametrů
echo "${TEST}" >> /dev/stderr