Tuesday, January 6, 2026

termux android: ffmpeg split video, rotate, convert h26h video, merge video

Requirement

Install termux from 

  • play store 
  • github.com/termux/termux-app latest but require allow install from unknown source

open termux and update

$ pkg upgrade

install ffmpeg

$ pkg install ffmpeg nano termux-api

Allow termux to manage files

$ termux-setup-storage

Finding the correct path 

Find location of your file, and open termux

You can get file info from file manager provided by your phone. This is information from Google File -> File info

/emulated/0/DCIM/Camera/Thumbnail/ebd64812dbf4ded1ce79xa 

Open your termux

 ~ $ cd ~/storage
~/storage $ pwd
/data/data/com.termux/files/home/storage
~/storage $ ls
audiobooks  downloads   movies    podcasts
dcim        external-0  music     shared
documents   media-0     pictures

In android it is DCIM but in termux it is become dcim 

Note these:

  • From file manager: /emulated/0/DCIM/Camera/Thumbnail/ebd64812dbf4ded1ce79xa -> to broadcast the files
  • From termux: /data/data/com.termux/files/home/storage/dcim/Camera/Thumbnail/ebd64812dbf4ded1ce79xa -> to browse in termux terminal

Split a video by time

Target:

  • every piece has 1 minutes 58 seconds
  • resolution low 480
  • output h264
  • sound mono

command to convert '172967815750944 (1).mp4'

$ ffmpeg -i 172967815750944\ \(1\).mp4 -vf scale=-2:480 -r 25 -c:v libx264 -preset veryfast -crf 28 -ac 1 -c:a aac -b:a 96k -f segment -segment_time 118 -reset_timestamps 1 out_%03d.mp4

this command will create files with name starting with name out_[number].mp4

This is fastest way to split video into smaller size, but you need to write the long command. This is bash sh to split file to avoid write long command. Open nano and copy paste it, name it with mysplitvid.sh. Make it execute $ chmod u+x mysplitvid.sh.

#!/data/data/com.termux/files/usr/bin/bash

# Check input
if [ -z "$1" ]; then
  echo "Usage: $0 inputvideo.mp4"
  exit 1
fi

INPUT="$1"
BASENAME=$(basename "$INPUT" .mp4)

# Output directory (public, visible to other apps)
OUTDIR="/sdcard/Download/${BASENAME}_split"
mkdir -p "$OUTDIR"

# FFmpeg split + encode
ffmpeg -i "$INPUT" \
  -vf scale=-2:480 \
  -r 25 \
  -c:v libx264 \
  -preset veryfast \
  -crf 28 \
  -ac 1 \
  -c:a aac \
  -b:a 96k \
  -f segment \
  -segment_time 118 \
  -reset_timestamps 1 \
  "$OUTDIR/${BASENAME}_out%02d.mp4"

# Notify Android media scanner for all outputs
for file in "$OUTDIR"/*.mp4; do
  am broadcast \
    -a android.intent.action.MEDIA_SCANNER_SCAN_FILE \
    -d "file://$file" >/dev/null
done

echo "✅ Done!"
echo "📂 Output folder: $OUTDIR”

Convert video h256 to social media

Target:

  • Video Codec: H.264 (libx264)
  • Audio Codec: AAC
  • Pixel Format: yuv420p (Required for maximum compatibility)
  • Audio Sample Rate: 48kHz or 44.1kHz
  • Container: MP4  

To convert an H.265 MP4 to a Twitter-compatible, highly compressed, lower-resolution MP4 using FFmpeg, you need to re-encode the video to H.264 video codec, AAC audio codec, a maximum resolution of 1280x720, and a lower bitrate/higher CRF value.

Assume your file is inputfile.mp4 and located at Download folder in your Android.  Here are command to convert your video to make compatible for social media. Currently social media do not support high compression like h265.

~/ $ cd ~/storage
~/storage $ ls
audiobooks downloads movies podcasts
dcim external-0 music shared
documents media-0 pictures
~/storage $ cd downloads
~/storage/downloads $ ls *.mp4
inputfile.mp4
~/storage/downloads $ ffmpeg -i inputfile.mp4 -c:v libx264 -crf 28 -preset medium -vf "scale=1280:720,format=yuv420p" -c:a aac -b:a 128k -movflags faststart output.mp4
...

This is bash sh to split file to avoid write long command. Open nano and copy paste it, name it with myconverth265.sh. Make it execute $ chmod u+x myconverth265.sh.

#!/data/data/com.termux/files/usr/bin/bash
set -e
if [ $# -ne 2 ]; then
    echo "Usage: $0 input.mp4 output.mp4"
    exit 1
fi

INPUT="$1"
OUTPUT="$2"

ffmpeg -i "$INPUT" \
  -c:v libx264 -crf 28 -preset medium \
  -vf "scale=1280:720,format=yuv420p" \
  -c:a aac -b:a 128k \
  -movflags faststart \
  "$OUTPUT"

MEDIA_PATH="$(realpath "$OUTPUT")"

am broadcast \
  -a android.intent.action.MEDIA_SCANNER_SCAN_FILE \
  -d "file://$MEDIA_PATH"

echo "Scanned:"
echo "$MEDIA_PATH"

Rotate

ffmpeg -i inputvid.mp4 -vf "transpose=1" outputvid.mp4
Parameters:
-i inputvid.mp4: Specifies the input video file.
-vf "transpose=1": This filter rotates the video 90 degrees clockwise.
0 = 90° counterclockwise and vertical flip (default) 
1 = 90° clockwise 
2 = 90° counterclockwise 
3 = 90° clockwise and vertical flip

Example use

$ cd storage/dcim/Camera
ffmpeg -i input.mp4 -vf "transpose=2" out.mp4
$ ls ~/storage/dcim/Camera/out.mp4 
/data/data/com.termux/files/home/storage/dcim/Camera/out.mp4
am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file:///storage/emulated/0/DCIM/Camera/out.mp4

Merge file with the same video properties

list file to merge

~/.../Thumbnail/ebd64812dbf4ded1ce79xa $ ls
out_001.mp4  out_004.mp4  out_007.mp4
out_002.mp4  out_005.mp4
out_003.mp4  out_006.mp4 

Create file list.txt

~/.../Thumbnail/ebd64812dbf4ded1ce79xa $ nano list.txt
E.g
# comment list of files
file 'out_001.mp4'
file 'out_002.mp4'
file 'out_003.mp4'
file 'out_004.mp4'
file 'out_005.mp4'
file 'out_006.mp4'
file 'out_007.mp4’ 

Merge

~/.../Thumbnail/ebd64812dbf4ded1ce79xa $ ffmpeg -f concat -safe 0 -i list.txt -c copy merged.mp4 

broadcast the new video

~/.../Thumbnail/ebd64812dbf4ded1ce79xa $ am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file:///sdcard/DCIM/Camera/Thumbnail/ebd64812dbf4ded1ce79xa

Make your output video accessible by other application 

Your output file owner is Termux. To make it accessible by other applications e.g. photos, files, etc, you can try these option

• Force android system to scan your output

$ am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file:///sdcard/[real_location]

From file location above

Note these:

  • From file manager: /emulated/0/DCIM/Camera/Thumbnail/ebd64812dbf4ded1ce79xa
  • From termux: /data/data/com.termux/files/home/storage/dcim/Camera/Thumbnail/ebd64812dbf4ded1ce79xa 

Replace [real_location] becomes file:///sdcard/DCIM/Camera/Thumbnail/ebd64812dbf4ded1ce79xa

It will scan all files in drive, or you can specify for specific file only.

Using termux-api (termux-media-scan) is an alternative way for am.  On newer Android versions (Android 10+), this may not work due to scoped storage restrictions. termux-media-scan is the recommended way. 

$ termux-media-scan /sdcard/[real_location] 

I test am and termux-media-scan on android 13 infinix Note 12, they work properly without issue. except for multimedia store in Thumbnail folder, only show in file manager, e.g google files.

Using bash script

To use bash script, at ~/ create bash script e.g. convert.sh and you can paste the bash code above under nano editor

~/ $ nano convert.sh
~/ $ chmod +x convert.sh

Goto directory e.g Download and run the script using ~/[filename].sh e.g 

~/.../Thumbnail/ebd64812dbf4ded1ce79xa $ ~/convert.sh vidIn.mp4 vinOut.mp3

 

 




Monday, December 22, 2025

python3: my personal package mini function

 

~/
├── .gitignore
├── dedetoklib/
│     ├── mydate
│     │     ├── __init__.py
│     │     ├── check_holiday.py
│     │     └── requirements.txt
│     ├── 
│     ├── 
│     └── 

├── test
│     ├── main.py

└── requirements.txt

 

check_holiday.py

import holidays
from datetime import datetime

# Create holiday object for Indonesia
ID_HOLIDAYS = holidays.country_holidays("ID")

def is_holiday(date_str):
    """
    Check if a date (YYYY-MM-DD) is a holiday.
    
    Returns:
        (bool, str | None): 
        - True and holiday name if holiday
        - False and None if not
    """
    date_obj = datetime.strptime(date_str, "%Y-%m-%d").date()
    
    if date_obj in ID_HOLIDAYS:
        return True, ID_HOLIDAYS[date_obj]
    else:
        return False, None

def is_working_day(date_str):
    """
    Check if a date (YYYY-MM-DD) is a working day (Monday to Friday).
    Returns:
        bool: True if Monday–Friday, False if Saturday or Sunday
    """
    date_obj = datetime.strptime(date_str, "%Y-%m-%d").date()
    # weekday(): Monday = 0, Sunday = 6    if date_obj.weekday() < 5:
        return True, "Weekday"
    else:
        return False, "Weekend"

Test main.py

from dedetoklib import check_holiday

dates = ["2025-01-01", "2025-01-02", "2025-08-17"]

for d in dates:
    # Check holiday
    holiday_flag, holiday_name = check_holiday.is_holiday(d)
    
    # Check working day
    working_flag, working_str = check_holiday.is_working_day(d)
    
    print(f"{d}: {working_str}, Holiday? {holiday_flag}", end="")
    if holiday_flag:
        print(f" ({holiday_name})")
    else:
        print()

 from dedetoklib import check_holiday as ch



 


 

python3: writing package

 
create folder to put your pyhton files e.g. dedetoklib, 

the structure of directory  

~/
├── .gitignore 

├── dedetoklib/
│   ├── __init__.py
│   └── hello.py

── main.py
└── requirements.txt

hello.py

def say_hello():
    return "Hello, World"

__init__.py

(leave it empty)

main.py 

import dedetoklib.hello

dedetoklib.hello.say_hello()

import as library

import dedetoklib.hello as h

print(h.say_hello())

 requirements.txt contains dependency.

.gitignore contains  files and folders to ignore.

Wednesday, December 10, 2025

Debian 13: rebuilding nvidia driver after kernel upgrade from linux-image-6.12.31-amd64 to linux-image-6.12.57+deb13-amd64

After you upgrade kernel, in my case linux-image-6.12.31-amd64 to linux-image-6.12.57+deb13-amd64, you need to rebuild nvidia driver. During boot, Debian will show some error loading nvidia module

To check which module fail during boot 

# journalctl -b -u systemd-modules-load.service 
Dec 10 09:48:02 mylocalpc systemd-modules-load[781]: modprobe: ERROR: Error running install command 'modprobe n> 
Dec 10 09:48:02 mylocalpc systemd-modules-load[781]: modprobe: ERROR: could not insert 'nvidia_modeset': Invali> 
Dec 10 09:48:02 mylocalpc systemd-modules-load[789]: modprobe: FATAL: Module nvidia-current-drm not found in di> 
Dec 10 09:48:02 mylocalpc systemd-modules-load[767]: Error running install command 'modprobe nvidia-modeset ; m> 
Dec 10 09:48:02 mylocalpc systemd-modules-load[767]: Failed to insert module 'nvidia_drm': Invalid argument 
Dec 10 09:48:02 mylocalpc systemd[1]: systemd-modules-load.service: Main process exited, code=exited, status=1/> 
Dec 10 09:48:02 mylocalpc systemd[1]: systemd-modules-load.service: Failed with result 'exit-code'. 
Dec 10 09:48:02 mylocalpc systemd[1]: Failed to start systemd-modules-load.service - Load 

To install dkms

root@mylocalpc:~# apt install dkms
dkms is already the newest version (3.2.2-1~deb13u1).
dkms set to manually installed.
Summary:
  Upgrading: 0, Installing: 0, Removing: 0, Not Upgrading: 1

To show dkms status

root@mylocalpc:~# dkms status
nvidia-current/550.163.01, 6.12.31-amd64, x86_64: installed 

To rebuild nvidia driver, it will build any kernel installed.

root@mylocalpc:~# dkms install nvidia-current/550.163.01
Sign command: /lib/modules/6.12.57+deb13-amd64/build/scripts/sign-file
Signing key: /var/lib/dkms/mok.key
Public certificate (MOK): /var/lib/dkms/mok.pub

Building module(s)................ done.
Signing module /var/lib/dkms/nvidia-current/550.163.01/build/nvidia.ko
Signing module /var/lib/dkms/nvidia-current/550.163.01/build/nvidia-modeset.ko
Signing module /var/lib/dkms/nvidia-current/550.163.01/build/nvidia-drm.ko
Signing module /var/lib/dkms/nvidia-current/550.163.01/build/nvidia-uvm.ko
Signing module /var/lib/dkms/nvidia-current/550.163.01/build/nvidia-peermem.ko
Installing /lib/modules/6.12.57+deb13-amd64/updates/dkms/nvidia-current.ko.xz
Installing /lib/modules/6.12.57+deb13-amd64/updates/dkms/nvidia-current-modeset.ko.xz
Installing /lib/modules/6.12.57+deb13-amd64/updates/dkms/nvidia-current-drm.ko.xz
Installing /lib/modules/6.12.57+deb13-amd64/updates/dkms/nvidia-current-uvm.ko.xz
Installing /lib/modules/6.12.57+deb13-amd64/updates/dkms/nvidia-current-peermem.ko.xz
Running depmod.... done.

to check status of nvidia modul

root@mylocalpc:~# dkms status
nvidia-current/550.163.01, 6.12.31-amd64, x86_64: installed
nvidia-current/550.163.01, 6.12.57+deb13-amd64, x86_64: installed

restart your debian. 

Friday, December 5, 2025

Pyhton3: playwright to get audio url stream

There are many tool to find url audio stream from radio station's page, here are a few list:

  1. playwright
  2. Selenium
  3. Puppeteer

To use Wget to find url audo on statik and simple web  

$ wget --spider -r -l 5 -nv -w 1 --no-clobber -A .mp3,.m3u8 "https://www.example.com/audio/stations" 2>&1 | grep -E '\.(mp3|m3u8)$' | awk '{print $3}' | sort -u > out_audio_urls.txt

Install playwright on debian with virtual environment:

myuser@mypc:~$ mkdir spider_playwright
myuser@mypc:~$ cd spider_playwright/
myuser@mypc:~/spider_playwright$ python3 -m venv venv
myuser@mypc:~/spider_playwright$ source venv/bin/activate
(venv) myuser@mypc:~/spider_playwright$ pip install playwright
Collecting playwright
  Downloading playwright-1.56.0-py3-none-manylinux1_x86_64.whl.metadata (3.5 kB)
Collecting pyee<14,>=13 (from playwright)
  Downloading pyee-13.0.0-py3-none-any.whl.metadata (2.9 kB)
Collecting greenlet<4.0.0,>=3.1.1 (from playwright)
  Using cached greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.metadata (4.1 kB)
Collecting typing-extensions (from pyee<14,>=13->playwright)
  Using cached typing_extensions-4.15.0-py3-none-any.whl.metadata (3.3 kB)
Downloading playwright-1.56.0-py3-none-manylinux1_x86_64.whl (46.3 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 46.3/46.3 MB 2.5 MB/s eta 0:00:00
Using cached greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (610 kB)
Downloading pyee-13.0.0-py3-none-any.whl (15 kB)
Using cached typing_extensions-4.15.0-py3-none-any.whl (44 kB)
Installing collected packages: typing-extensions, greenlet, pyee, playwright
Successfully installed greenlet-3.2.4 playwright-1.56.0 pyee-13.0.0 typing-extensions-4.15.0
(venv) myuser@mypc:~/spider_playwright$ playwright install
Downloading Chromium 141.0.7390.37 (playwright build v1194) from https://cdn.playwright.dev/dbazure/download/playwright/builds/chromium/1194/chromium-linux.zip
173.9 MiB [====================] 100% 0.0s
Chromium 141.0.7390.37 (playwright build v1194) downloaded to /home/myuser/.cache/ms-playwright/chromium-1194
Downloading Chromium Headless Shell 141.0.7390.37 (playwright build v1194) from https://cdn.playwright.dev/dbazure/download/playwright/builds/chromium/1194/chromium-headless-shell-linux.zip
104.3 MiB [====================] 100% 0.0s
Chromium Headless Shell 141.0.7390.37 (playwright build v1194) downloaded to /home/myuser/.cache/ms-playwright/chromium_headless_shell-1194
Downloading Firefox 142.0.1 (playwright build v1495) from https://cdn.playwright.dev/dbazure/download/playwright/builds/firefox/1495/firefox-debian-13.zip
96.7 MiB [====================] 100% 0.0s
Firefox 142.0.1 (playwright build v1495) downloaded to /home/myuser/.cache/ms-playwright/firefox-1495
Downloading Webkit 26.0 (playwright build v2215) from https://cdn.playwright.dev/dbazure/download/playwright/builds/webkit/2215/webkit-debian-13.zip
88.1 MiB [====================] 100% 0.0s
Webkit 26.0 (playwright build v2215) downloaded to /home/myuser/.cache/ms-playwright/webkit-2215
Downloading FFMPEG playwright build v1011 from https://cdn.playwright.dev/dbazure/download/playwright/builds/ffmpeg/1011/ffmpeg-linux.zip
2.3 MiB [====================] 100% 0.0s
FFMPEG playwright build v1011 downloaded to /home/myuser/.cache/ms-playwright/ffmpeg-1011

create file myspider.py and customize to your requirements, make it executable

(venv) myuser@mypc:~/spider_playwright$ chmod u+x myspider.py 

source code myspider.py modify it to meet your requirements 

import asyncio
from playwright.async_api import async_playwright

# generated and adjust by chatgpt.com

AUDIO_HINTS = [
    ".mp3", ".aac", ".m3u8", ".ogg", ".opus", ".wav",
    "stream", "/proxy/", "/live", "radio"
]

async def scan_audio_stream(url: str, listen_seconds: int = 15):
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=False)
        page = await browser.new_page()

        # ----- Detect manual browser close -----
        browser_disconnected = asyncio.Event()

        def on_browser_disconnect():
            print("\n[BROWSER CLOSED] Exiting application.")
            browser_disconnected.set()

        browser.on("disconnected", on_browser_disconnect)
        # ---------------------------------------

        found = set()

        async def on_response(res):
            u = res.url.lower()
            ct = res.headers.get("content-type", "").lower()

            if "audio" in ct or any(k in u for k in AUDIO_HINTS):
                if u not in found:
                    print(f"[NEW AUDIO STREAM] {u}")
                    found.add(u)

        page.on("response", on_response)

        print("Opening page...")
        await page.goto(url, wait_until="domcontentloaded")
        await asyncio.sleep(2)

        # ---- Find audio player iframe ----
        candidate_frames = [
            f for f in page.frames
            if any(kw in f.url.lower() for kw in ["player", "radio", "embed"])
        ]
        if not candidate_frames:
            candidate_frames = page.frames  # fallback

        print("Attempting to click play...")
        for f in candidate_frames:
            try:
                for sel in ["button[aria-label='Play']", "button.play", ".jp-play", "button"]:
                    btn = await f.query_selector(sel)
                    if btn:
                        print(f"Clicked play in iframe → {f.url}")
                        await btn.click()
                        break
            except:
                pass

        print(f"\nListening for audio streams up to {listen_seconds} seconds...\n")

        # ----- Wait for 15 seconds OR browser close -----
        try:
            await asyncio.wait_for(browser_disconnected.wait(), timeout=listen_seconds)
        except asyncio.TimeoutError:
            print("\n[TIMEOUT] Done scanning.")
        # -------------------------------------------------

        await browser.close()
        return list(found)


if __name__ == "__main__":
    results = asyncio.run(scan_audio_stream(
        "https://www.example.com", # CHANGE HERE
        listen_seconds=15     # ← Fast scan
    ))

    print("\n===== ALL STREAMS FOUND =====")
    for s in results:
        print(s)

Code

from playwright.sync_api import sync_playwright

def main():
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=False   # <-- You can manually interact
        )
        context = browser.new_context()
        page = context.new_page()

        print("🚀 Browser launched. Type the radio station URL manually.")
        print("👉 When you press Play, audio stream URLs will appear here.\n")

        # Capture outgoing requests
        def on_request(request):
            url = request.url

            if (
                ".mp3" in url or
                ".aac" in url or
                ".m3u8" in url or
                ".ogg" in url or
                "stream" in url.lower() or
                request.resource_type == "media"
            ):
                print("🎧 AUDIO STREAM REQUEST FOUND:")
                print(url, "\n")

        page.on("request", on_request)

        # Capture responses containing audio
        def on_response(response):
            url = response.url
            headers = response.headers
            content_type = headers.get("content-type", "")

            if any(fmt in content_type for fmt in ["audio", "mpeg", "aac", "ogg"]):
                print("🎧 AUDIO STREAM RESPONSE FOUND:")
                print(url, "\n")

        page.on("response", on_response)

        # Open a blank page — you will type the URL manually
        page.goto("about:blank")

        # Keep browser open
        browser.wait_for_event("disconnected")


if __name__ == "__main__":
    main()

Enjoy