<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>xscDevBlog - LastSharp &#38; Co. &#187; part of lastsharp</title>
	<atom:link href="http://dev.xscheme.de/tag/part-of-lastsharp/feed/" rel="self" type="application/rss+xml" />
	<link>http://dev.xscheme.de</link>
	<description>Der xscheme-DevelopmentBlog</description>
	<lastBuildDate>Sun, 23 May 2010 11:40:10 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Audiostreaming mit irrKlang (C#)</title>
		<link>http://dev.xscheme.de/2008/08/audiostreaming-mit-irrklang/</link>
		<comments>http://dev.xscheme.de/2008/08/audiostreaming-mit-irrklang/#comments</comments>
		<pubDate>Thu, 14 Aug 2008 05:59:27 +0000</pubDate>
		<dc:creator>xsc</dc:creator>
				<category><![CDATA[C#]]></category>
		<category><![CDATA[audio]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[irrklang]]></category>
		<category><![CDATA[part of lastsharp]]></category>
		<category><![CDATA[streaming]]></category>

		<guid isPermaLink="false">http://dev.xscheme.de/?p=147</guid>
		<description><![CDATA[
Auf der Suche nach einem Ersatz für die DirectX-Wiedergabe in LastSharp bin ich gestern über irrKlang gestolpert. Soweit so gut: plattformunabhängig, einfach zu bedienen, eigentlich alles in Ordnung. Aber dann gab es da doch noch ein größeres Manko, das ich nicht ignorieren konnte: das Streaming von Dateien, die im Internet liegen, ist nicht möglich &#8211; [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.ambiera.com/irrklang/"><img src="http://dev.xscheme.de/wp-content/uploads/2008/08/irrklang_link_big.png" style="margin-bottom: 1em" /></a><br/><br />
Auf der Suche nach einem Ersatz für die DirectX-Wiedergabe in LastSharp bin ich gestern über <a href="http://www.ambiera.com/irrklang/">irrKlang</a> gestolpert. Soweit so gut: plattformunabhängig, einfach zu bedienen, eigentlich alles in Ordnung. Aber dann gab es da doch noch ein größeres Manko, das ich nicht ignorieren konnte: das Streaming von Dateien, die im Internet liegen, ist nicht möglich &#8211; und genau das bräuchte ich aber für LastSharp. Was also machen?</p>
<p><span id="more-147"></span></p>
<p>Über <a href="http://www.ambiera.com/irrklang/docu/index.html#fileOverriding">dieses Tutorial</a> bin ich der Sache näher gekommen: Man muss eine eigene Implementierung des Interfaces <code>IFileFactory</code> erstellen, und &#8211; falls nötig &#8211; eine eigene <code>Stream</code>-Klasse. Und letzteres war durchaus nötig, da mir keine bereitgestellte <code>Stream</code>-Klasse die entsprechenden Möglichkeit der Pufferung eines Audiostreams geboten hat. So ist die Klasse <code>InternetStream</code> geboren worden, die eine Verbindung zu einer Datei im Internet herstellt (mittels HttpWebRequest), und die empfangenen Daten in einer Datei auf der Festplatte speichern kann, auf die ebenso die Möglichkeit gleichzeitigen Zugriffs besteht:</p>
<pre class="c#" name="code">
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Threading;
using System.Windows.Forms;
using System.Collections;
using IrrKlang;

public class InternetStream : Stream
{
    // =======================================================
    // Buffer-Daten
    private Stream resp = null;
    private string BufferFile = "streaming.mp3";
    private FileStream OutputStream = null;
    private FileStream BufferStream = null;
    private Thread BufferThread = null;
    // =======================================================
    // Konstruktor
    public InternetStream(string uri, string bfile)
    {
        // Dateiname des lokalen Files
        this.BufferFile = bfile;
        // FileStream zum Schreiben öffnen
        if (File.Exists(this.BufferFile)) { try { File.Delete(this.BufferFile); } catch { } }
        this.BufferStream = new FileStream(this.BufferFile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
        // ResponseStream zum File im Internet
        this.resp = ((HttpWebRequest)WebRequest.Create(uri)).GetResponse().GetResponseStream();
        // Pufferung starten
        this.BufferThread = new Thread(new ThreadStart(this.FillBuffer));
        this.BufferThread.Start();
    }
    // =======================================================
    // Read: wenn zehn mal 0 zurückgegeben wird, werden alle
    // Streams geschlossen und der Thread beendet
    private int ReadTries = 0;
    public override int Read(byte[] array, int offset, int count)
    {
        // Ausgabe-Stream öffnen
        if (this.OutputStream == null) this.OutputStream = new FileStream(this.BufferFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
        try
        {
            // Dem Puffer Zeit geben, Daten zu holen
            while (this.OutputStream.Length == 0) Thread.Sleep(100);
            // Daten lesen
            int s = this.OutputStream.Read(array, offset, count);
            // Wenn 0: ein erfolgloser Versuch mehr
            if (s == 0) this.ReadTries++;
            if (this.ReadTries &gt; 10)
            {
                this.Close();
            }
            //
            return s;
        }
        catch { return 0; }
    }
    // =======================================================
    // Seek
    public override long Seek(long offset, SeekOrigin loc)
    {
        return this.OutputStream.Seek(offset, loc);
    }
    // =======================================================
    // Puffer-Thread
    private void FillBuffer()
    {
        byte[] buffer = new byte[20 * 1024];
        int rd = 0;
        while ((rd = this.resp.Read(buffer, 0, buffer.Length)) &gt; 0)
        {
            this.BufferStream.Write(buffer, 0, rd);
        }
    }
    // =======================================================
    // Schließen
    public override void Close()
    {
        this.OutputStream.Close();
        this.BufferStream.Close();
        this.BufferThread.Abort();
        if (File.Exists(this.BufferFile))
            try { File.Delete(this.BufferFile); }
            catch { }
        base.Close();
    }
    // =======================================================
    // Length
    public override long Length
    {
        get
        {
            if (this.OutputStream == null) return 0;
            else return this.OutputStream.Length;
        }
    }
    // =======================================================
    // Properties: CanRead, CanSeek, CanWrite
    public override bool CanRead
    {
        get { return true; }
    }

    public override bool CanSeek
    {
        get { return true; }
    }

    public override bool CanWrite
    {
        get { return false; }
    }
    // =======================================================
    // restliche Methoden, damit die Stream-Implementierung
    // komplett ist
    public override void Flush()
    {
        this.OutputStream.Flush();
    }

    public override long Position
    {
        get
        {
            return this.OutputStream.Position;
        }
        set
        {
            this.OutputStream.Position = value;
        }
    }

    public override void SetLength(long value)
    {
        this.OutputStream.SetLength(value);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        return;
    }
}</pre>
<p>Die entsprechende <code>IFileFactory</code>-Implementation:</p>
<pre class="c#" name="code">
public class StreamFileFactory : IFileFactory
{
    // =====================================
    // Öffne ein File (von Irrklang verwendete Methode)
    public Stream openFile(string filename)
    {
        if (filename.StartsWith("http://"))
        {
            // neuen Starten
            string fn = Application.StartupPath + "/streaming.mp3";
            try
            {
                InternetStream istream = new InternetStream(filename, fn);
                return istream;
            }
            catch { return null; }
        }
        else
        {
            // lokale Datei
            return new FileStream(filename, FileMode.Open, FileAccess.Read);
        }
    }
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://dev.xscheme.de/2008/08/audiostreaming-mit-irrklang/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>mp3Gain-Wrapper (C#)</title>
		<link>http://dev.xscheme.de/2008/08/mp3gain-codesnippet-c-batch/</link>
		<comments>http://dev.xscheme.de/2008/08/mp3gain-codesnippet-c-batch/#comments</comments>
		<pubDate>Tue, 12 Aug 2008 15:48:32 +0000</pubDate>
		<dc:creator>xsc</dc:creator>
				<category><![CDATA[Batch]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[part of lastsharp]]></category>

		<guid isPermaLink="false">http://dev.xscheme.de/?p=101</guid>
		<description><![CDATA[mp3Gain ist ein Programm zur (reversiblen!) Normalisierung der Lautstärke in MP3-Dateien. Die folgende Klasse kann mithilfe der Kommandozeilenversion von mp3Gain einen MP3-Titel normalisieren.

using System;
/*
 * [class] mp3Gain: Klasse zur Normalisierung von MP3-Dateien mithilfe von mp3Gain
 *
 * Copyright (C) 2008 Yannick Scherer
 * This program is free software; you can redistribute it and/or modify it [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://dev.xscheme.de/wp-content/uploads/2008/08/mp3gainlogosmall.gif" align="left" style="margin-right: 1em;" /><a href="http://mp3gain.sourceforge.net">mp3Gain</a> ist ein Programm zur (reversiblen!) Normalisierung der Lautstärke in MP3-Dateien. Die folgende Klasse kann mithilfe der <a href="http://prdownloads.sourceforge.net/mp3gain/mp3gain-dos-1_4_6.zip?download">Kommandozeilenversion von mp3Gain</a> einen MP3-Titel normalisieren.<br />
<span id="more-101"></span></p>
<pre class="c#" name="code">using System;
/*
 * [class] mp3Gain: Klasse zur Normalisierung von MP3-Dateien mithilfe von mp3Gain
 *
 * Copyright (C) 2008 Yannick Scherer
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General
 * Public License as published by the Free Software Foundation; either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program;
 * if not, see http://www.gnu.org/licenses/.
 * */

using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.IO;

class mp3Gain
    {
        // mp3Gain-Daten
        private static Process          mp3GainProcess = null;
        private static ProcessStartInfo mp3GainProcessInfo = new ProcessStartInfo(mp3GainPath);
        private static string           mp3GainCurrentFile = "";
        public static bool              mp3GainIsRunning = false;
        public static string            mp3GainPath    { get { return System.Windows.Forms.Application.StartupPath+"/mp3gain/mp3gain.exe"; } }
        public static string            mp3GainParams { get { return "/r /d 20 /k /c /q"; } }
        // Lautstärke normalisieren
        public static void NormalizeVolume(string mp3File) {
            if (!mp3GainIsRunning &amp;&amp; File.Exists(mp3GainPath) &amp;&amp; File.Exists(mp3File)) {
                // Prozess läuft
                mp3GainIsRunning   = true;
                mp3GainCurrentFile = mp3File;
                // Datei, an der gearbeitet wird
                string mp3Work = mp3File+"_work.mp3";
                File.Copy(mp3File, mp3Work);
                // mp3GainProcessInfo initialisieren
                mp3GainProcessInfo.Arguments = mp3GainParams+" \""+mp3Work+"\"";
                mp3GainProcessInfo.WindowStyle = ProcessWindowStyle.Hidden;
                // mp3Gain aufrufen
                mp3GainProcess = new Process();
                mp3GainProcess.StartInfo = mp3GainProcessInfo;
                mp3GainProcess.EnableRaisingEvents = true;
                mp3GainProcess.Exited += new EventHandler(mp3GainExited);
                mp3GainProcess.Start();
            }
        }
        public static void mp3GainExited(object sender, EventArgs e)
        {
            // Daten rückkopieren
            string mp3Work = mp3GainCurrentFile + "_work.mp3";
            if (new FileInfo(mp3Work).Length &gt; 0)
            {
                File.Copy(mp3Work, mp3GainCurrentFile, true);
                File.Delete(mp3Work);
            }
            // Reset
            mp3GainIsRunning = false;
            mp3GainCurrentFile = "";
            return;
        }

    }</pre>
<p>Eine MP3-Datei wird normalisiert, durch den Aufruf <code>mp3Gain.NormalizeVolume(&lt;MP3-Datei&gt;)</code>.</p>
<p>Innerhalb der Klasse müssen noch der Pfad zu mp3gain.exe (<em>mp3GainPath</em>) angepasst werden, sowie die gewünschten Parameter (<em>mp3GainParams</em>). Eine Übersicht über die Parameter von mp3Gain gibt es <a href="http://wiki.ubuntuusers.de/MP3Gain?rev=66766">hier</a>.</p>
<p><strong>Batch</strong></p>
<p>Ein BatchFile, das mp3Gain verwendet, könnte folgendermaßen aussehen (vorrausgesetzt, es befindet sich im selben Verzeichnis wie mp3gain.exe):</p>
<pre>@rem // Ziehen Sie eine MP3-Datei auf dieses Batchfile, um sie zu normalisieren.

@rem // In aktuelles Verzeichnis wechseln
@cd /D %~d0%~p0

@echo --------------------------------------------------------------------
@echo Normalisieren von %1 ...
@echo --------------------------------------------------------------------
@ "%cd%/mp3gain.exe" &lt;Parameter&gt; %1
@echo --------------------------------------------------------------------
@echo Prozess abgeschlossen...
@echo --------------------------------------------------------------------
@pause</pre>
<p>Dieser Code lässt sich natürlich erweitern, sodass ganze Ordner bearbeitet werden. Das überlasse ich aber jedem, der fleißig genug dafür ist&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://dev.xscheme.de/2008/08/mp3gain-codesnippet-c-batch/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>LastFM.XMLProcessor</title>
		<link>http://dev.xscheme.de/2008/08/lastxml/</link>
		<comments>http://dev.xscheme.de/2008/08/lastxml/#comments</comments>
		<pubDate>Sat, 09 Aug 2008 20:00:52 +0000</pubDate>
		<dc:creator>xsc</dc:creator>
				<category><![CDATA[C#]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[part of lastsharp]]></category>

		<guid isPermaLink="false">http://dev.xscheme.de/?p=15</guid>
		<description><![CDATA[Die Klasse LastFM.XMLProcessor kann auf Basis des Last.FM-Protokolls 1.2 eine Verbindung zum Server herstellen, sowie Playlists herunterladen. (Scrobbling unterstütz sie nicht.) Eine leicht modifizierte Version dieser Klasse kommt auch in LastSharp zum Einsatz.

using System;
using System.Xml;
using System.Net;
using System.IO;
using System.Text;
using System.Collections;

namespace LastFM
{
    // ====================================================
    // Klasse mit Hilfsfunktionen
    [...]]]></description>
			<content:encoded><![CDATA[<p>Die Klasse LastFM.XMLProcessor kann auf Basis des Last.FM-Protokolls 1.2 eine Verbindung zum Server herstellen, sowie Playlists herunterladen. (Scrobbling unterstütz sie nicht.) Eine leicht modifizierte Version dieser Klasse kommt auch in LastSharp zum Einsatz.<br />
<span id="more-15"></span></p>
<pre class="c#" name="code">using System;
using System.Xml;
using System.Net;
using System.IO;
using System.Text;
using System.Collections;

namespace LastFM
{
    // ====================================================
    // Klasse mit Hilfsfunktionen
    // ====================================================
    class Utilities
    {
        // MD5 erstellen
        public static string MD5(string password)
        {
            byte[] textBytes = System.Text.Encoding.Default.GetBytes(password);
            try
            {
                System.Security.Cryptography.MD5CryptoServiceProvider cryptHandler;
                cryptHandler = new System.Security.Cryptography.MD5CryptoServiceProvider();
                byte[] hash = cryptHandler.ComputeHash(textBytes);
                string ret = "";
                foreach (byte a in hash)
                {
                    if (a &lt; 16)
                        ret += "0" + a.ToString("x");
                    else
                        ret += a.ToString("x");
                }
                return ret;
            }
            catch
            {
                throw;
            }
        }
        // Ruft eine URI auf und liefert sie als String zurück
        public static string ExecuteURI(string uri)
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
            HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
            StreamReader sr = new StreamReader(resp.GetResponseStream());
            string result = sr.ReadToEnd();
            sr.Close();
            return result;
        }
    }

    // ====================================================
    // Datenkapselung für Playlist und Tracks
    // ====================================================
    public class Playlist : ArrayList
    {
        public string PlaylistName = "";
        public DateTime Timestamp = new DateTime();
    }
    public class Track
    {
        public string Title="";
        public string Artist = "";
        public string Location = "";
        public string Album = "";
        public string Image = "";
        public int Duration = 0;
        // Gibt die Dauer des Tracks im Format MM:SS aus
        public string DurationString()
        {
            int sec = (this.Duration / 1000) % 60;
            int min = ((this.Duration / 1000) - sec) / 60;
            return ((min &lt; 10) ? "0" : "") + min + ":" + ((sec &lt; 10) ? "0" : "") + sec;
        }
    }

    // ====================================================
    // Handshake-Klasse für Bereitstellung der Handshake-Daten
    // ====================================================
    public class Handshake : Hashtable
    {
        /// Verarbeitet die Handshake-Rückgabe
        public Handshake(string HandshakeString)
        {
            string[] lines = HandshakeString.Split('\n');
            foreach (string line in lines)
            {
                string[] p = line.Split('=');
                this.Add(p[0], p[1]);
            }
            if (!this.ContainsKey("session") || !this.ContainsKey("base_path") ||
                !this.ContainsKey("base_url"))
            {
                throw new Exception("Ungültiger Handshake! (Passwort falsch?)");
            }
        }
        // Daten (nur die wichtigsten)
        public string SessionID  { get { return (string) this["session"]; } }
        public string BasePath   { get { return (string) this["base_path"]; } }
        public string BaseURL    { get { return (string) this["base_url"]; } }
        public string RequestURL { get { return "http://" + this.BaseURL + this.BasePath + "/"; } }
        // Der Hashtable ist nicht veränderbar
        public override bool IsReadOnly
        {
            get
            {
                return true;
            }
        }
    }

    // ====================================================
    // XMLProcessor-Klasse
    // ====================================================
    public class XMLProcessor
    {
        // ========================================
        // based on: http://code.google.com/p/thelastripper/wiki/LastFM12UnofficialDocumentation
        // ========================================
        // Login-Daten
        private string user;
        private string password;
        //aktuelle Radiostationsdaten
        private bool tuned_in = false;
        private string to_station = null;
        //
        public bool TunedIn { get { return tuned_in; } }
        public string Station { get { return to_station; } }
        public string Username { get { return user; } }
        // ========================================
        public XMLProcessor(string username, string password)
        {
            this.user        = username;
            this.password = Utilities.MD5(password);
        }
        ///
        /// Führt einen Handshake mit LastFM aus.
        ///
        public Handshake DoHandshake()
        {
            // Request ausführen
            string uri =
                "http://ws.audioscrobbler.com/radio/handshake.php?version=1.3.1.1" +
                "&amp;platform=win32&amp;username=" + this.username + "&amp;passwordmd5="
                + this.password + "&amp;language=de&amp;player=lastsharp";
            string resp = Utilities.ExecuteURI(uri);
            // Daten verarbeiten (in Handshake-Klasse)
            return new Handshake(resp);
        }

        ///
        /// Setzt die Radiostation
        ///
        public bool TuneIn(Handshake hs, string station)
        {
            // Operation ausführen
            string uri = hs.RequestURL + "adjust.php?session=" + hs.SessionID + "&amp;url=" + station + "〈=de";
            string resp = Utilities.ExecuteURI(uri);
            // Überprüfen + Status setzen
            if (resp.StartsWith("response=OK"))
            {
                this.tuned_in = true;
                this.to_station = station;
                return true;
            }
            else
            {
                this.tuned_in = false;
                this.to_station = null;
                return false;
            }
        }

        ///
        /// Ruft die Playlist ab
        ///
        public Playlist GetPlaylist(Handshake hs)
        {
            // Radiostation ausgewählt?
            if (!this.tuned_in) throw new Exception("Keine Radiostation angewählt.");
            // Playlist abrufen
            string uri =
                  hs.RequestURL + "xspf.php?sk=" + hs.SessionID +
                  "&amp;discovery=0&amp;desktop=1.3.1.1";
            string resp = Utilities.ExecuteURI(uri);
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(resp);
            // =========================================
            // Verarbeiten
            // =========================================
            Playlist pl = new Playlist();
            XmlNode root = doc.FirstChild;
            if (root.Name != "playlist")
                throw new Exception("Ungültiges XML-Format der Playlist");
            // Titel der Playlist + Timestamp
            XmlNode pl_title = root.SelectSingleNode("title");
            if (pl_title != null) pl.PlaylistName = pl_title.InnerText;
            pl.Timestamp = DateTime.Now;
            // Tracklist
            XmlNode pl_tracks = root.SelectSingleNode("trackList");
            if (pl_tracks != null) for (int i = 0; i &lt; pl_tracks.ChildNodes.Count; i++)
                {
                    {
                        XmlNode trn = pl_tracks.ChildNodes[i];
                        Track tr = new Track();
                        tr.Artist     = trn.SelectSingleNode("creator").InnerText;
                        tr.Album    = trn.SelectSingleNode("album").InnerText;
                        tr.Title      = trn.SelectSingleNode("title").InnerText;
                        tr.Location = trn.SelectSingleNode("location").InnerText;
                        tr.Image    = trn.SelectSingleNode("image").InnerText;
                        tr.Duration = Convert.ToInt32(trn.SelectSingleNode("duration").InnerText);
                        pl.Add(tr);
                    }
                }
            return pl;
        }
    }
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://dev.xscheme.de/2008/08/lastxml/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
