[banshee/gst#] [gst#] Add cdda ripping feature
- From: Gabriel Burt <gburt src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [banshee/gst#] [gst#] Add cdda ripping feature
- Date: Thu, 26 May 2011 20:49:14 +0000 (UTC)
commit 8b29b8d342ae171831c2e3ce32c5b0598b1141e1
Author: Olivier Dufour <olivier duff gmail com>
Date: Wed May 25 23:06:40 2011 +0200
[gst#] Add cdda ripping feature
.../Banshee.GStreamerSharp/AudioCdRipper.cs | 428 ++++++++++++++++++++
src/Backends/Banshee.GStreamerSharp/Makefile.am | 2 +
2 files changed, 430 insertions(+), 0 deletions(-)
---
diff --git a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/AudioCdRipper.cs b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/AudioCdRipper.cs
new file mode 100644
index 0000000..952c910
--- /dev/null
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/AudioCdRipper.cs
@@ -0,0 +1,428 @@
+//
+// AudioCdRipper.cs
+//
+// Author:
+// Olivier Dufour <olivier duff gmail com>
+//
+// Copyright (c) 2011 Olivier Dufour
+//
+// 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.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using System.Threading;
+using System.Runtime.InteropServices;
+using Mono.Unix;
+
+using Hyena;
+using Banshee.Base;
+using Banshee.Collection;
+using Banshee.ServiceStack;
+using Banshee.MediaEngine;
+using Banshee.MediaProfiles;
+using Banshee.Configuration.Schema;
+
+using Gst;
+using Banshee.Streaming;
+using System.Collections;
+
+namespace Banshee.GStreamerSharp
+{
+ public class AudioCdRipper : IAudioCdRipper
+ {
+ private string encoder_pipeline;
+ private string output_extension;
+ private string output_path;
+ private TrackInfo current_track;
+ private string device;
+ private int paranoia_mode;
+ private System.Timers.Timer timer;
+
+ Gst.Pipeline pipeline;
+ Element cddasrc;
+ Bin encoder;
+ Element filesink;
+
+ public event AudioCdRipperProgressHandler Progress;
+ public event AudioCdRipperTrackFinishedHandler TrackFinished;
+ public event AudioCdRipperErrorHandler Error;
+
+ public void Begin (string device, bool enableErrorCorrection)
+ {
+ try {
+ this.device = device;
+ this.paranoia_mode = enableErrorCorrection ? 255 : 0;
+ Profile profile = null;
+ ProfileConfiguration config = ServiceManager.MediaProfileManager.GetActiveProfileConfiguration ("cd-importing");
+
+ if (config != null) {
+ profile = config.Profile;
+ } else {
+ profile = ServiceManager.MediaProfileManager.GetProfileForMimeType ("audio/vorbis")
+ ?? ServiceManager.MediaProfileManager.GetProfileForMimeType ("audio/flac");
+ if (profile != null) {
+ Log.InformationFormat ("Using default/fallback encoding profile: {0}", profile.Name);
+ ProfileConfiguration.SaveActiveProfile (profile, "cd-importing");
+ }
+ }
+
+ if (profile != null) {
+ encoder_pipeline = profile.Pipeline.GetProcessById ("gstreamer");
+ output_extension = profile.OutputFileExtension;
+ }
+
+ if (String.IsNullOrEmpty (encoder_pipeline)) {
+ throw new ApplicationException ();
+ }
+ timer = new System.Timers.Timer ();
+ timer.Interval = 200;
+ timer.AutoReset = true;
+ timer.Elapsed += OnTick;
+ Hyena.Log.InformationFormat ("Ripping using encoder profile `{0}' with pipeline: {1}", profile.Name, encoder_pipeline);
+ } catch (Exception e) {
+ throw new ApplicationException (Catalog.GetString ("Could not find an encoder for ripping."), e);
+ }
+ }
+
+ void OnTick (object o, System.Timers.ElapsedEventArgs args)
+ {
+ Format format = Format.Time;
+ State state;
+ long position;
+
+ pipeline.GetState (out state, 0);
+ if (state != State.Playing) {
+ return;
+ }
+
+ if (!cddasrc.QueryPosition (ref format, out position)) {
+ return;
+ }
+
+ RaiseProgress (current_track, TimeSpan.FromSeconds (position / (long)Clock.Second));
+ }
+
+ public void Finish ()
+ {
+ if (output_path != null) {
+ Banshee.IO.File.Delete (new SafeUri (output_path));
+ }
+
+ TrackReset ();
+
+ encoder_pipeline = null;
+ output_extension = null;
+
+ timer.Stop ();
+
+ if (pipeline != null && pipeline is Element) {
+ pipeline.SetState (State.Null);
+ pipeline = null;
+ }
+ }
+
+ public void Cancel ()
+ {
+ Finish ();
+ }
+
+ private void TrackReset ()
+ {
+ current_track = null;
+ output_path = null;
+ }
+
+ private TagList MakeTagList (TrackInfo track)
+ {
+ TagList tags = new TagList ();
+ tags.Add (TagMergeMode.Replace, CommonTags.Artist, track.ArtistName);
+ tags.Add (TagMergeMode.Replace, CommonTags.Album, track.AlbumTitle);
+ tags.Add (TagMergeMode.Replace, CommonTags.Title, track.TrackTitle);
+ tags.Add (TagMergeMode.Replace, CommonTags.Genre, track.Genre);
+
+ tags.Add (TagMergeMode.Replace, CommonTags.TrackNumber, (uint)track.TrackNumber);
+ tags.Add (TagMergeMode.Replace, CommonTags.TrackCount, (uint)track.TrackCount);
+ tags.Add (TagMergeMode.Replace, CommonTags.AlbumDiscNumber, (uint)track.DiscNumber);
+ tags.Add (TagMergeMode.Replace, CommonTags.AlbumDiscCount, (uint)track.DiscCount);
+
+ tags.Add (TagMergeMode.Replace, Gst.Tag.Date, track.Year);
+ tags.Add (TagMergeMode.Replace, Gst.Tag.Date, track.ReleaseDate);
+
+ tags.Add (TagMergeMode.Replace, CommonTags.Composer, track.Composer);
+ tags.Add (TagMergeMode.Replace, CommonTags.Copyright, track.Copyright);
+ tags.Add (TagMergeMode.Replace, CommonTags.Comment, track.Comment);
+
+ tags.Add (TagMergeMode.Replace, CommonTags.MusicBrainzTrackId, track.MusicBrainzId);
+ tags.Add (TagMergeMode.Replace, CommonTags.MusicBrainzArtistId, track.ArtistMusicBrainzId);
+ tags.Add (TagMergeMode.Replace, CommonTags.MusicBrainzAlbumId, track.AlbumMusicBrainzId);
+ return tags;
+ }
+
+ public void RipTrack (int trackIndex, TrackInfo track, SafeUri outputUri, out bool taggingSupported)
+ {
+ taggingSupported = false;
+ TrackReset ();
+ current_track = track;
+
+ using (TagList tags = MakeTagList (track)) {
+ output_path = String.Format ("{0}.{1}", outputUri.LocalPath, output_extension);
+
+ // Avoid overwriting an existing file
+ int i = 1;
+ while (Banshee.IO.File.Exists (new SafeUri (output_path))) {
+ output_path = String.Format ("{0} ({1}).{2}", outputUri.LocalPath, i++, output_extension);
+ }
+
+ Log.DebugFormat ("GStreamer ripping track {0} to {1}", trackIndex, output_path);
+
+ if (!ConstructPipeline ()) {
+ return;
+ }
+
+ // initialize the pipeline, set the sink output location
+ filesink.SetState (State.Null);
+ filesink ["location"] = output_path;
+
+ // find an element to do the tagging and set tag data
+ foreach (Element element in encoder.GetAllByInterface (typeof (TagSetter))) {
+ TagSetter tag_setter = element as TagSetter;
+ if (tag_setter != null) {
+ tag_setter.AddTag (TagMergeMode.ReplaceAll, Tag.Encoder, new Gst.GLib.Value ("Banshee 2.0.0"));
+ tag_setter.AddTag (TagMergeMode.ReplaceAll, Tag.EncoderVersion, new Gst.GLib.Value ( (2 << 16) | (0 << 8) | 0));
+ // TODO Version not hardcoded
+
+ if (tags != null) {
+ tag_setter.AddTag (tags, TagMergeMode.Append);
+ }
+
+ /*if (banshee_is_debugging ()) {
+ bt_tag_list_dump (gst_tag_setter_get_tag_list (tag_setter));
+ }*/
+
+ // We'll warn the user in the UI if we can't tag the encoded audio files
+ taggingSupported = true;
+ }
+ }
+
+ // Begin the rip
+ cddasrc ["track"] = trackIndex + 1;
+ pipeline.SetState (State.Playing);
+ timer.Start ();
+ }
+ }
+
+ bool ConstructPipeline ()
+ {
+ Element queue;
+
+ pipeline = new Gst.Pipeline ("pipeline");
+ if (pipeline == null) {
+ RaiseError (current_track, Catalog.GetString ("Could not create pipeline"));
+ return false;
+ }
+
+ cddasrc = ElementFactory.MakeFromUri (URIType.Src, "cdda://1", "cddasrc");
+ if (cddasrc == null) {
+ RaiseError (current_track, Catalog.GetString ("Could not initialize element from cdda URI"));
+ return false;
+ }
+
+ cddasrc ["device"] = device;
+
+ if (cddasrc.HasProperty ("paranoia-mode")) {
+ cddasrc ["paranoia-mode"] = paranoia_mode;
+ }
+
+ try {
+ encoder = (Bin)Parse.BinFromDescription (encoder_pipeline, true);
+ } catch (Exception e) {
+ string err = Catalog.GetString ("Could not create encoder pipeline : {0}");
+ RaiseError (current_track, String.Format (err, e.Message));
+ return false;
+ }
+
+ queue = ElementFactory.Make ("queue", "queue");
+ if (queue == null) {
+ RaiseError (current_track, Catalog.GetString ("Could not create queue plugin"));
+ return false;
+ }
+
+ queue ["max-size-time"] = 120 * Gst.Clock.Second;
+
+ filesink = ElementFactory.Make ("filesink", "filesink");
+ if (filesink == null) {
+ RaiseError (current_track, Catalog.GetString ("Could not create filesink plugin"));
+ return false;
+ }
+
+ pipeline.Add (cddasrc, queue, encoder, filesink);
+
+ if (!Element.Link (cddasrc, queue, encoder, filesink)) {
+ RaiseError (current_track, Catalog.GetString ("Could not link pipeline elements"));
+ }
+
+ pipeline.Bus.AddWatch (OnBusMessage);
+
+ return true;
+ }
+
+ private string ProbeMimeType ()
+ {
+ Iterator elem_iter = ((Bin)encoder).ElementsRecurse;
+ string preferred_mimetype = null;
+ IEnumerator en = elem_iter.GetEnumerator ();
+ while (en.MoveNext ()) {
+ Element element = (Element)en.Current;
+ Iterator pad_iter = element.SrcPads;
+ IEnumerator enu = pad_iter.GetEnumerator ();
+ while (enu.MoveNext ()) {
+ Pad pad = (Pad)enu.Current;
+ Caps caps = pad.Caps;
+ Structure str = (caps != null ? caps [0] : null);
+
+ if (str != null) {
+ string mimetype = str.Name;
+ int mpeg_layer;
+
+ Gst.GLib.Value val = str.GetValue ("mpegversion");
+
+ // Prefer and adjust audio/mpeg, leaving MP3 as audio/mpeg
+ if (mimetype.StartsWith ("audio/mpeg")) {
+ mpeg_layer = (Int32)val.Val;
+ switch (mpeg_layer) {
+ case 2: mimetype = "audio/mp2"; break;
+ case 4: mimetype = "audio/mp4"; break;
+ default: break;
+ }
+
+ preferred_mimetype = mimetype;
+
+ // If no preferred type set and it's not RAW, prefer it
+ } else if (preferred_mimetype == null &&
+ !mimetype.StartsWith ("audio/x-raw")) {
+ preferred_mimetype = mimetype;
+
+ // Always prefer application containers
+ } else if (mimetype.StartsWith ("application/")) {
+ preferred_mimetype = mimetype;
+ }
+ }
+ }
+ }
+ return preferred_mimetype;
+ }
+
+ private void RefreshTrackMimeType (string mimetype)
+ {
+ if (current_track == null)
+ return;
+
+ if (mimetype != null) {
+ string [] split = mimetype.Split (';', '.', ' ', '\t');
+ if (split != null && split.Length > 0) {
+ current_track.MimeType = split[0].Trim ();
+ } else {
+ current_track.MimeType = mimetype.Trim ();
+ }
+ }
+ }
+
+ private bool OnBusMessage (Bus bus, Message msg)
+ {
+ switch (msg.Type) {
+ case MessageType.Eos:
+ pipeline.SetState (State.Null);
+ timer.Stop ();
+ OnNativeFinished ();
+ break;
+
+ case MessageType.StateChanged:
+ State old_state, new_state, pending_state;
+ msg.ParseStateChanged (out old_state, out new_state, out pending_state);
+ if (old_state == State.Ready && new_state == State.Paused && pending_state == State.Playing) {
+ string mimetype = ProbeMimeType ();
+ if (mimetype == null)
+ return true;
+ Log.Information ("Ripper : Found Mime Type for encoded content: {0}", mimetype);
+ RefreshTrackMimeType (mimetype);
+ }
+ break;
+
+ case MessageType.Error:
+ Enum error_type;
+ string err_msg, debug;
+ msg.ParseError (out error_type, out err_msg, out debug);
+ RaiseError (current_track, String.Format ("{0} : {1}", err_msg, debug));
+ timer.Stop ();
+ break;
+ }
+
+ return true;
+ }
+
+ protected virtual void RaiseProgress (TrackInfo track, TimeSpan ellapsedTime)
+ {
+ AudioCdRipperProgressHandler handler = Progress;
+ if (handler != null) {
+ handler (this, new AudioCdRipperProgressArgs (track, ellapsedTime, track.Duration));
+ }
+ }
+
+ protected virtual void RaiseTrackFinished (TrackInfo track, SafeUri outputUri)
+ {
+ AudioCdRipperTrackFinishedHandler handler = TrackFinished;
+ if (handler != null) {
+ handler (this, new AudioCdRipperTrackFinishedArgs (track, outputUri));
+ }
+ }
+
+ protected virtual void RaiseError (TrackInfo track, string message)
+ {
+ AudioCdRipperErrorHandler handler = Error;
+ if (handler != null) {
+ handler (this, new AudioCdRipperErrorArgs (track, message));
+ }
+ }
+
+ private void OnNativeMimeType (IntPtr ripper, IntPtr mimetype)
+ {
+ if (mimetype != IntPtr.Zero && current_track != null) {
+ string type = GLib.Marshaller.Utf8PtrToString (mimetype);
+ if (type != null) {
+ string [] split = type.Split (';', '.', ' ', '\t');
+ if (split != null && split.Length > 0) {
+ current_track.MimeType = split[0].Trim ();
+ } else {
+ current_track.MimeType = type.Trim ();
+ }
+ }
+ }
+ }
+
+ private void OnNativeFinished ()
+ {
+ SafeUri uri = new SafeUri (output_path);
+ TrackInfo track = current_track;
+
+ TrackReset ();
+
+ RaiseTrackFinished (track, uri);
+ }
+ }
+
+}
+
diff --git a/src/Backends/Banshee.GStreamerSharp/Makefile.am b/src/Backends/Banshee.GStreamerSharp/Makefile.am
index 0a58174..9405300 100644
--- a/src/Backends/Banshee.GStreamerSharp/Makefile.am
+++ b/src/Backends/Banshee.GStreamerSharp/Makefile.am
@@ -4,7 +4,9 @@ LINK = $(REF_BACKEND_GSTREAMER) $(GST_SHARP_LIBS)
INSTALL_DIR = $(BACKENDS_INSTALL_DIR)
SOURCES = \
+ Banshee.GStreamerSharp/AudioCdRipper.cs \
Banshee.GStreamerSharp/BpmDetector.cs \
+ Banshee.GStreamerSharp/CddaManager.cs \
Banshee.GStreamerSharp/PlayerEngine.cs
RESOURCES = Banshee.GStreamerSharp.addin.xml
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]