blami tec

Alguns apunts de tecnologia

Executant una comanda des de Rust

leave a comment »

He aconseguit desenvolupar una petita funció anomenada run_easy() que permet executar una comanda des de rust.

En concret, la comanda és l’execució d’un programa en Java que rep arguments per línia de comanda, llegeix per entrada estàndard, escriu per sortida estàndard i d’error estàndard, i es queda en un bucle infinit.

run_easy() permet indicar la comanda, l’entrada, els arguments i el temps màxim d’espera en segons.

run_easy() retorna l’estructura RunResult:

pub struct RunResult {
    pub status: i32,
    pub stdout: String,
    pub stderr: String,
}

El codi de la funció és:

fn run_easy(input: &str, program: &str, args: &[&str], timeout: u64) -> RunResult {
    let mut child = Command::new(program)
        .args(args)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .unwrap();

    // write input
    _ = child.stdin.take().as_mut().unwrap().write_all(input.as_bytes());

    // wait for execution
    let duration = Duration::from_secs(timeout);
    let status_code = match child.wait_timeout(duration).unwrap() {
        Some(status) => status.code(),
        None => { // child hasn't exited yet
            child.kill().unwrap();
            child.wait().unwrap();
            Some(124)
        }
    };

    // recollect output
    let mut stdout = String::new();
    child.stdout.unwrap().read_to_string(&mut stdout).unwrap();
    let mut stderr = String::new();
    child.stderr.unwrap().read_to_string(&mut stderr).unwrap();

    // compose result
    RunResult {
        status: status_code.unwrap(),
        stdout: stdout,
        stderr: stderr
    }
}

Et caldrà indicar els següents use:

use std::io::prelude::*;
use std::process::{Command, Stdio};
use std::time::Duration;
use wait_timeout::ChildExt;

No oblidis incloure la dependència a Cargo.toml:

[dependencies]
wait-timeout = "0.1.5"

Finalment, un exemple d’ús:

    let input = "THIS IS THE WONDERFUL INPUT";
    let args = &["MyProgram", "un", "dos", "tres"];
    let program = "java";
    let result = run_easy(input, program, args, 4);
    println!("Resulting execution:");
    println!("\tstatus: {}", result.status);
    println!("\toutput:\n{}", result.stdout);
    println!("\terror:\n{}", result.stderr);

El contingut de MyProgram.java pot ser tant senzill com:

public class MyProgram {
    public static void main(String[] args){
        for (String arg: args) {
            System.out.println("arg: " + arg);
        }
        String text = Entrada.readLine();
        System.out.println("Hello World " + text);
        System.err.println("some error text");
        while(true);
    }
}

De moment és el més senzill que he estat capaç d’escriure.

Written by blami

7 Març 2022 at 12:29

Arxivat a General

Tagged with ,

Extreure subtítols d’un mkv

leave a comment »

Tinc un .mkv ple de pistes d’audio i subtítols en diferents idiomes, i una tele que no el permet gestionar.

Converteixo el vídeo a mp4 fent servir HandBrake. Funciona molt bé i fins i tot em permet fer-ho des de línia de comandes:

$ nice HandBrakeCLI -i nom.mkv -o nom.mp4

Tot i que HandBrake es manega també amb els subtítols, no me n’he sabut sortir per extreure’ls en un fitxer apart.

La solució m’ha vingut per una altra eina mkvtoolbox.

Per obtenir la llista de pistes contingudes al fitxer:

$ mkvmerge -i nom.mkv
Fitxer «nom.mkv»: contenidor: Matroska
ID de la pista 0: video (MPEG-H/HEVC/h.265)
ID de la pista 1: audio (AAC)
ID de la pista 2: audio (AAC)
ID de la pista 3: subtitles (SubStationAlpha)
ID de la pista 4: subtitles (SubStationAlpha)
ID de la pista 5: subtitles (SubStationAlpha)
ID de la pista 6: subtitles (SubStationAlpha)
ID de la pista 7: subtitles (SubStationAlpha)
ID de la pista 8: subtitles (SubRip/SRT)
ID de la pista 9: subtitles (SubRip/SRT)
ID de la pista 10: subtitles (SubRip/SRT)
ID de la pista 11: subtitles (SubStationAlpha)
…

La veritat és que el resultat no és gaire descriptiu perquè no et diu en quin idioma és cada subtítol.

Per poder saber a quin idioma correspon cada pista, una opció és fer servir:


$ mkvinfo nom.mkv
…
| + Pista
|  + Número de la pista: 5 (ID de la pista per a «mkvmerge» i «mkvextract»: 4)
|  + UID de la pista: 12122612628107263142
|  + Tipus de pista: Subtítols
|  + Etiqueta «Pista per omissió»: 0
|  + Etiqueta del lligam: 0
|  + ID del còdec: S_TEXT/UTF8
|  + Idioma: rus
|  + Nom: Full
…

Suposem que descobrim que ens interessa el subtítol de la pista 4. Per extreure’l fem:

$ mkvextract tracks nom.mkv 4:nom.srt

I ja ho tindríem!

Written by blami

5 Març 2022 at 11:47

Arxivat a General

Tagged with

pytest: estructura de directoris

leave a comment »

Introducció

El mecanisme d’importació de Python em resulta força confós. Segurament és per la gran flexibilitat que ofereix.

Com sigui, cada cop que haig de fer un nou projecte amb pytest em toca cercar com muntar les carpetes, on col·locar el __init__.py, etc. Això acaba resultant en una manera diferent per cada projecte, que els lints que tinc instal·lats al vim no reconeguin imports, i altres problemes.

Com que l’estructura habitual que faig servir és en realitat molt senzilla, he pensat fer aquest post per tenir-lo de referència. T’imagines que a partir d’ara els meus projectes tenen tots la mateixa estructura?

Objectiu

L’objectiu és:

  • una carpeta on col·locar el codi font de l’aplicació. Bàsicament és un mòdul principal i un o més mòduls que contenen codi que fa servir el mòdul principal
  • una carpeta on col·locar el codi de tests que, per pytest, per defecte serà test/. Aquesta carpeta ha de poder contenir també mòduls de suport pels tests.

Exemple

L’exemple de projecte amb que il·lustraré aquesta estructura és molt bàsic

  • el projecte es dirà projecte
  • el mòdul principal es dirà main.py i farà servir funcionalitats del mòdul de suport utilmain.py
  • el mòdul de test de main es dirà, com sol ser habitual, test/test_main.py i farà servir funcionalitats de test/utiltest.py.

El mòdul principal, en aquest exemple, oferirà la funció absolutitza() que rebrà un valor (esperem que numèric) i retornarà la versió absoluta. Sí, Python ja ens ofereix aquesta funcionalitat amb abs() però no voldrem aquí un exemple tan complex com perquè Python no el tingui resolt, oi?

Per poder portar a terme aquesta funcionalitat, absolutitza() fa servir la funció utilmain.es_positiu() que dirà si el valor que li passem és o no positiu.

Estructura de carpetes

L’estructura de carpetes per aquest projecte serà:

projecte/$ tree
.
├── __init__.py
├── main.py
├── test
│   ├── __init__.py
│   ├── test_main.py
│   └── utiltest.py
└── utilmain.py

Aconseguim les carpetes amb la comanda:

$ mkdir -p projecte/test

Continguts

Els fitxers __init__.py permeten indicar a Python que consideri tota la carpeta on apareixen com un mòdul. No cal que tinguin res però hi podríem afegir codi arbitrari per inicialitzar coses. Aquí simplement els crearem buits

Aconseguim els fitxers __init__.py amb la comanda:

$ touch projecte/__init__.py projecte/test/__init__.py

El contingut dels altres fitxers seria:

  • main.py
# projecte/main.py

from projecte import utilmain

def absolutitza(valor):
    return valor if utilmain.es_positiu(valor) else -valor

  • utilmain.py
# program/utilmain.py

def es_positiu(valor):
    return valor >= 0
  • test_main.py
# test_main.py

from projecte.test import utiltest
from projecte.main import absolutitza

def test_quan_es_negatiu():
    utiltest.printlog("absolutitza(-1)")
    assert absolutitza(-1) == 1

def test_quan_es_positiu():
    utiltest.printlog("absolutitza(1)")
    assert absolutitza(1) == 1
  • utiltest.py
# utiltest.py

def printlog(missatge):
    print('Comprovant', missatge)

Fixa’t especialment en els imports. Són absoluts al projecte.

Una mica farragós, potser, però pylint els detecta sense problemes

Resultat

Un cop tenim això, ja podem passar els tests

projecte/$ pytest
======================== test session ========================
platform linux -- Python 3.7.3, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /tmp/ramdisk/projecte
collected 2 items                                                                                                   

test/test_main.py ..                                                                                          [100%]

===================== 2 passed in 0.01s =====================

Conclusions

Amb aquesta estructura aconseguim que el nostre projecte pugui tenir tests (amb mòduls d’ajuda de tests)

El cost és crear dos __init__.py i complicar el mecanisme d’importació doncs cal indicar a tot arreu el nom del projecte. Sembla, però, que aquesta és la manera recomanada, així que començaré a fer-lo servir a partir d’ara i, si trobo res millor… actualitzaré aquesta entrada.

Written by blami

4 Setembre 2021 at 14:06

Arxivat a General

Tagged with ,

Design a site like this with WordPress.com
Per començar