lunedì 23 dicembre 2013

MP3 e ConcurrentQueue

L'altro giorno ho realizzato che avere i propri LP (per la vecchia scuola) in flac su un telefonino è oltremodo stupido, in quanto probabilmente nessuno noterebbe mai la differenza.

Decido quindi di convertire la libreria in MP3 (320k).

Siccome un programmatore non è contento se non fa le cose da sé, ho scritto un piccolo wrapper per ffmpeg (che a sua volta include le librerie LAME) avendo cura che le conversioni occupino tutta la potenza di calcolo disponibile, lanciando una istanza per core.

Per evitare di incorrere in elaborazioni "doppione", è necessario sincronizzare i thread affinché si eviti che più di un thread gestisca un singolo file.

Ci viene in aiuto il namespace System.Collections.Concurrent che include delle collezioni concepite allo scopo.

Nella fattispecie, ho scelto ConcurrentQueue per la sua logica FIFO (First In First Out).

Per Queue si intende una coda, proprio come quella del supermercato o delle poste: chi prima arriva, prima se ne va.

Per questo tipo di applicazione poteva andar bene anche uno Stack (pila) con logica LIFO (Last In First Out) perché l'ordine in cui vengono convertiti i file non è importante.

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Collections.Concurrent;
 
namespace mp320kenc
{
    class Program
    {
        static string encpath = getResourceFullPath("ffmpeg.exe");
 
        static void Main(string[] args)
        {
            if(args.Length > 0)
            {
                ConcurrentQueue<filetoencode> q = new ConcurrentQueue<filetoencode>();
                string dir = args[0];
                DirectoryInfo di = new DirectoryInfo(dir);
                string newdir = di.Parent.CreateSubdirectory(di.Name + " - MP3").FullName;
 
                foreach(FileInfo f in di.GetFiles())
                {
                    string ext = f.Extension.ToLower();
 
                    if(ext == ".wav" || ext == ".flac")
                    {
                        q.Enqueue(new filetoencode(f.FullName, Path.Combine(newdir, Path.GetFileNameWithoutExtension(f.Name)+".mp3")));
                    }
                }
 
                for (int i = 0; i < Environment.ProcessorCount; i++)
                {
                    System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(encode));
                    t.Start(q);
                }
            }
        }
        static void encode(object b)
        {
            ConcurrentQueue<filetoencode> q = (ConcurrentQueue<filetoencode>)b;
 
            while (!q.IsEmpty)
            {
                filetoencode enc;
                if(q.TryDequeue(out enc))
                {
                    Console.WriteLine("Encoding " + Path.GetFileName(enc.path));
                    ProcessStartInfo p = getPCI(enc.path, enc.dest);
                    Process.Start(p).WaitForExit();
                }
            }
        }
        public static String getResourceFullPath(String name)
        {
            return Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase), name).Substring(6);
        }
        private static ProcessStartInfo getPCI(string oldpath, string newpath)
        {
            ProcessStartInfo p = new ProcessStartInfo();
            p.WindowStyle = ProcessWindowStyle.Hidden;
            p.CreateNoWindow = true;
            p.UseShellExecute = false;
            p.Arguments = String.Format("-i \"{0}\" -acodec mp3 -b:a 320k \"{1}\"",oldpath,newpath);
            p.FileName = encpath;
            return p;
        }
        class filetoencode
        {
            public string path;
            public string dest;
 
            public filetoencode(string p, string de)
            {
                path = p;
                dest = de;
            }
        }
    }
}