Setting up a cheap 5.1 surround sound setup reusing hardware

Initial 4.0 setup

I had some sort of custom 4.0 (4 speaker, 0 subwoofer) surround setup, aka quadraphonic, associated through Sweex 7.1 external USB sound card connected to a laptop running Devuan GNU/Linux – video output going through HDMI to a projector.

This surround was composed of a an old Denon PMA-715R amplifier (65 W per channel 8Ω) coupled with bookshelf loudspeakers Infinity reference 1 mkII (50W 6Ω) for the two front speakers, and an Edifier M3280BT 2.1 sound system (8W per channel + 20W subwoofer) for the rear speakers.

Yes, it meant that the Edifier subwoofer was actually unused and it could have been used to make a 4.1 setup. But it made sense to keep the best hardware for the front.

It also meant that it was connected to the Sweex in 5.1 mode (only designed to do 7.1/5.1/2.1 – not 4.0), using only two plugs (2 rear, 2 front = 4.0) out of the 3 required (front/bass = 1.1, 4.0+1.1 = 5.1). So it meant also using the extra-stereo filter in Smplayer, otherwise sound would sometimes be incomplete, especially with movies with a complex 5.1 flow.

Not perfect but good enough for years.

5.1 setup

The gradual death of the old Denon PMA-715R amplifier changed that.

A full surround setup is expensive and would leave me with unused hardware, especially the Infinity loudspeakers.

I bought two Ruizhi 2x50W + 100W amps for 30 euros each (well, I could have bought the simpler Ruizhi 2x50W for 10 euros each, but I was not entirely decided about the final setup at that moment), 12V DC adapters to go along, and two Edifier P12 20W speakers.

I doubt these Ruizhi can actually do 2x50W proper but if they do only half it will be enough.

The setup is now: front left+right = Infinity 50W + Ruizhi 50W ; rear left+right = Edifier P12 20W + Ruizhi 50W ; center+bass : Edifier M3280BT 8Wx2+20W.

It would be possible to use bluetooth with all these (Ruizhi and M3208BT) but I kept if simple with cables.

Such setup from scratch cost around 30 * 2 (Ruizhi) + 80 (P12) + 60 (M3280BT is no longer sold but similar models can be found between 50 to 90 euros, like Edifier M1370 or XB6BT) + 150 (passive 50/80W loudspeakers can be found from 100 to 200 euros) + cables =~ 400 euros for the whole. It is not entirely sure it is worth it: very cheap 5.1 setups, around 200 euros, would be as good and models around 400 euros might be better.

Reusing existing hardware, as in my case, we are down to 140 euros.

Preventing stored files deletion without assigning to root or using chattr

To prevent accidental or malicious files deletion (for instance a local collection of images, or a collection of movies on a Samba server), one option is to grant the directory to root or use chattr to make these files immutables (which also require root privileges).

That works. But any further modification would then require root privileges.

The proposed approach is, instead, to change ownership of files that reached a certain age (one week, one month or one year) to a dedicated “read-only” user, in a way that usual users can still add new files in the collection directories but no longer remove the old ones to safekeep.

This is not opposed to backups or filesystem snapshots, it is a step to prevent data loss instead of curing it.

Say on your video storage library on a samba server, files added by guest users are forcibly assigned to nobody:smbusers. You would then create a dedicated nobody-ro user and would configure in /etc/read-only-this.conf the library path to be handled by the read-only-this.pl script. Run by a daily cronjob, the read-only-this.pl script would reassign all files older than say one week to nobody-ro:smbusers, with no write group privilege. Directories would get the special T sticky bit so samba guest users would still be able to add new files but not remove old ones.

It would be possible to actually allow nobody-ro to log in through samba, shell or whatever scripts, to enable file removal or directory reorganisation. But the video storage library is protected from mistakes from regular users or malicious deletion by scripts using regular users accounts.

(Note that the read-only-this.pl script cares only for video/image/audio/documents mime types – the mime-type selection might later be added as configuration option)

It is included in the rien-common package.

Converting video files to H.265/HEVC, no washed out colors and all streams mapped, with ffmpeg

I had a few 1080p video files using AV1 codec. Not sure why, if it is a player issue or hardware issue, nonetheless, my (slightly aging) laptop was having a hard time playing these while playing with no effort at all H.265/HEVC 2160p videos. The following command converts all mkv files in a folder to H.265/HEVC, not washing out colors and keep all streams:

for av in *.mkv; do ffmpeg -i "$av" -c:v libx265 -color_trc smpte2084 -color_primaries bt2020 -crf 22 -map 0 -map_metadata 0 -map_chapters 0 -c:a copy -c:s copy "${av%.*}"-x265.mkv ; done

In my case, it results in slightly larger files but, and that was the point, these play on the laptop with no noticeable CPU-usage:

1,2G 'XX1 AV1 Opus [AV1D].mkv'
1,1G 'XX1 AV1 Opus [AV1D]-x265.mkv'
830M 'XX2 AV1 Opus [AV1D].mkv'
951M 'XX2 AV1 Opus [AV1D]-x265.mkv'

Sorting and moving automatically videos from download to storage directory

In the spirit the earlier scripts to clean up ogg/mp3 collection (tags, filenames) with lltag, the following script is a proposal to automatically move, from download directory to storage directory, the video files that deserved to be kept. This is especially useful when both directories are on different physical drives and, as such, take a while, and typically with implies heavy IO usage at the moment you are actually using the computer that host the drivers.

The idea is to put a simple mark on directories or files, in the download area, that should be move to the storage area, assuming storage area contains top directories to sort files. The mark is arbitrary: ##Mark##, Mark matching a top directory within the download area.

For example, say we have in the download area the file Vabank II, czyli riposta (1985) DVDRip XviD AC3-BR.avi, associated with a subtitle file Vabank II, czyli riposta (1985) DVDRip XviD AC3-BR.en.srt, within a directory named Vabank II. It must go in storage area top directory Action Espionnage.

The way the script sort-download-area.pl works requires only the Vabank II or Vabank II, czyli riposta (1985) DVDRip XviD AC3-BR.avi to be renamed to include ##Action Espionnage##. And, obviously, to make it more practical, if can be also ##Action##, ##action##, ##Espionnage##, ##AE##, ##ActionEspionnage##, and others aliases as long as they are not confusing regarding other top directories of the download area.

Then running sort-download-area.pl --download /mnt/download --storage /mnt/storage (assuming these are the relevant directories) will take care of moving the found video and text/subtitles files (based on mime-type and filenames). The old directory will remain, the script won’t take any risk to erase any data by itself.

It can be run with --debug option to make a dry-run, to check if everything is in order, list possible marks, etc. If run as root, it will take care of changing mode and ownership to match the relevant download area top directory.

Once a satisfying setup is in place (assuming the script is in /usr/local/bin), it is enough to add a /etc/cron.daily/sort-download-area like:

#!/bin/sh
/usr/local/bin/sort-download-area.pl  --download /mnt/download --storage /mnt/storage

Here the current version of the sort-download-area.pl (but you are advised to always take the latest gitlab version) :

#!/usr/bin/perl

use strict "vars";
use Fcntl ':flock';
use POSIX qw(strftime);
use File::Find;
use File::Basename;
use File::Path qw(make_path);
use File::Copy qw(move);
use File::MimeInfo;
use File::Slurp qw(read_dir);
use Getopt::Long;
use Term::ANSIColor qw(:constants);

# config:
my $user = "nobody";
my $group = "nobody";
my ($download, $storage);
my $debug = 0;
my ($getopt, $help);

# get standard opts with getopt
eval {
    $getopt = GetOptions("debug" => \$debug,
			 "help" => \$help,
			 "download|d:s" => \$download,
			 "storagedir:s" => \$storage);
};

if ($help) {
    print STDERR <<EOF;
Usage: $0 [OPTIONS]

    -d DIR, --download DIR   (mandatory) path to the download/input area
    -s DIR, --storage DIR    (mandatory) path to the storage/output area

    --debug                  Dry-run debug test


Author: yeupou\@gnu.org
       https://yeupou.wordpress.com/

EOF
    exit(1);    
}

unless ($download and $storage) {
    die "Both --download INDIR and --storage OUTDIR must be provided.\nExiting";
}
unless (-d $download and -d $storage) {
    die "Both $download (--download) and $storage (--storage) must exists.\nExiting";
}

sub debug {
    return unless $debug;
    print $_[1] if $_[1]; 
    print $_[0];
    print RESET if $_[1];
    print "\n";
}

########################################################################
## run

# silently forbid concurrent runs
# (http://perl.plover.com/yak/flock/samples/slide006.html)
open(LOCK, "< $0") or die "Failed to ask lock. Exit";
flock(LOCK, LOCK_EX | LOCK_NB) or exit;


####
#### Find out current possible storage top-dirs
#### (with their respective uid/gid)
# value equal to the real-top dir
my %storage_topdir;
# uid/gid of the real-top dir
my %storage_topdir_uid;
my %storage_topdir_gid;
# keep a list of confusing marks
my %storage_topdir_confusingmark;


debug("\n\nStorage ($storage) top-dirs:\n", ON_CYAN);

for my $dir (read_dir($storage)) {
    next unless -d "$storage/$dir";
    next if ($dir =~ /^\./);   # skip hidden dirs

    # store top dir details
    $storage_topdir{$dir} = $dir;
    $storage_topdir_uid{$dir} = (lstat "$storage/$dir")[4];
    $storage_topdir_gid{$dir} = (lstat "$storage/$dir")[5];    
    debug("\t$storage_topdir{$dir}", GREEN);
    debug("\t($storage_topdir_uid{$dir}:$storage_topdir_gid{$dir})");

    # store also top dir useful aliases (end user might want to use shortcuts)
    # but no checks will be made in case of confusing aliases (ie two top dirs shortened in the name way)
    # for instance, Action Espionnage would also accept:
    #           action espionnage (lowercased)
    #		ActionEspionnage (removal of non-word chars)
    #		actionespionnage (lowercased removal of non-word chars)
    #		AE (only capital letters)
    #           ea (lowercase only capital letters)
    #           Action (single word apart)
    #           action (lowercased single word apart)
    #           Espionnage (single word apart)
    #           espionnage (lowercased single word apart)
    

    # alias as lowercased : WesteRn eq western
    my $alias = lc($dir);
    if ($alias ne $dir) {
	unless ($storage_topdir{$alias} or $storage_topdir_confusingmark{$alias}) {
	    debug("\t\t$alias (lowercased)");
	    $storage_topdir{$alias} = $dir;
	} else {
	    debug("\t\tlowercased alias ($alias) is confusing regarding earlier items, skipping", ON_RED);
	    $storage_topdir_confusingmark{$alias} = 1;
	    delete($storage_topdir{$alias});
	}
    }
    # alias with space in place of any non word characters
    $alias = $dir;
    $alias =~ s/[^[:alnum:]]//g;
    
    if ($alias ne $dir) {
	unless ($storage_topdir{$alias} or $storage_topdir_confusingmark{$alias}) {
	    debug("\t\t$alias (removal of non-word chars)");
	    $storage_topdir{$alias} = $dir;
	} else {
	    debug("\t\tremoval of non-word chars alias ($alias) is confusing regarding earlier items, skipping", ON_RED);
	    $storage_topdir_confusingmark{$alias} = 1;
	    delete($storage_topdir{$alias});
	}
	
	# same lowercased
	$alias = lc($alias);
	if ($alias ne $dir) {
	    unless ($storage_topdir{$alias} or $storage_topdir_confusingmark{$alias}) {
		debug("\t\t$alias (lowercased removal of non-word chars)");
		$storage_topdir{$alias} = $dir;
	    } else {
		debug("\t\tlowercased removal of non-word chars alias ($alias) is confusing regarding earlier items, skipping", ON_RED);
		$storage_topdir_confusingmark{$alias} = 1;
		delete($storage_topdir{$alias});
	    }
	}
    }
    # alternatively, only keep the capitalized letters
    $alias = $dir;
    $alias =~ s/[^[:alnum:]]//g;
    $alias =~ s/[^[:upper:]]//g;
    if ($alias ne $dir) {
	unless ($storage_topdir{$alias} or $storage_topdir_confusingmark{$alias}) {
	    debug("\t\t$alias (only capital letters)");
	    $storage_topdir{$alias} = $dir;
	} else {
	    debug("\t\tonly capital letter alias ($alias) is confusing regarding earlier items, skipping", ON_RED);
	    $storage_topdir_confusingmark{$alias} = 1;
	    delete($storage_topdir{$alias});
	}
	# same lowercased     
	$alias = lc($alias);
	unless ($storage_topdir{$alias} or $storage_topdir_confusingmark{$alias}) {
	    debug("\t\t$alias (lowercased only capital letter alias)");
	    $storage_topdir{$alias} = $dir;
	} else {
	    debug("\t\tlowercased only capital letter alias ($alias) is confusing regarding earlier items, skipping", ON_RED);
	    $storage_topdir_confusingmark{$alias} = 1;
	    delete($storage_topdir{$alias});
	}
    }
    # finally, if several worlds compose a string, try to register each
    # (this is where it is most likely to find confusing aliases)
    if (split(" ", $dir) > 1) {
	foreach my $word (split(" ", $dir)) {
	    $alias = $word;
	    unless ($storage_topdir{$alias} or $storage_topdir_confusingmark{$alias}) {
		debug("\t\t$alias (single word apart)");
		$storage_topdir{$alias} = $dir;
	    } else {
		debug("\t\tsingle word apart alias ($alias) is confusing regarding earlier items, skipping", ON_RED);
		$storage_topdir_confusingmark{$alias} = 1;
		delete($storage_topdir{$alias});
	    }
	    $alias = lc($alias);
	    unless ($storage_topdir{$alias} or $storage_topdir_confusingmark{$alias}) {
		debug("\t\t$alias (lowercased single word apart)");
		$storage_topdir{$alias} = $dir;
	    } else {
		debug("\t\tlowercased single word apart alias ($alias) is confusing regarding earlier items, skipping", ON_RED);
		$storage_topdir_confusingmark{$alias} = 1;
		delete($storage_topdir{$alias});
	    }
	}
    }
        
}


####
#### Find out any file or directory that we should be moving
#### (do not start moving files unless we checked everything)

# build an hash of files to move
# (with a secondary hash to keep track of the storage topdir) 
my %tomove;
my %tomove_topdir;


debug("\n\nDownload ($download) files:\n", ON_CYAN);

sub wanted {
    # $File::Find::dir is the current directory name,
    # $_ is the current filename within that directory
    # $File::Find::name is the complete pathname to the file.

    # check if we have a ##STRING## inside
    my $mark;
    $mark = $1 if $File::Find::name =~ m/##(.*)##/;

    # none found, skipping
    next unless $mark;

    # string refers to non-existant directory, skipping
    unless ($storage_topdir{$mark}) {
	debug("Mark $mark found for $File::Find::name while no such storage directory exists in $storage", ON_RED);
	# this is an issue that requires manual handling, print ont STDERR
	print STDERR ("Mark $mark found for $File::Find::name while no such storage directory exists in $storage\n");
	next;
    }

    # take into account only videos and text files
    my $suffix;
    $suffix = $1 if $_ =~ /([^\.]*)$/;
    my ($mime_type,$mime_type2) = split("/", mimetype($File::Find::name));
    if ($mime_type ne "video" and
	$mime_type ne "text") {
	# second pass to allow even more text files based on extension
	# (subtitles : srt sub ssa ass idx txt smi)
		
	unless ($suffix eq "srt" or
		$suffix eq "sub" or
		$suffix eq "txt" or
		$suffix eq "ssa" or
		$suffix eq "ass" or
		$suffix eq "idx" or
		$suffix eq "smi") {
	    debug("\tskip $_ ($mime_type/$mime_type2 type)");
	    next;		
	}
    }

    my $destination_dir = "$storage/$storage_topdir{$mark}";
    my $destination_file = $_;
    $destination_file =~ s/##(.*)##//g;
    $destination_file =~ s/^\s*//;
    $destination_file =~ s/\s*$//;

    # now handle the special S00E00 case of series, like 30 Rock (2006) - S05E16 or 30 Rock S05E16
    my ($season, $before_season, $show);
    $before_season = $1 and $season = $2 if $_ =~ m/^(.*)S(\d\d)\ ?E\d\d[^\d]/i;
    if ($season) {
	# there is a season, we must determine the show name
	#    30 Rock (2006) - S05E16 => 30 Rock
	# end user must pay attention to have consistent names
	$show = $1 if $before_season  =~ m/^([\w|\s|\.|\'|\,]*)/g;
	# dots often are used in place of white spaces
	$show =~ s/\./ /g;    
	# keep only spaces in shows name, nothing else
	$show =~ s/[^[:alnum:]|\ ]//g;    
	$show =~ s/^\s*//;
	$show =~ s/\s*$//;
	# capitalize first letter
	$show =~ s/\b(\w)/\U$1/g;
	
	# if we managed to find the show name, then set up the specific series tree
	last unless $show;
	debug("found show: $show", MAGENTA);
	$destination_dir = "$storage/$storage_topdir{$mark}/$show/S$season";	    	
    }

    
    # if we reach this point, everything seems in order, plan the move
    debug("plan -> $destination_dir/$destination_file");
    $tomove{$File::Find::name} = "$destination_dir/$destination_file";
    $tomove_topdir{$File::Find::name} = $storage_topdir{$mark};


    # additionally, if we deal with a video, look for any possibly related file to add also that would not have been picked
    # otherwise
    if ($mime_type eq "video") {
	my $other_files_path = $File::Find::name;
	$other_files_path =~ s/\.$suffix$//g;

	debug("glob $other_files_path*");
	my @other_files =
	    glob('$other_files_path*.srt'),
	    glob('$other_files_path*.sub'),
	    glob('$other_files_path*.txt'),
	    glob('$other_files_path*.ssa'),
	    glob('$other_files_path*.ass'),
	    glob('$other_files_path*.idx'),
	    glob('$other_files_path*.smi');
	foreach my $file (@other_files) {
	    debug("plan -> $destination_dir/$file");
	    $tomove{"$File::Find::dir/$file"} = "$destination_dir/$file";
	    $tomove_topdir{"$File::Find::name/$file"} = $storage_topdir{$mark};
	}		    
    }

    debug();
      
}
find(\&wanted, $download);

####
#### Actually move files now
####

debug("\n\nMove from download ($download) to storage ($storage):\n", ON_CYAN);

foreach my $file (sort keys %tomove) {

    debug(basename($file), YELLOW);

    my $uid = $storage_topdir_uid{$tomove_topdir{$file}};
    my $gid = $storage_topdir_gid{$tomove_topdir{$file}};    

    # create directory if needed
    my $dir = dirname($tomove{$file});
    unless (-e $dir) {
	make_path($dir, { chmod => 0770, user => $uid, group => $gid }) unless $debug;
	debug("make_path $dir (chmod => 0770, user => $uid, group => $gid)");
    }

    # then move the file (chown if root)
    # avoid overwriting, add number in the end, no extension saving
    my $copies;
    if (-e $tomove{$file}) {
	while (-e "$tomove{$file}.$copies") {
	    $copies++;
	    # stop at 10, makes no sense to keep more than that amount of copies
	    last if $copies > 9;
	}
    }    
    $tomove{$file} .= ".$copies" if $copies;
    move($file, $tomove{$file}) unless $debug;
    chown($uid, $gid, $tomove{$file}) unless $debug or $< ne 0;
    debug("$file -> $tomove{$file}");

    debug();	  
}

# EOF

Build a simple mobile music player with mpd and a Raspberry Pi B+

Turned out that my kitchen device was not satisfying. Not for the reasons suggested in comments, not because I wanted to use cheezy OS like the ones actually supported for most tablets (last time I checked, you cannot get a decent libre OS with full hardware support) for instance. But  the Raspberry Pi B+ is just not powerful enough to browse Internet of these days with such a resolution. It is just too slow.

On another hand, for years, I had issues with a declining portable music player I have plugged into the car audio system (that have RCA connectors or otherwise only specific mp3 files support). Either it got stuck on some files, or it had problems to recharge. And even working best as it could, the random mode seemed to have a few songs in favor.

So, for less than 30 €, I ordered a tactile 3,5″ screen from Quimat. It work fines with Raspbian, provided you use their specific script that you can obtain via (otherwise the screen would  remain white):

git clone https://github.com/goodtft/LCD-show.git

The box isn’t perfect, on one side the screen won’t be properly supported. But I do not intend to put my paws so much on it so let’s say it is acceptable for such price.

Then, to get some acceptable music player system, I went for a mpc/mpd solution, not wanting to bother with Kodi or any complicated solution that might not work or require a dedicated system other  than raspbian.

So I ended up with mpd along with awesomewm and a few wrapper scripts for mpc just build playlist or send OSD notifications.

IMG_20171216_154453

(since the screen I improved the icon set, removed the visible cursor)

I use cava to provide a visualizer. Access to the device is made through anonymous Samba. My -utils-mpc package carries such setup mostly based on mpc-monitor (check currently played, could be used to made stats or scrobbling later), mpc+notify (run mpc command with sendnotify call),  mpc-playlist-build, mpc-playlist-next and few sample conffiles (awesome/rc.lua, smb.conf, redshift.conf + extra details in the README about input calibration, mpd.conf).

This Raspbian was purged of systemd, because I do want unexpected troubles, and of pulseaudio, because it causes mpd sometimes to stall and works perfectly without.

All files at stored in the main mpd music directory. Any file within a subdirectory will be treated as belonging to a specific playlist.

Plugging the USB energy input on the car relevant plug generates some odd noise: it has to be plugged to an energy bank. It seems to draw very little power.

Last step was to fill the 32GB USB key serving as storage for the music directory. Turns it was quite boring to hand pick such amount of files. So I used another quite crude script to fill it, taking randomly two thirds of available files for a given directory (a band name):

#!/bin/bash
DEST=/media/user/mpdmusic

if [ ! -d "$PWD/$1" ]; then echo "$PWD/$1" not found && exit; fi

LIST=`find "$1" | grep -v .JPG$ | grep -v .jpg$ | grep -v .png$ | shuf`

COUNT=0
for file in $LIST; do
 [ -d "$file" ] && continue ;
 COUNT=$(($COUNT+1))
done

echo $COUNT
THIRD=$(($COUNT / 3))
COUNT=$(($COUNT - $THIRD))
echo a third is... $COUNT
# div by 3 and and skip this count

if [ "$2" ]; then COUNT=$2; fi

echo but... $COUNT

for file in $LIST; do
 if [ "$COUNT" -lt 0 ]; then exit ; fi
 [ -d "$file" ] && continue ;
 #echo $COUNT $file
 COUNT=$(($COUNT - 1))
 cp -v "$file" $DEST/`basename "$file"`
done

Quite crude indeed. mpc-monitor could be used to make stats to, in the end, remove unwanted out. But for now it should properly replace dying mp3/ogg player that you have no control over beside the power-off and play button.

Sure, maybe there are cool mp3/ogg/whatever players out there that could come for cheaper. Not really the point, I enjoy having full control over this one, even if I am not using more than 0,001% of this power. And, BTW, I intend, for another pre-electronics vehicule, to get a proper setup with music player and GPS so any experience in this regard is worth it.

 

 

 

 

Using same soundcard among users with PulseAudio not in system mode

Sound on GNU/Linux never have been convenient. Right now, de facto standard is PulseAudio: yeah, made by the same people that does this nightmare of systemd. When it works it is better than just ALSA  (Advanced Linux Sound Architecture). When it doesn’t, you’re in for a headache.

Anyway, I had this situation where I wanted user whatever to be able to use the soundcard. But the soundcard was blocked and reserved by PulseAudio started by my regular user account.

First option is to make PulseAudio work as a system daemon. UNIX-style option. Quite obviously, that would be too easy to implement for these systemd people. So they implemented the option altogether advising not to use it. I did not care about the advice, though, so I tried. And then I understood why, while advising not to use it, they said they would not be accountable for problems using it. Because it is utter trash, unreliable, giving out error endless messages and, in the end, not working at all.

 So the system mode is a no-go, in the short run and definitely not in the long run either.

Alternate option is to open PulseAudio through the loopback network device. To do so, in /etc/pulse/default.pa add the TCP module with 127.0.0.1:

load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1

Obviously, by default tcpwrapper will refuse access, so you have also to add the relevant counterpart in /etc/hosts.allow :

pulseaudio-native: 127.0.0.1

From now on, after restarting PulseAudio, you should be able to access it through any user (in audio group).

Update: some comments on reddit made me think there has been a misunderstanding on the scope of this post. It is not to describe inner workings of audio on common GNU/Linux systems with PulseAudio. The following does and almost perfectly explain why I did not bother get specific on the topic :

1000px-Pulseaudio-diagram.svg.png

Creating primitive subtitle (SubRip/.srt) file from scratch

I had a video I wanted to add a few subtitles lines here and there, without much effort. I ran:

$ apt-cache search subtitle editor
aegisub - Advanced subtitle editor
aegisub-l10n - Aegisub language packages
gaupol - subtitle editor for text-based subtitle files
libsubtitleeditor-dev - subtitleeditor lib - development files
libsubtitleeditor0 - subtitleeditor lib - runtime files
subtitleeditor - Graphical subtitle editor with sound waves representation
python-aeidon - reading, writing and manipulating text-based subtitle files
gnome-subtitles - Subtitle editor for the GNOME Desktop environment

and installed a few of them.

I was quite a disappointing experience. These softwares are more or less working but none was easy and fast enough to use for me not to prefer just using mpv and emacs, viewing with mpv and typing relevant lines in a text file with  timestamps.

Some of these softwares looked almost ok but adding new lines that was generally inconvenient (not able to insert at the exact place the playthrough is paused, or supposed to be able to but simple not working).

So, yeah, I got the simple way and built a file like :

1:01:01 adjkajdalm kadmùlajpod jaaaaaaaa aaaaaaaa aaaaaaaaaaaaaaaaaaa aaaaaaaaaaaa
11:01:01 adjkajdalm kadmùlajp odjaaaaaaaaa aaaaaaaaaaaaaaaaaa
111:01:01 adjkajda

And wrote a fast primitive script as follows:

#!/usr/bin/perl
use strict;
my $file = "THIS";
open(IN, "< $file.txt"); # input
open(OUT, "> $file.srt");
my $count = 0;
while (<IN>) {
 next if /^\s$/; #skip empty
 $count++;
 print OUT "$count\n";
 if (/^(\d{1,3})\:(\d{2}):(\d{2})\s/) { # extract time HH:MM:SS
 my ($h, $m, $s) = ($1, $2, $3);
 s/^$h:$m:$s\s//g; # remove the time from the line
 my $duration = 3; # 3 sec average duration 
 $duration = 5 if length($_) > 34; # 5 for almost two lines
 $duration = 2 if length($_) < 15; # 2 when very short
 
 my ($hplus, $mplus) = ($h, $m);
 my $splus = ($s + $duration);
 if ($splus > 60) { $mplus = ($m + 1); $splus = ($splus - 60); }
 if ($mplus > 60) { $hplus = ($h + 1); $mplus = ($mplus - 60); }
 
 print OUT sprintf("%02d:%02d:%02d,000 --> %02d:%02d:%02d,000",
 $h, $m, $s,
 $hplus, $mplus, $splus)."\n";
 } else {
 print "$count TIME ?\n"; 
 }
 print OUT $_;
 print OUT "\n";
}
close(IN);
close(OUT);
#EOF

It requires $file to be set accordingly to the name given to the input text file, obviously.

When satisfied with the written .srt, it’s convenient to embed it in the end file:

ffmpeg -i infile.mp4 -f srt -i infile.srt -c:v copy -c:a copy -c:s mov_text outfile.mp4

 

Creating an mp4 with black screen and silent audio (with ffmpeg)

Assuming you have a  black PNG image named blck.png, you can make it a mp4 video:

ffmpeg -loop 1 -i blck.png -t 900 -r 1 -c:v libx264 blck.mp4

And if you want to concatenate it with other mp4 (see previous post), it also needs to include a sound track. So, assuming your main files are in 48000hz (otherwise adjust), you can add it with the command:

ffmpeg -f lavfi -i anullsrc=r=48000 -i blck.mp4 -to 00:15:00 -c:v copy -c:a aac -strict experimental blck-snd.mp4

(note : -to needs to be set to the expected time, option -shortest fails)

 

 

Concatenating mp4 videos (with ffmpeg)

mp4 cannot be concatenated directly. But fmpeg (and so avconv I guess) can by the way of intermediary mpeg transport streams.

Here’s a dirty bash script that does that assuming you want to concat files named like MYFILE-1.mp4 MYFILE-2.mp4. Then you would just execute the script with MYFILE- as argument.

#!/bin/bash
echo $1
[ ! -e "$1"1.*4 ] && echo "nothing for $1""1.mp4" && exit

list=""
for i in `seq 1 20`; do
[ ! -e "$1"$i.*4 ] && continue
echo "$1$i.mp4 exists, creating $1$i.ts"
ffmpeg -i "$1"$i.*4 -c copy -bsf:v h264_mp4toannexb -f mpegts "$1"$i.ts
list="$list|$1"$i".ts"
done

echo "##########################################"
list=`echo "$list" | cut -c 2-`
echo "concat of $list"
ffmpeg -i "concat:$list" -c copy -bsf:a aac_adtstoasc "$1""F.mp4"
rm -f "$1"*.ts

# let user manually remove source files after checks
# EOF

Getting correct delays for subtitles

In 2010, I posted a couple of articles regarding videos and subtitles. Today I submitted a feature request to smplayer so they include within the player the ability to save in the subtitle file the custom delay we may have selected.

Basically, it would implies for smplayer to simply do something like with libsubtitles-perl‘s subs:

subs -i -b 00:00:16.5 Boardwalk.Empire.S01E10.480p.HDTV_en.srt

I sure hope they’ll implement that soon. Should not be too complicated while at the same time very convenient as, most of the time, issues with downloaded subtitles are delay-related.