Skip to content

A brief tutorial to encode in x265 (and Opus) using FFmpeg

So far, I’ve mainly used handbrake to encode into x265 (that’s more or less the same thing as HEVC, more specifically HEVC or H.265 is the current kickass video standard, and x265 is one of the main – and best – HEVC encoders), because I don’t encode much and their GUI is comfortable. However, they lack Opus support and tend to be slow to upgrade the included x265 library, even in their nightly builds. So I looked for another solution, and eventually settled for FFmpeg.

FFmpeg is a well-known media encoding software, with support for more than 100 codecs, which are very regularly updated to their current version. Its only but big weakness is that it doesn’t have a GUI and I find the documentation a bit lacking. That steep learning curve is what prevented me from using it until now, but I eventually went ahead and took the time to figure out all I needed to. And here it goes.

Without further ado, here is the command I now use (detailed explanations follow):
for %%A in (*.mkv) do ffmpeg -i "%%A" -vf scale=400:300 -ac 2 -metadata:s:s:0 language=eng -disposition:s:0 default -codec:a libopus -b:a 48k -vbr on -compression_level 10 -frame_duration 60 -application audio -codec:v libx265 -preset veryfast -x265-params crf=23 -codec:s copy "out/%%A"

The for ... do part and the %%A thing are for Windows bash: I look for all files in the current folder named (something).mkv, I process them and I put the output file into an “out” subfolder, keeping the filename intact. Here is what the command looks like without the bash loop, with this time an AVI video as input:
ffmpeg -i "inputvideo.avi" -vf scale=400:300 -ac 2 -metadata:s:s:0 language=eng -disposition:s:0 default -codec:a libopus -b:a 48k -vbr on -compression_level 10 -frame_duration 60 -application audio -codec:v libx265 -preset veryfast -x265-params crf=23 -codec:s copy "outputvideo.mkv"

-i "inputvideo.avi" is our input file… not much to say about this.

-vf scale=400:300 means rescale the video to 400 px width x 300 px height. You can also set only the height or the width, and set the other one to “-1” so that FFmpeg will set it to the proper value to keep the original ratio. I picked those values for the example, you’ll probably want to set yours quite higher, except for testing where a tiny resolution with an ultrafast preset helps save time. To read more about scaling, see Scaling (resizing) with ffmpeg on the FFmpeg wiki.

-ac 2 means convert the audio to stereo. I use this to get a smaller audio compression, obviously you don’t have to if you want to keep 5.1 or something. To read more about changing the number of audio channels, see Manipulating audio channels with ffmpeg on the FFmpeg wiki

-metadata:s:s:0 language=eng means add a metadata that says the first subtitles track (numbered 0) is in English. The first s: is to access all streams, the second s: is to access all subtitles streams (and then obviously 0 is the index of the first and only stream in my case). See also Set a subtitle language using ffmpeg on Stackoverflow.

-disposition:s:0 default means set the first subtitles track as default. Not that unlike the previous option, only one s: is needed, as the first one mentioning streams wouldn’t make sense (disposition is always for streams). See also ffmpeg set subtitles track as default on Stackoverflow.

-codec:a libopus -b:a 48k -vbr on -compression_level 10 -frame_duration 60 -application audio is the block of parameters for the audio codec. We use libopus (Opus), with a bitrate (-b:a 48k) of 48 kbps (for Opus in stereo, that should be good enough as long as you’re not recording something highly musical), with variable bitrate (VBR) enabled (-vbr on), with the highest compression level (-compression_level 10, slower but better compression, this is actually the default), with a frame duration of 60 ms (-frame_duration 60, this is the highest possible duration value, having a longer frame duration improves quality a bit at low bitrates), with a intended use of generic audio (-application audio). See also libopus in the ffmpeg-codecs documentation and How to encode audio with Opus codec on Stackoverflow.

-codec:v libx265 -preset veryfast -x265-params crf=23 is the block of parameters for the video codec. We use libx265 (x265), with a veryfast preset (possible values range from placebo to ultrafast, with the default being medium, and are analyzed here), and using constant quality / rate factor (CRF) with a quality of 23. Unless you really need to target a specific size, I strongly advise you to use CRF instead of average bitrate: this will allow you to do only one pass at no quality cost, and will give you the best size for the quality you want. See also libx265 in the ffmpeg-codecs documentation and How to generate an MP4 with H.265 codec using FFmpeg on Stackoverflow.

-codec:s copy means we copy the subtitle stream. See also How do I add and/or keep subtitles when converting video on AskUbuntu.

Finally, “outputvideo.mkv” is our output file. Note that here we create an MKV file, but to create an MP4 file instead all you have to do is pick the .mp4 extension instead of .mkv.

A little note about multiple streams: I’ve never dealt with multiple streams of the same kind so far, so I don’t know what do to in such cases. My best guess is that there must be a way to pick streams via their index… I’ll update this post if one day I have to deal with such multistream file.

Posted in multimedia.

Script Greasemonkey pour bloquer un utilisateur sur GNT

(NB: exceptionally, this article is written in French because it’s targeted at a French, non-English speaking audience – sorry for the inconvenience)

GNT (Génération Nouvelles Technologies) est un site de news IT. Les news y sont généralement intéressantes, mais les commentaires sont souvent peuplées de trolls. Il est possible de bloquer des utilisateurs, mais il faut être inscrit et abonné. Voici un script qui vous permettra de bloquer des utilisateurs directement via Greasemonkey (pas la peine donc de se connecter et/ou s’abonner). Notez que je mentionne Greasemonkey car c’est l’extension la plus connue, mais si vous êtes sous Chrome/Chromium/Vivaldi/etc, le script marche aussi avec Tampermonkey.
Pour utiliser le script, installez-le puis modifiez la liste des utilisateurs bannis (pour l’exemple j’ai bloqué “Bruno” et “Bruno2”): il suffit de changer ou ajouter des noms dans la liste “configBannedUsers”.

Télécharger le script (installation directe dans Greasemonkey / Tampermonkey)

Copyright (c) 2016 PatheticCockroach -

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.


// ==UserScript==
// @name			GNT Ban Hammer
// @namespace		GNT_Ban_Hammer
// @description		Ignore specified users on GNT
// @version			1
// @encoding		UTF-8
// @include			http*://**
// @grant			none
// @author			PatheticCockroach -
// @license			MIT License
// @url
// ==/UserScript==

// Configure here your list of banned users
let configBannedUsers = ["Bruno","Bruno2"];

const GNT_Ban_Hammer_REPEAT_INTERVAL = 100;
const GNT_Ban_Hammer_REPETITIONS = 30;
const GNT_Ban_Hammer_RESTART_INDEX = 0;
let GNT_Ban_HammerCount = 0;

let hideParentOfComUserDiv = function(comUserDiv, userName = "") {
	console.log("GNT Ban Hammer: blocking user \"" + userName + "\"");
	comUserDiv.parentNode.innerHTML = "User "+userName+" blocked by GNT Ban Hammer";
let nodeInnerHTMLContains = function(node,text) {
	return (node.innerHTML.indexOf(text) > -1) || (node.innerHTML == text);

let GNT_Ban_Hammer_start = function(){
	console.log('GNT_Ban_Hammer_start running');
	// commentaire
	let comUserDivs = document.getElementsByClassName('comm_user');
	for(let comUserDiv of comUserDivs) {
		for(let bannedUser of configBannedUsers) {
			if(nodeInnerHTMLContains(comUserDiv, bannedUser)) {
				hideParentOfComUserDiv(comUserDiv, bannedUser);
	// tribune summary
	let tribuneUserSpans = document.getElementsByClassName('tribune-nick');
	for(let tribuneUserSpan of tribuneUserSpans) {
		for(let bannedUser of configBannedUsers) {
			if(nodeInnerHTMLContains(tribuneUserSpan, bannedUser)) {
				hideParentOfComUserDiv(tribuneUserSpan.parentNode, bannedUser);
	// tribune full
	let tribuneUserDivs = document.getElementsByClassName('pm_user');
	for(let tribuneUserDiv of tribuneUserDivs) {
		for(let bannedUser of configBannedUsers) {
			if(nodeInnerHTMLContains(tribuneUserDiv, bannedUser)) {
				hideParentOfComUserDiv(tribuneUserDiv, bannedUser);
	if (GNT_Ban_HammerCount++ < GNT_Ban_Hammer_REPETITIONS) {
		setTimeout(GNT_Ban_Hammer_start, GNT_Ban_Hammer_REPEAT_INTERVAL);
	} else {

let GNT_Ban_Hammer_restart = function() {
	console.log('GNT_Ban_Hammer_restart running');
	GNT_Ban_HammerCount = GNT_Ban_Hammer_RESTART_INDEX;
	setTimeout(GNT_Ban_Hammer_start, GNT_Ban_Hammer_REPEAT_INTERVAL);

let GNT_Ban_Hammer_armPagination = function() {
	let paginationDiv = document.querySelector('.pagination');
	paginationDiv.onclick = GNT_Ban_Hammer_restart;
	let paginationDivInner = document.querySelector('.pagination');
	let paginationLinks = paginationDiv.getElementsByTagName('a');
	for(let paginationLink of paginationLinks) {
		paginationLink.onclick = GNT_Ban_Hammer_restart;


// the part below is just here as a quick check that there is no runtime error
var input=document.createElement("input");
input.value="GreaseMonkey Button";
input.onclick = showAlert;
function showAlert() {
    alert("Hello World");

Posted in Greasemonkey scripts.

How to set up a Tor relay node in a VM

A simple way to support Tor is to simply run a relay (or, for those who lack bandwidth, run a bridge). However, I’m not a big fan of running it just like this on my PC, so I prefer to isolate it in a virtual machine. That sounds safer, and it allows me to just keep the VM image whenever I change computer. And also, it will allow you to use the very same Linux distribution as I use, for the most similar setup steps possible 😉

The first step is to install a virtualization software. But while you do, you may want to start downloading Debian, or whichever distro you want to run in your VM. I suggest getting the 64 bits Debian network install (the *netinst.iso), as we won’t need many packages. If you can use it, the torrent download is usually a lot faster than the direct download.
Back to the virtualization software: VirtualBox will do nicely. Install it, and when you do, should you choose to mess with the installation settings, make sure you keep “VirtualBox Bridge Networking” selected.

Then start create a new virtual machine: set the type to “Linux” and version to “Debian (64-bit)”.
You can leave the memory at 1024 MB. You could even try to set it lower (512 MB should actually be more than enough). This is because we will install only the bare minimum (and notably, no desktop environment). If you plan to install a desktop environment and other stuff (and basically, do more than just run the Tor relay on this), then you should consider adding a bit more RAM.
In the hard drive section, check “Create a virtual hard disk now”.
Creating a VirtualBox VM, step 1

Choose a path to store your disk image. Set the size to around 2 GB (you could probably set it to a bit less, 1.5 GB should be enough if you stick to the minimal install, but it might cause space issues when upgrading), or a bit higher if you don’t mind wasting a bit to avoid potential limitations in the future.
For hard drive disk type, pick VDI (VirtualBox Disk Image), and choose dynamically allocated storage if you’re using an SSD (otherwise, on a HDD, choosing fixed size will avoid fragmentation). Hit “create” and voilà, you’re ready to install Debian.
Creating a VirtualBox VM, step 2

Start your brand new VM. A dialog will pop up, asking you to select a start-up disk: browse to the Debian ISO image you just downloaded and select it.

First-time launch of a VirtualBox VM - Select start-up disk

You’ll arrive to the “Debian GNU/Linux installer boot menu”. Select, as you prefer, either “Install” or “Graphical install”. I’ll pick graphical install for the sake of tutorial aesthetics, but non-graphic has the same options, only you don’t get a mouse pointer to make the navigation easier.
Pick your language, country, locale, keyboard layout, etc.
At some point, you’ll have to set up a root password and a user password: I would advise to keep them all lower-case letters at first, because I once had problem with numbers (so bad that I had to reinstall as I couldn’t log in…).
Then you’ll reach the partitioning screen. Select “Manual” (as guided would create a useless swap partition), and create a unique, primary, bootable partition. When you’re done it should look like this:
Debian installation - Creating a partition

It will complain that it has no swap, just ignore it and confirm you want to write the changes. Then wait a couple of minutes for the base system to install. Pick a Debian archive mirror, configure your HTTP proxy (hopefully you have none and should leave that field empty – if you do use an HTTP proxy, I think you’ll need to search for proper ways to configure Tor), wait a bit for APT to set up, configure the popularity contest if you want, and finally you get to software selection. Unless you have specific needs or don’t want to do everything in the console (in which case you should install a desktop environment), uncheck everything.
Debian installation - Minimalist software selection

Package installation should be fast as we selected the small possible set. Then install the GRUB boot loader to the master boot record (put it on /dev/sda), and voilà, installation complete. Click continue to reboot.

Rather than jumping straight into Tor installation, first shut down the machine. We need to configure some network stuff.
In VirtualBox main screen, go to the machine’s settings. Then to Network, and configure Adapter 1 so that it is attached to “Bridged Adapter”. I do this because I have a router, and most (if not all) ISPs where I live do provide a router. If you don’t have a router, then I think NAT would be more appropriate (and then I guess you’d need to configure port forwarding within VirtualBox itself). I found this page which explains the different network connection types quite well. It’s from the WMware documentation, but seems to translate directly to VirtualBox too.

Now you can launch the VM again. Log in, and after a few seconds you should be able to see your VM in your router’s interface. You can then use your router to assign a fixed local IP to the VM, and forward ports to it. You’ll need to forward at least 1 port for Tor relaying (same port as “ORPort” in Tor’s config), and a second one if you also enable the directory (same port as “DirPort” in Tor’s config).

For the Tor installation itself, I will refer you to my previous Tor installation guide here. It’s more than 4 years old already, but it still applies pretty much to the letter.
Note that the post focuses on creating a Tor relay, but it will only take a slightly different Tor configuration to create a bridge or an exit node.
If you have any questions, the comments are available as usual 😉

Posted in Tor.

How to set up link aggregation on ThecusOS

I have a Thecus N7510 NAS (but I suppose this should work on all Thecus NASes as long as they have 2 network ports), and it’s a good NAS (very good value for a 7-drives NAS) but its big weakness is the Ethernet ports, which are 100 Mbps and not Gigabit. It is however mitigated by the fact that you can aggregate the 2 ports and double the speed (in practice I reach up to ~38MB/s transfer speeds). But configuring the link aggregation isn’t very obvious, so here is a short guide showing how to.

NB: I used firmware version, which I think is ThecusOS 5 (?). The menu path will probably be a bit different in the upcoming ThecusOS 7, but I suppose the base principles will be similar.

– Plug ethernet cables on both ethernet ports, and on your router or switch
– Go to System Network → linking aggregation
– You’ll see a WAN/LAN1 tab and a LAN2 tab
– Notice the “IP”, “Netmask” and “Gateway” items in the WAN/LAN1 tab: you’ll need to copy those later
– Click on the “+” tab
– You should now have a window like this, with “Available Interfaces” on one side and “Selected Interfaces” on the other:
ThecusOS link aggregation - Picking network interfaces
– Move both interfaces from available to selected, and click “Link”
– You will now be back to the Link Aggregation screen, in a new tab called “LINK1”
– Configure link type to “Load balance” (I’m not sure what extra stuff the other types do, see here or there of you want more details about them, but load balance is what I use and it _does_ double the speed). It should actually already be this way
– Copy “Netmask” and “Gateway” from the WAN/LAN1 tab. Assign an available IP (check what’s free on your router)
– Set default gateway to LINK1 (that should be already selected I think). Here’s a summary of what it should look like (with your own values for IP/netmask/gateway)
ThecusOS link aggregation - Setting getaway and netmask
– Click Apply. I think you will also need to reboot the NAS.
– Don’t forget now that the administration interface is on the new IP too!

And… voilà 🙂

Posted in servers.

Tweaking referer settings in Firefox (and Tor Browser)

I recently found a nice little summary of referrer-related settings in Firefox, which were modified heavily in Firefox 28. (NB: little history reminder, the settings names spell referer with just one r, as it was originally misspelled)

In about:config:

0 = always send,
1 = send if base domains match,
2 = send if hosts match

false = send real referrer,
true = spoof referrer (use target URI as referrer)

0 = send full URI,
1 = scheme+host+port+path,
2 = scheme+host+port

Referrer processing is done in this order. So if XOriginPolicy is set to 2, then spoofSource and trimmingPolicy are useless if going from to, since no referrer is sent anyway.

The old setting, network.http.sendRefererHeader, is still there and can be used to completely disable referrer (by setting it to 0). Otherwise, to use the new settings described above, set it to 2 (default = always send referrer).

Note that even with the highest privacy & security settings, it seems that Tor Browser doesn’t touch those settings, so you’ll need to set them manually in Tor Browser too if you want to reduce referrer tracking.

Posted in Firefox, privacy.

Solving a compilation problem that may occur in Eclipse with TypeScript/Angular projects

The full error message is quite useless and is as follow:

Cannot compile modules unless the ‘–module’ flag is provided with a valid module type. Consider setting the ‘module’ compiler option in a ‘tsconfig.json’ file

The only thing I found about it is this bug discussion on GitHub, which doesn’t help much either, but gives a clue: basically, check that your tsconfig.json file is there (if not, run tsc -init) and looks good.

In a recent post, I showed how to hide files in a project. I use this to hide .js and files, and the node_modules folder. It seems that, in Eclipse 4.6 “Neon”, when you hide the node_modules right from the start (before the first TypeScript compilation in your project), it may cause that error message to pop up (actually it even caused an error message about @angular/core being not found), and then it will stick around.
Sadly, the only way I found to make it disappear is to delete the project and re-create it (NB: no need to delete the whole project, just remove the project from Eclipse, then delete the .project file, and then re-create a project in Eclipse). Then run a TypeScript compilation, and after that you should be able to hide the node_modules folder without the error message reappearing.

Posted in Eclipse, programming, web development.

Hiding some file types in Eclipse projects

I recently discovered TypeScript (yay, better late than never), which is really nice to turn JavaScript into something a lot more readable, particularly when you want to do a bit of OOP. I mean, I’m not an OOP addict, but still in some cases it is much more convenient than going full procedural.
Anyhow, for those of you who don’t know it, TypeScript is a superset of JavaScript, and when you use it, you have a “compiler” that converts your TypeScript into JavaScript + a map files. Which means that for 1 .ts file you actually write and read, you end up with 2 additional files (.js and that you never really touch (at least not in the IDE). So, the file list gets clogged up, and I wondered if there is a way to hide files I don’t need to see in my usual IDE, Eclipse.

And there is an easy way indeed, here it is in just a picture:
Hiding specific file name patterns in Eclipse

And to sum it up:

  • right click on your project
  • in the menu, click properties
  • browse to Resource → Resource Filters
  • add the filters you want to exclude file patterns (you can use simple match, regular expressions, exclude by folder name, etc)
  • don’t forget to check “recursive” if you also want to match files within subfolders
  • press OK
  • You may need to refresh your project for the changes to show (left click on your project root and press F5)

I used that in Eclipse 4.6 “Neon”, but this should work across many versions.

Update (2016-07-08): my regex for .js files when using Angular 2

In Angular, there is a config file named systemjs.config.js, and I wanted to be able to view it in Eclipse. So I couldn’t just block all files matching the simple expression “*.js”. It turned out that matching a string that doesn’t contain a word using a regex isn’t trivial, but I found a solution there on stackoverflow. Transposed to matching all .js files but the ones containing “config”, here’s my regex: ((?!config).)*\.js

Posted in Eclipse, programming.

A little catch when submitting an iOS app with Facebook SDK

When you submit your app to Apple, at the end of the process they ask you if the app uses the iOS Advertising Identifier. As far as I understood, the Facebook SDK does use this identifier, so if you want to avoid wasting a week because of a rejected submission, you should declare that you use it. More specifically:

This app uses the Advertising Identifier to (select all that apply)?
– Serve advertisements within the app
– Attribute this app installation to a previously served advertisement
– Attribute an action taken within this app to a previously served advertisement

If you will be using the Audience Network framework, you must select the first option.
If you are using our core framework to track install attribution and app events, please select the second and third options.
If you are using both, select all three.


In the case of the app I’ve been working on, we track install attribution (to check of an ad on Facebook led to an app installation), so option 2 and 3.

Another interesting read (for historical purpose!):

On a side note, Facebook’s tool to see if your app seems properly configured:

Posted in programming, Xcode.

Going HTTPS (finally) with Let’s Encrypt

I’ve long used a CAcert certificate to provide secure access to this site, as I’m on a budget since the ads barely pay for 1% of the hosting. However, sadly CAcert certificates aren’t accepted by browsers without a warning first, so this remained a kind of hidden option. Now that Let’s Encrypt is getting mainstream enough, it was time to finally get a “proper” SSL/TLS certificate.

I will list here the commands I used for setting up my first Let’s Encrypt certificate, which I added to my picture gallery a couple of weeks ago. It’s actually extremely close to what’s listing in Let’s Encrypt’s “Getting Started” guide. Note that as I’m using a distribution which doesn’t have a letsencrypt package, I’m using the compile-it-yourself version, but as you’ll see it’s still very easy (only it takes up more space because you need to install Git).

First, install Git if you don’t already have it:
apt-get install git

Now go to a folder where you want to place Let’s Encrypt (anywhere you want, as long as it’s not exposed to the world – ie don’t put this into your web-facing folders), for instance:
cd /home/mycertifs

Then clone letsencrypt:
git clone

View the help if you want to (NB: at this moment, it will check for update and install):
./letsencrypt-auto --help
You see we use ./letsencrypt-auto, it’s because we use the manually installed version. If you are lucky enough to have a distro package, the command will be instead just letsencrypt

Then to create the certificate:
./letsencrypt-auto certonly --webroot -w /home/www/gal -d
This will request a certificate for and check domain ownership by placing a verification file in /home/www/gal (make sure that’s where the webserver can be reached from the world). The script takes care of generating a server private key, etc.
The first time you run the script, it will also ask for an e-mail to be used in case of issue/recovery (be sure to enter a real one!).

When all is done, the script will output generic advice + some info on your newly created certificate. Here’s a verbatim:

 - If you lose your account credentials, you can recover through
   e-mails sent to [the e-mail you entered].
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/ Your
   cert will expire on 2016-06-26. To obtain a new version of the
   certificate in the future, simply run Let's Encrypt again.
 - Your account credentials have been saved in your Let's Encrypt
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Let's
   Encrypt so making regular backups of this folder is ideal.
 - If you like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:
   Donating to EFF:          

As a bonus, here is how I configured my Apache HTTPd 2.2 virtual host. There are probably better settings for SSLCipherSuite, but as of today they still get an A at the SSL Labs HTTPS tester and at HT Bridge’s too. So most likely that should be good enough for you (and if it’s not, I’m pretty sure that then you have the budget for an EV certificate 😉 ):

<VirtualHost *:443>
   DocumentRoot "/home/www/gal/"
   <Directory "/home/www/gal/">
   allow from all
   Options -Indexes
   SSLEngine on
   SSLProtocol all -SSLv2 -SSLv3
   SSLHonorCipherOrder On
   SSLCertificateFile /etc/letsencrypt/live/
   SSLCertificateKeyFile /etc/letsencrypt/live/
   SSLCertificateChainFile /etc/letsencrypt/live/
   SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown

Last but not least, when the time comes to renew, you can run a simulation using this:
./letsencrypt-auto renew --dry-run
and renew for real using this:
./letsencrypt-auto renew

Note that I configured my virtual host manually, but I believe there is a way to configure it automatically. I’m not a big fan of letting a script do some unknown magic to my stuff unless it’s really required, and since the virtual host thing is one time only (for renewals, you should be able to keep the very same configuration), doing it by hand made sense to me.

Posted in security, servers, web development.

How to fix Android Studio 2.0 file permission issues

Error:Execution failed for task ':app:clean'.
Unable to delete file (or folder): [some file saved as magical admin]

You are probably here because you encountered the above-mentioned error in Android Studio, most likely on Windows and most likely with version 2.0+. A variant to this is a write error or an unzip error (Android Studio failing to extract some .aar archive because… it couldn’t clear the target folder first).
I tried several things:

  • FileASSASSIN, which tries different techniques to delete a file. It didn’t work but was able to tell me that the file was locked by an unknown program that wasn’t running anymore (at least that how I interpreted that “FileHandle”, if I recall correctly, was null)
  • LockHunter, which tries to unlock files (ditto, it failed to remove the file too)
  • and finally a working solution: disable Android Studio’s new feature “Instant Run” (search for “Instant Run” in settings and uncheck the box). And then restart (the computer, not just the Studio). The “funny” thing is that bug has been reported… for Linux on an NTFS partition

Another option, which I’ve used in the past on similar cases as a last resort, is to reboot into Linux and deletes the files from there. That’s actually my primary reason for still having a dual boot at the moment…

Posted in Android.