Posts Tagged ‘линукс’

Записване на онлайн радио

Thursday, April 3rd, 2014

От известно време слушам Eurodance канала на Digitally Imported. Отвреме навреме пускат уникални миксове, които не могат да бъдат намерени никъде из нета. За това се захванах и написах едно кратко скриптче за да ги рипна.

Радиото излъчва ICY таг, който съдържа името на песента. По този таг може да се разделят песните в общия стрийм, като единствения кусур е че идва с няколко секунди закъснение или предварение.

Използвах -dumpstream опцията на mplayer за записване на потока. Паралелно с това върви още един mplayer процес, който наблюдава за пристигането на ICY таг и пуска ново записване в нов файл. Скрипта създава нова директория за всеки ден, като песните които запазва са с префикс на времето в което са излъчени.

  1. #!/bin/bash
  2.  
  3. # задаваме някой директории и файлове
  4. FIFO=/tmp/mplayer_recording
  5. OUT_DIR=/home/ivanatora/di.fm
  6.  
  7. LAST_PID=0
  8. LAST_SONG=""
  9.  
  10. if [ ! -e "$FIFO" ]
  11. then
  12.     mkfifo $FIFO
  13. fi
  14.  
  15. # това е процесът, който следи таговете с имената на песните и ги изпраща в едно FIFO
  16. nohup mplayer -ao null http://pub3.di.fm:80/di_eurodance | stdbuf -o L grep ICY > /tmp/mplayer_recording &
  17.  
  18. # тук четем от FIFO-то и пускаме нов записващ процес при получаване на ICY таг
  19.  
  20. while read LINE < $FIFO; do
  21.     DATE=`date +%F`
  22.     if [ ! -d "$OUT_DIR/$DATE" ]
  23.     then
  24.         mkdir "$OUT_DIR/$DATE"
  25.     fi
  26.  
  27.     echo "—–"
  28.     echo $LINE
  29.     # името на песента е заградена в единични кавички
  30.     # @TODO: понякога в самото име има единични кавички – да се обмисли този случай
  31.     SONG=`echo $LINE | cut -d\‘ -f2 | sed -e ‘s/ /_/g‘`
  32.    echo "Last: $LAST_SONG"
  33.    echo "Current: $SONG"
  34.    # тук имам един бъг някъде – получавам таговете по два пъти. Това е workaround, който дава да се продължи само ако тагът който идва е различен от предния.
  35.    if [ "$SONG" != "$LAST_SONG" ]
  36.    then
  37.        TIME=`date +%T`
  38.        echo "##### $TIME"
  39.  
  40.        # ако вече имаме записващ процес – време е да го спрем и да пуснем нов
  41.  
  42.        if [ $LAST_PID -ne 0 ]
  43.        then
  44.            kill $LAST_PID
  45.        fi
  46.  
  47.        nohup mplayer http://pub3.di.fm:80/di_eurodance -dumpstream -dumpfile "$OUT_DIR/$DATE/$TIME"_"$SONG".mp3 &
  48.        LAST_PID=$!
  49.        LAST_SONG="$SONG"
  50.    fi
  51. done

Тук имам следние проблеми:
1) Таговете ми идват по два пъти.
2) Закъснението на таговете, което не е константа, която може да бъде уловена и компенсирана. На практика няколко секунди от началото на песента се записват в предния файл.
3) Предполагам че може да бъде оптимизирано и направено само с 1 mplayer процес.
4) Ако ми умре нета за секунда, mplayer-ите се прекратяват, което минава през фифото и цикъла `while read` заминава.

Някой bash-майстор може да се произнесе с идеи 🙂

Alpine mass delete messages

Friday, March 7th, 2014

You have new mail.

Почти не използвам локалната пощенска кутия. Всъщност единственото за което я ползвам е да следя за грешки по кроновете които работят на системата. Принципно целия STDOUT+STDERR бива изпращан по мейл на текущия потребител. STDOUT не ми трябва, понеже често си оставям някакви дебъгващи принтове тук-таме по скриптовете. За това си описвам задачите с >/dev/null. По този начин STDOUT се филтрира, и остава само STDERR. Ако искам да махна абсолютно целия изход от изпълнението правя >/dev/null 2>&1.

Ако нещо по кроновете видимо не работи, отварям пощата и чета. Обикновено я преглеждам през няколко седмици за да изтрия старите логове от крона. За това използвам alpine, понеже не ми се занимава с никакви тежки и сериозни GUI изпълнения като Thunderbird. Удобно е да се логна в ssh от където и да е и да изтараша пощенската кутия.

Днес alpine се стартира малко по-бавно и какво да гледам в пощенската кутия – 12 136 писма. Оказа се че съм написал доста тъпо един от кроновете:


*/5 * * * * cd /home/ivanatora/rilski_ezera; wget "http://www.rilskiezera.bg/meteo/rilskiezerahut.jpg" -O img_`date +\%Y-\%m-\%d_\%R`.jpg >/dev/null

Това сваля снимка от х. Рилски Езера на всеки 5 минути и по-късно си правя таймлапс със свалените изображения. Въпросът е защо така злобно ми е нафлудил пощата, при условие че STDOUT е филтриран?

Защото съм забравил че `wget` не принти съобщенията си в STDOUT, а вместо това в STDERR (факт)…

Както и да е – оправих крона, но сега проблема беше как да изчистя пощенската кутия с alpine. Обикновено трия пощата по следния начин – отивам върху първото писмо и задържам D. Това го маркира за изтриване (което ще се случи след затваряне на клиента) и преминава към следващото. Това минава през съобщенията едно след друго и нормално за няколко секунди изчиствам цялата кутия. Само че в случая с 12 хиляди писма това би ми отнело към 20 минути набиране върху D-то.

Поблъсках се малко и намерих процедурата за изтриване на всички съобщения:

1. В INBOX се натиска `;` за Select messages
2. Натиска се A за Select all.
3. Натиска се A за Apply command
4. Натиска се D за Delete

След това се излиза нормално от клиента с ^X и писмата се изтриват.

Hello, Android!

Tuesday, April 23rd, 2013

От едно време насам като се прибера вкъщи и си пускам да слушам онлайн радиа. А от както открих Digitally Imported и си лягам с пуснато радио.

Понеже аз съм доста мързелив тип (признавам си), по едно време ми стана тежко както си седя на дивана с крака върху масата да ставам да ходя до десктопа и да сменям радиото или да го спирам. Слава богу – има SSH и mplayer, така че тези неща мога да ги правя от лаптопа който се намира на фундаменталното разстояние на 3 метра от бюрото. Другия проблем е че искам радиото да се спира половин час след като си легна. Има много приспиващи станцийки, но не искам да бучат през цялата вечер.

Първия ми порив беше да сложа rc0.d скрипт на лаптопа, който преди shutdown да се вързва към десктопа и да прави някаква магия с `at` и `killall mplayer` която да гаси радиото след 30 минути. Но това щеше да бъде много лесно, а пък ми се занимаваше с нещо ново.

И нали сега са модерни мобилните приложения, реших да пробвам да си напиша апп за телефона. Свалих официалния ADT-bundle с инструментите за разработка и почнах. Тук да спомена че с Java имам един-единствен опит от университета и нищо повече. Какви са ми впечатленията:

Eclipse
+ той бил много читав редактор с всичките нужни code completion-и, javadoc popup-и, syntax highlights и т.н.
+ превъзходна интеграция с дебъгера на андроида – разработчиците от Google са си свършили работата
+ интеграция с емулатора, който е идеален за тестване на бутони, поведение… доста е тежък обаче и този апп през повечето време съм го тествал директно на телефона
+ logcat – инструмент, който вади директно дебъг съобщенията от емулатора или телефона в еклипса. Първия път като го свързах към телефона и се шашнах. МАЛЕ, всички нишки и процеси продуцират огромно количество дебъг съобщения и exceptions 😀 Хората не си ли махат дебъга в продукшън версиите на приложенията?
+ понякога logcat решава че няма да работи повече, което се оправя с рестарт на IDE-то
+ еклипса много ограничава писането на кода в добрия смисъл – не те оставя да пишеш глупости, за всеки проблем (примерно assign type mismatch) излизат възможните решения и кода често се пише с цък от мишката
+ кода наистина се пише с цък от мишката – ако ви трябва да имплементирате метод от parent class, 3 клика и шаблона ви се налива във вашия код
+ няма вградена интеграция с SVN
+ от много ум понякога изтрещява и започва да дава несъществуващи грешки, което се оправя с рестарт на IDE-то
Java
+ струва ми се много странен език като идвам от web програмирането
+ по-ниска степен на абстракция от web езиците и следователно:
+ нещата стават с адски много писане. Примерно един file_get_contents(“http://domain.com”) от PHP в Java става с 30 реда плътно изписан код, в който се ползват 15 различни типа обекти
+ няма асоциативни масиви, което сериозно ми бърка в здравето. Има разни workaround-и като да се ползват обекти, ама не е същото.
+ много грозни stack trace-ове, на които още не мога да свикна 🙂

Android
+ доста подреден lifecycle на приложенията
+ подробна документация на официалното място, но ми се струва доста суха. В Stackoverflow обаче има хиляди материали с примерен код
+ във връзка с горното, 90% от писането на кода е да намериш вече решения проблем и да го copy-paste-неш правилно 🙂
+ нишки и кой код в коя нишка се изпълнява… редовно се опитвам да достъпвам елементи от UI-нишката от друга нишка и естествено не се получава 🙂 Другия проблем е програмно да си извадите бавните операции в задна нишка, защото иначе се получава лагване на апп-а.
+ layouts, където си седи презентационната логика и activities, където си седи бизнес логиката
+ broadcast receivers, или слушатели за глобални събития
+ запазване на settings между различните изпълнения на апп-а, обаче settings трябва да са от много примитивен тип
+ куриозно е да се опиташ да натъпчеш UI в такива малки разделителни способности – буквално след третото текстово поле и мястото свършва 🙂
Първата идея беше на десктопа да направя няколко PHP скрипта, които общо взето да правят system(‘mplayer нещоси’) и от телефона да ги изпълнявам с HTTP GET заявки. Но се оказа че, да направиш HTTP GET в Android е грозно, голямо и ужасно, поне за текущите ми умения, така че тази идея беше отхвърлена още на първата вечер. Втората идея беше да се ползва SSH като от телефона директно се пращат командите към десктопа. Това се оказа доста по-лесно, че и целия апп стана много универсален.

И ето го апп-а My Multimedia Controller, който вече взе да придобива малко търговски вид. Качвам и кода, някой ако иска да ми се подиграва 🙂 Сериозно, не бих се подписал под него и мисля че трябва да бъде принтиран на хартия и ритуално изгорен. Обаче пък приложението работи.
Features:
+ управлява нивото на звука на десктопа
+ превключва измежду няколко радио станции
+ запомняне на “домашна WiFi мрежа”. Ако се конектнете към нея, ще получите нотификация, от която да се пусне апп-а
+ sleep mode – таймер за спиране на радиото, точно като sleep timer-а на телевизорите 🙂

Prerequestes:
+ Linux desktop, `amixer` за контрол на нивото на звука (`amixer` вече не е задължително изискване от r17 натам), `mplayer` за свирене на радио станциите
+ Android 2.2+ device

Required permissions:
+ INTERNET – за да работи SSH
+ ACCESS_NETWORK_STATE и ACCESS_WIFI_STATE – за нотификацията при конект към домашната мрежа. В една по-ранна версия на приложението при такъв конект се пускаше самото приложение, но с практиката се оказа че това е доста intrusive behavior и оставих само нотификация 🙂

 

TODO:
+ профили за SSH – да запазва данните за конект за повече от 1 PC добавено в r13
+ повече контрол върху различни миксер канали и възможност за софтуерно volume добавено в r17
+ динамично въвеждане на радио станции, че в момента URL-тата са хардкоднати
+ иконката да се смени с по-добра
+ обратна връзка за текущо свирената песен (ICY info тага, ако го има)

 

 

Download:

Project r20 – 1.7MB

APK r20 – 337KB

Не е финална версия 🙂

Linux + scheduled tasks

Monday, March 25th, 2013

Може да ви се наложи да стартирате команда по часовник или по време когато не сте на машината.
Единият вариант който се използва е cron, но това е по-скоро за задачи, които се повтарят във времето. Няма да го разлеждаме, тъй като нета е пълен с примери и на всеки рано или късно му се е налагало да си блъска главата с него 🙂

По-интересното е да направим единично стартиране на задача по зададено време.

В Линукс това става много лесно с командата `at`:


(19:18:49)[ivanatora@~]$ at 19:20
warning: commands will be executed using /bin/sh
at> beep
at>
job 17 at Mon Mar 25 19:20:00 2013

Забележка: това <EOT> предполагам че е end-of-terminal и се получава с CTRL+D.
В случая задачата която ще се стартира е `beep`. Можете да напишете цял скрипт в промпта на `at` или да укажете външен файл който съдържа скриптираните задачи чрез `at [-f file]`. Отбележете че интерпретаторът, с който се изпълнява скрипта в случая е /bin/sh – това ще ви се отрази ако искате да скриптирате по-сложни задачи използвайки вградените възможности. Естествено, нищо не ви пречи да стартирате скрипт писан на какъвто и да е език:


(19:23:46)[ivanatora@~]$ at 19:50
warning: commands will be executed using /bin/sh
at> php /home/ivanatora/test.php
at>
job 18 at Mon Mar 25 19:50:00 2013

Списък с текущите активни задачи може да видите с `atq`:


(19:24:02)[ivanatora@~]$ atq
18 Mon Mar 25 19:50:00 2013 a ivanatora

Може да се зачудите къде отиде задача 17? Ами тя изтече докато напиша тези редове 😉

Може да изтривате задачи от списъка с `atrm`:


(19:25:35)[ivanatora@~]$ atrm 18
(19:26:13)[ivanatora@~]$ atq
(19:26:37)[ivanatora@~]$

Може да преглеждате с детайли определена задача с `at [-c job]`:

(19:27:20)[ivanatora@~]$ at 19:30
warning: commands will be executed using /bin/sh
at> beep
at>
job 19 at Mon Mar 25 19:30:00 2013
(19:27:37)[ivanatora@~]$ at -c 19
#!/bin/sh
# atrun uid=1000 gid=1000
# mail ivanatora 0
umask 2
SSH_AGENT_PID=2367; export SSH_AGENT_PID
GPG_AGENT_INFO=/tmp/gpg-xsDFU4/S.gpg-agent:2368:1; export GPG_AGENT_INFO
XDG_SESSION_COOKIE=4784344ffacd7f8cf96108324bae42fb-1364229708.104884-44687589; export XDG_SESSION_COOKIE
WINDOWID=37748745; export WINDOWID
GNOME_KEYRING_CONTROL=/tmp/keyring-bylSRi; export GNOME_KEYRING_CONTROL
GTK_MODULES=canberra-gtk-module; export GTK_MODULES
USER=ivanatora; export USER
LS_COLORS=rs=0:di=01\;34:ln=01\;36:mh=00:pi=40\;33:so=01\;35:do=01\;35:bd=40\;33\;01:cd=40\;33\;01:or=40\;31\;01:su=37\;41:sg=30\;43:ca=30\;41:tw=30\;42:ow=34\;42:st=37\;44:ex=01\;32:\*.tar=01\;31:\*.tgz=01\;31:\*.arj=01\;31:\*.taz=01\;31:\*.lzh=01\;31:\*.lzma=01\;31:\*.tlz=01\;31:\*.txz=01\;31:\*.zip=01\;31:\*.z=01\;31:\*.Z=01\;31:\*.dz=01\;31:\*.gz=01\;31:\*.lz=01\;31:\*.xz=01\;31:\*.bz2=01\;31:\*.bz=01\;31:\*.tbz=01\;31:\*.tbz2=01\;31:\*.tz=01\;31:\*.deb=01\;31:\*.rpm=01\;31:\*.jar=01\;31:\*.war=01\;31:\*.ear=01\;31:\*.sar=01\;31:\*.rar=01\;31:\*.ace=01\;31:\*.zoo=01\;31:\*.cpio=01\;31:\*.7z=01\;31:\*.rz=01\;31:\*.jpg=01\;35:\*.jpeg=01\;35:\*.gif=01\;35:\*.bmp=01\;35:\*.pbm=01\;35:\*.pgm=01\;35:\*.ppm=01\;35:\*.tga=01\;35:\*.xbm=01\;35:\*.xpm=01\;35:\*.tif=01\;35:\*.tiff=01\;35:\*.png=01\;35:\*.svg=01\;35:\*.svgz=01\;35:\*.mng=01\;35:\*.pcx=01\;35:\*.mov=01\;35:\*.mpg=01\;35:\*.mpeg=01\;35:\*.m2v=01\;35:\*.mkv=01\;35:\*.webm=01\;35:\*.ogm=01\;35:\*.mp4=01\;35:\*.m4v=01\;35:\*.mp4v=01\;35:\*.vob=01\;35:\*.qt=01\;35:\*.nuv=01\;35:\*.wmv=01\;35:\*.asf=01\;35:\*.rm=01\;35:\*.rmvb=01\;35:\*.flc=01\;35:\*.avi=01\;35:\*.fli=01\;35:\*.flv=01\;35:\*.gl=01\;35:\*.dl=01\;35:\*.xcf=01\;35:\*.xwd=01\;35:\*.yuv=01\;35:\*.cgm=01\;35:\*.emf=01\;35:\*.axv=01\;35:\*.anx=01\;35:\*.ogv=01\;35:\*.ogx=01\;35:\*.aac=00\;36:\*.au=00\;36:\*.flac=00\;36:\*.mid=00\;36:\*.midi=00\;36:\*.mka=00\;36:\*.mp3=00\;36:\*.mpc=00\;36:\*.ogg=00\;36:\*.ra=00\;36:\*.wav=00\;36:\*.axa=00\;36:\*.oga=00\;36:\*.spx=00\;36:\*.xspf=00\;36:; export LS_COLORS
XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0; export XDG_SESSION_PATH
XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0; export XDG_SEAT_PATH
SSH_AUTH_SOCK=/tmp/ssh-UytBrOmq2318/agent.2318; export SSH_AUTH_SOCK
DEFAULTS_PATH=/usr/share/gconf/fluxbox.default.path; export DEFAULTS_PATH
XDG_CONFIG_DIRS=/etc/xdg/xdg-fluxbox:/etc/xdg; export XDG_CONFIG_DIRS
DESKTOP_SESSION=fluxbox; export DESKTOP_SESSION
PATH=/home/ivanatora/bin:/usr/lib/lightdm/lightdm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/home/ivanatora/bin/; export PATH
PWD=/home/ivanatora; export PWD
GNOME_KEYRING_PID=2307; export GNOME_KEYRING_PID
LANG=en_US.UTF-8; export LANG
MANDATORY_PATH=/usr/share/gconf/fluxbox.mandatory.path; export MANDATORY_PATH
UBUNTU_MENUPROXY=libappmenu.so; export UBUNTU_MENUPROXY
PS1=\\[\\033[1\;34m\\]\(\\t\)\\[\\033[0m\\][\\u@\\[\\033[1\;33m\\]\\w\\[\\033[0m\\]]\\\$\ ; export PS1
GDMSESSION=fluxbox; export GDMSESSION
SPEECHD_PORT=7560; export SPEECHD_PORT
COLORFGBG=15\;default; export COLORFGBG
HOME=/home/ivanatora; export HOME
SHLVL=2; export SHLVL
LOGNAME=ivanatora; export LOGNAME
DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-rCyQHFVg4i,guid=31de69d47f7c412295741b5100000041; export DBUS_SESSION_BUS_ADDRESS
XDG_DATA_DIRS=/usr/share/fluxbox:/usr/local/share/:/usr/share/; export XDG_DATA_DIRS
LESSOPEN=\|\ /usr/bin/lesspipe\ %s; export LESSOPEN
BROWSER=firefox; export BROWSER
LESSCLOSE=/usr/bin/lesspipe\ %s\ %s; export LESSCLOSE
COLORTERM=rxvt-xpm; export COLORTERM
XAUTHORITY=/home/ivanatora/.Xauthority; export XAUTHORITY
cd /home/ivanatora || {
echo 'Execution directory inaccessible' >&2
exit 1
}
beep

 

Ау, уау, какво стана? Къде е задачата?

Задачата е най-отдолу, но за да стигне до нея `at` зарежда доста от променливите на обкръжението. Който е писал и дебъгвал крон задачи знае колко е досадно да разчиташ на определена променлива от средата, а тя да не е това което трябва…И въпреки всичкото това зареждане тук ни липсва една важна променлива, която ако сетнем ръчно ни отваря нови хоризонти. А именно – DISPLAY. По подразбиране дефаултния дисплей е “:0.0” и ако го използваме, може да стартираме задача, която работи с Х-а.

Една примерна грубa елементарна напомнянка:


(19:34:37)[ivanatora@~]$ at 19:36
warning: commands will be executed using /bin/sh
at> beep
at> DISPLAY=:0.0 xmessage "remember to eat dinner!"
at>
job 23 at Mon Mar 25 19:36:00 2013

Възможните формати за подаване на времето за стартиране е доста обширен и може да го видите в /usr/share/doc/at/timespec

Компресия на видео

Tuesday, December 25th, 2012

От както си хакнах Magic Lantern на фотоапарата, вече мога да снимам и видео. Не съм никакъв видео маниак с продуцентски наклонности, но от време навреме е хубаво да можеш да запишеш 1-2 минути.

Имаше два основни проблема за решаване:
1. Крайните видео файлове са огромни по размери за скромните ми хард дискове. Примерно 1 минута VGA видео – 160MB. А ако се снима на FULL HD, размерите стават още по-потресаващи.
2. Апарата няма хардуерен микрофон и записва без звук. По принцип въобще няма функцията “видео”, но благодарение на софтуерните ентусиасти – снима 🙂

Първия проблем ми беше особено належащ, защото наистина нямам никакво излишно място, а и щайгата ми въобще няма претенция да е станция за видео обработка. Така че ми трябваше софтуер, който да върви на Linux, да може да се batch-ва и да изкарва качествени смачкани видеа.

Естествено първо пробвах с mencoder, но така и не можах да изкарам качествено видео. Артефактите от компресията са едно на ръка, допълнително всичко що се мърда беше замацано и блурнато… Пробвах какви ли не кодеци и какви ли не компресии.
После пробвах директен ъплоуд в YouTube и да си го оправят както знаят 🙂 Една готина тяхна опция е “Reduce camera shake” за клипчета снимани от ръка. За съжаление YouTube го надроби същото като mencoder – грозотия.

Правилния начин беше компресия с H264 и ffmpeg.
Тук също имаше препъващи камъни. Оказа се че на древното ми Убунту 10.04 има ffmpeg, но супер стара версия без H264. Почнах да си псувам тихичко и да премислям за N-ти път алтернативата за ъпгрейд, когато ей така на майтап си свалих сорса на ffmpeg и пуснах да се компилира. Очаквах да изреве че всяка нужна библиотека е с прекалено стара версия в системата ми, но не! За 10 минути имах работещ нов bleeding edge ffmpeg.
H264 се сваля и се инсталира отделно. В това HOWTO е описано как става.
Веднъж като имаме работещ ffmpeg+H264, останалото е лесно:

ffmpeg -i MVI_2355.MOV -acodec aac -vcodec h264 out.mp4

В случая може би би било по-добре да не се задава никакъв аудио кодек, понеже видеото няма звук.

Крайния резултат в размера на файла е покъртителен:

-rw-rw-rw- 1 ivanatora ivanatora 163M 2012-12-23 20:15 MVI_2355.MOV
-rw-r--r-- 1 ivanatora ivanatora  22M 2012-12-24 12:24 out.mp4

Около 8 пъти по-малък размер. Видео качеството е същото като на оригинала – с минимална загуба.

Втория проблем въобще не ми беше приоритет, но така и така ме беше хванало настроение, та разучих какви са опциите.
По форумите казват че можело да се използва външен микрофон, който да се включи в слота за дистанционното и някак си да се записва от там.
Вместо това реших да пробвам да запиша аудиото с телефона и после някак си да ги напасна с видеото.

За Android има доста приложения диктофони или voice recorders. Пробвах поне 3-4 безплатни от маркета, но все нещо не ми харесваха. Или бяха ограничени до много къси записи, или записваха в някакви незнайни формати, или качеството на звука беше ужасно. Накрая се спрях на дефаултния voice recorder – записва в AMR, няма никакви ограничения и звукът е доста приличен.

Недостатък на целия метод е че трябва едновременно да пусна записа на аудиото и видеото, и после едновременно да ги спра. Е, обект на следващото ТНТМ ще е монтиране на аудио с произволна дължина и начало 🙂

След като имаме крайния AMR с аудиото, много лесно го преобразуваме в какъвто и да е формат:

ffmpeg -i audio.amr audio.wav

И също така лесно го добавяме във видеото:

ffmpeg -i audio.wav -i out.mp4 -strict -2 -ar 22050 test.mp4

В резултат вече имам FULL HD capable машина която с малко час по труд и творчество може да записва и аудио.