diff --git a/musicus/res/musicus.gresource.xml b/musicus/res/musicus.gresource.xml index 4e94aea..acc5530 100644 --- a/musicus/res/musicus.gresource.xml +++ b/musicus/res/musicus.gresource.xml @@ -4,12 +4,10 @@ ui/ensemble_editor.ui ui/ensemble_screen.ui ui/ensemble_selector.ui - ui/import_dialog.ui - ui/import_disc_dialog.ui - ui/import_folder_dialog.ui ui/instrument_editor.ui ui/instrument_selector.ui ui/login_dialog.ui + ui/medium_editor.ui ui/performance_editor.ui ui/person_editor.ui ui/person_list.ui @@ -25,6 +23,7 @@ ui/recording_selector_screen.ui ui/selector.ui ui/server_dialog.ui + ui/source_selector.ui ui/track_editor.ui ui/track_selector.ui ui/track_set_editor.ui diff --git a/musicus/res/ui/import_folder_dialog.ui b/musicus/res/ui/import_folder_dialog.ui deleted file mode 100644 index 1b1ea91..0000000 --- a/musicus/res/ui/import_folder_dialog.ui +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - True - vertical - - - True - Import folder - - - True - True - - - True - go-previous-symbolic - - - - - - - - - True - center - center - True - 18 - vertical - 18 - - - True - 0.5 - 80 - folder-symbolic - - - - - True - 0.5 - Import from a folder - - - - - - - - True - 0.5 - Select a folder containing audio files with the button below. After adding the metdata in the next step, the folder will be copied to your music library. - center - True - 40 - - - - - Select - True - True - center - - - - - - - diff --git a/musicus/res/ui/import_dialog.ui b/musicus/res/ui/medium_editor.ui similarity index 100% rename from musicus/res/ui/import_dialog.ui rename to musicus/res/ui/medium_editor.ui diff --git a/musicus/res/ui/import_disc_dialog.ui b/musicus/res/ui/source_selector.ui similarity index 65% rename from musicus/res/ui/import_disc_dialog.ui rename to musicus/res/ui/source_selector.ui index 70bea77..876f366 100644 --- a/musicus/res/ui/import_disc_dialog.ui +++ b/musicus/res/ui/source_selector.ui @@ -1,5 +1,4 @@ - @@ -11,7 +10,7 @@ True False - Import CD + Import music True @@ -27,11 +26,6 @@ - - False - True - 0 - @@ -49,21 +43,6 @@ False error False - - - False - 6 - end - - - - - - False - False - 0 - - False @@ -75,33 +54,16 @@ Failed to load the CD. Make sure you have inserted it into your drive. True - - False - True - 0 - - - False - False - 0 - - - - - - False - True - 0 - True False + True center center 18 @@ -193,55 +155,7 @@ 1 - - - True - True - - - True - False - none - - - True - False - 500 - 300 - - - True - False - 6 - 6 - 12 - 6 - 0 - in - - - - - - - - - - - - - - - content - 2 - - - - True - True - 1 - diff --git a/musicus/src/dialogs/import.rs b/musicus/src/dialogs/import.rs deleted file mode 100644 index 3ab9e03..0000000 --- a/musicus/src/dialogs/import.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::backend::Backend; -use crate::editors::{TrackSetEditor, TrackSource}; -use crate::widgets::{Navigator, NavigatorScreen}; -use glib::clone; -use gtk::prelude::*; -use gtk_macros::get_widget; -use std::cell::RefCell; -use std::rc::Rc; - -/// A dialog for editing metadata while importing music into the music library. -pub struct ImportDialog { - backend: Rc, - source: Rc, - widget: gtk::Box, - navigator: RefCell>>, -} - -impl ImportDialog { - /// Create a new import dialog. - pub fn new(backend: Rc, source: Rc) -> Rc { - // Create UI - - let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/import_dialog.ui"); - - get_widget!(builder, gtk::Box, widget); - get_widget!(builder, gtk::Button, back_button); - get_widget!(builder, gtk::Button, add_button); - - let this = Rc::new(Self { - backend, - source, - widget, - navigator: RefCell::new(None), - }); - - // Connect signals and callbacks - - back_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } - })); - - add_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let editor = TrackSetEditor::new(this.backend.clone(), this.source.clone()); - navigator.push(editor); - } - })); - - this - } -} - -impl NavigatorScreen for ImportDialog { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - - fn get_widget(&self) -> gtk::Widget { - self.widget.clone().upcast() - } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } -} diff --git a/musicus/src/dialogs/import_disc.rs b/musicus/src/dialogs/import_disc.rs deleted file mode 100644 index 38ff99d..0000000 --- a/musicus/src/dialogs/import_disc.rs +++ /dev/null @@ -1,211 +0,0 @@ -use crate::backend::Backend; -use crate::ripper::Ripper; -use crate::widgets::{List, Navigator, NavigatorScreen}; -use anyhow::Result; -use glib::clone; -use gtk::prelude::*; -use gtk_macros::get_widget; -use std::cell::RefCell; -use std::rc::Rc; - -/// The current status of a ripped track. -#[derive(Debug, Clone)] -enum RipStatus { - None, - Ripping, - Ready, - Error, -} - -/// Representation of a track on the ripped disc. -#[derive(Debug, Clone)] -struct RipTrack { - pub status: RipStatus, - pub index: u32, - pub title: String, - pub subtitle: String, -} - -/// A dialog for importing tracks from a CD. -pub struct ImportDiscDialog { - backend: Rc, - widget: gtk::Box, - stack: gtk::Stack, - info_bar: gtk::InfoBar, - list: Rc>, - ripper: Ripper, - tracks: RefCell>, - navigator: RefCell>>, -} - -impl ImportDiscDialog { - /// Create a new import disc dialog. - pub fn new(backend: Rc) -> Rc { - // Create UI - - let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/import_disc_dialog.ui"); - - get_widget!(builder, gtk::Box, widget); - get_widget!(builder, gtk::Button, back_button); - get_widget!(builder, gtk::Stack, stack); - get_widget!(builder, gtk::InfoBar, info_bar); - get_widget!(builder, gtk::Button, import_button); - get_widget!(builder, gtk::Frame, frame); - - let list = List::::new("No tracks found."); - frame.add(&list.widget); - - let mut tmp_dir = glib::get_tmp_dir().unwrap(); - let dir_name = format!("musicus-{}", rand::random::()); - tmp_dir.push(dir_name); - - std::fs::create_dir(&tmp_dir).unwrap(); - - let ripper = Ripper::new(tmp_dir.to_str().unwrap()); - - let this = Rc::new(Self { - backend, - widget, - stack, - info_bar, - list, - ripper, - tracks: RefCell::new(Vec::new()), - navigator: RefCell::new(None), - }); - - // Connect signals and callbacks - - back_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } - })); - - import_button.connect_clicked(clone!(@strong this => move |_| { - this.stack.set_visible_child_name("loading"); - - let context = glib::MainContext::default(); - let clone = this.clone(); - context.spawn_local(async move { - match clone.ripper.load_disc().await { - Ok(disc) => { - let mut tracks = Vec::::new(); - for track in disc.first_track..=disc.last_track { - tracks.push(RipTrack { - status: RipStatus::None, - index: track, - title: "Track".to_string(), - subtitle: "Unknown".to_string(), - }); - } - - clone.tracks.replace(tracks.clone()); - clone.list.show_items(tracks); - clone.stack.set_visible_child_name("content"); - - clone.rip().await.unwrap(); - } - Err(_) => { - clone.info_bar.set_revealed(true); - clone.stack.set_visible_child_name("start"); - } - } - }); - })); - - this.list.set_make_widget(|track| { - let title = gtk::Label::new(Some(&format!("{}. {}", track.index, track.title))); - title.set_ellipsize(pango::EllipsizeMode::End); - title.set_halign(gtk::Align::Start); - - let subtitle = gtk::Label::new(Some(&track.subtitle)); - subtitle.set_ellipsize(pango::EllipsizeMode::End); - subtitle.set_opacity(0.5); - subtitle.set_halign(gtk::Align::Start); - - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - vbox.add(&title); - vbox.add(&subtitle); - vbox.set_hexpand(true); - - use RipStatus::*; - - let status: gtk::Widget = match track.status { - None => { - let placeholder = gtk::Label::new(Option::None); - placeholder.set_property_width_request(16); - placeholder.upcast() - } - Ripping => { - let spinner = gtk::Spinner::new(); - spinner.start(); - spinner.upcast() - } - Ready => gtk::Image::from_icon_name( - Some("object-select-symbolic"), - gtk::IconSize::Button, - ) - .upcast(), - Error => { - gtk::Image::from_icon_name(Some("dialog-error-symbolic"), gtk::IconSize::Dialog) - .upcast() - } - }; - - let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 6); - hbox.set_border_width(6); - hbox.add(&vbox); - hbox.add(&status); - - hbox.upcast() - }); - - this - } - - /// Rip the disc in the background. - async fn rip(&self) -> Result<()> { - let mut current_track = 0; - - while current_track < self.tracks.borrow().len() { - { - let mut tracks = self.tracks.borrow_mut(); - let mut track = &mut tracks[current_track]; - track.status = RipStatus::Ripping; - self.list.show_items(tracks.clone()); - } - - self.ripper - .rip_track(self.tracks.borrow()[current_track].index) - .await - .unwrap(); - - { - let mut tracks = self.tracks.borrow_mut(); - let mut track = &mut tracks[current_track]; - track.status = RipStatus::Ready; - self.list.show_items(tracks.clone()); - } - - current_track += 1; - } - - Ok(()) - } -} - -impl NavigatorScreen for ImportDiscDialog { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - - fn get_widget(&self) -> gtk::Widget { - self.widget.clone().upcast() - } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } -} diff --git a/musicus/src/dialogs/import_folder.rs b/musicus/src/dialogs/import_folder.rs deleted file mode 100644 index ca91219..0000000 --- a/musicus/src/dialogs/import_folder.rs +++ /dev/null @@ -1,88 +0,0 @@ -use super::ImportDialog; -use crate::backend::Backend; -use crate::editors::TrackSource; -use crate::widgets::{Navigator, NavigatorScreen}; -use glib::clone; -use gtk::prelude::*; -use gtk_macros::get_widget; -use std::cell::RefCell; -use std::rc::Rc; -use std::path::Path; - -/// The initial screen for importing a folder. -pub struct ImportFolderDialog { - backend: Rc, - widget: gtk::Box, - navigator: RefCell>>, -} - -impl ImportFolderDialog { - /// Create a new import folderdialog. - pub fn new(backend: Rc) -> Rc { - // Create UI - - let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/import_folder_dialog.ui"); - - get_widget!(builder, gtk::Box, widget); - get_widget!(builder, gtk::Button, back_button); - get_widget!(builder, gtk::Button, import_button); - - let this = Rc::new(Self { - backend, - widget, - navigator: RefCell::new(None), - }); - - // Connect signals and callbacks - - back_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } - })); - - import_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let chooser = gtk::FileChooserNative::new( - Some("Select folder"), - Some(&navigator.window), - gtk::FileChooserAction::SelectFolder, - None, - None, - ); - - chooser.connect_response(clone!(@strong this => move |chooser, response| { - if response == gtk::ResponseType::Accept { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let path = chooser.get_filename().unwrap(); - let source = TrackSource::folder(&path).unwrap(); - let dialog = ImportDialog::new(this.backend.clone(), Rc::new(source)); - navigator.push(dialog); - } - } - })); - - chooser.run(); - } - })); - - this - } -} - -impl NavigatorScreen for ImportFolderDialog { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - - fn get_widget(&self) -> gtk::Widget { - self.widget.clone().upcast() - } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } -} diff --git a/musicus/src/dialogs/mod.rs b/musicus/src/dialogs/mod.rs index 6f0a8ae..b244bda 100644 --- a/musicus/src/dialogs/mod.rs +++ b/musicus/src/dialogs/mod.rs @@ -1,12 +1,3 @@ -pub mod import; -pub use import::*; - -pub mod import_folder; -pub use import_folder::*; - -pub mod import_disc; -pub use import_disc::*; - pub mod about; pub use about::*; diff --git a/musicus/src/editors/mod.rs b/musicus/src/editors/mod.rs index 133c0a5..8974e76 100644 --- a/musicus/src/editors/mod.rs +++ b/musicus/src/editors/mod.rs @@ -10,12 +10,6 @@ pub use person::*; pub mod recording; pub use recording::*; -pub mod track_set; -pub use track_set::*; - -pub mod track_source; -pub use track_source::*; - pub mod work; pub use work::*; diff --git a/musicus/src/import/disc_source.rs b/musicus/src/import/disc_source.rs new file mode 100644 index 0000000..74ec8eb --- /dev/null +++ b/musicus/src/import/disc_source.rs @@ -0,0 +1,171 @@ +use anyhow::{anyhow, bail, Result}; +use discid::DiscId; +use futures_channel::oneshot; +use gstreamer::prelude::*; +use gstreamer::{Element, ElementFactory, Pipeline}; +use std::cell::RefCell; +use std::path::{Path, PathBuf}; +use std::thread; + +/// Representation of an audio CD being imported as a medium. +#[derive(Clone, Debug)] +pub struct DiscSource { + /// The MusicBrainz DiscID of the CD. + pub discid: String, + + /// The tracks on this disc. + pub tracks: Vec, +} + +/// Representation of a single track on an audio CD. +#[derive(Clone, Debug)] +pub struct TrackSource { + /// The track number. This is different from the index in the disc + /// source's tracks list, because it is not defined from which number the + /// the track numbers start. + pub number: u32, + + /// The path to the temporary file to which the track will be ripped. The + /// file will not exist until the track is actually ripped. + pub path: PathBuf, +} + +impl DiscSource { + /// Try to create a new disc source by asynchronously reading the + /// information from the default disc drive. + pub async fn load() -> Result { + let (sender, receiver) = oneshot::channel(); + + thread::spawn(|| { + let disc = Self::load_priv(); + sender.send(disc).unwrap(); + }); + + let disc = receiver.await??; + + Ok(disc) + } + + /// Rip the whole disc asynchronously. After this method has finished + /// successfully, the audio files will be available in the specified + /// location for each track source. + pub async fn rip(&self) -> Result<()> { + for track in &self.tracks { + let (sender, receiver) = oneshot::channel(); + + let number = track.number; + let path = track.path.clone(); + + thread::spawn(move || { + let result = Self::rip_track(&path, number); + sender.send(result).unwrap(); + }); + + receiver.await??; + } + + Ok(()) + } + + /// Load the disc from the default disc drive. + fn load_priv() -> Result { + let discid = DiscId::read(None)?; + let id = discid.id(); + + let mut tracks = Vec::new(); + + let first_track = discid.first_track_num() as u32; + let last_track = discid.last_track_num() as u32; + + let tmp_dir = Self::create_tmp_dir()?; + + for number in first_track..=last_track { + let file_name = format!("track_{:02}.flac", number); + + let mut path = tmp_dir.clone(); + path.push(file_name); + + let track = TrackSource { + number, + path, + }; + + tracks.push(track); + } + + let disc = DiscSource { + discid: id, + tracks, + }; + + Ok(disc) + } + + /// Create a new temporary directory and return its path. + // TODO: Move to a more appropriate place. + fn create_tmp_dir() -> Result { + let mut tmp_dir = glib::get_tmp_dir() + .ok_or_else(|| { + anyhow!("Failed to get temporary directory using glib::get_tmp_dir()!") + })?; + + let dir_name = format!("musicus-{}", rand::random::()); + tmp_dir.push(dir_name); + + std::fs::create_dir(&tmp_dir)?; + + Ok(tmp_dir) + } + + /// Rip one track. + fn rip_track(path: &Path, number: u32) -> Result<()> { + let pipeline = Self::build_pipeline(path, number)?; + + let bus = pipeline + .get_bus() + .ok_or_else(|| anyhow!("Failed to get bus from pipeline!"))?; + + pipeline.set_state(gstreamer::State::Playing)?; + + for msg in bus.iter_timed(gstreamer::CLOCK_TIME_NONE) { + use gstreamer::MessageView::*; + + match msg.view() { + Eos(..) => break, + Error(err) => { + pipeline.set_state(gstreamer::State::Null)?; + bail!("GStreamer error: {:?}!", err); + } + _ => (), + } + } + + pipeline.set_state(gstreamer::State::Null)?; + + Ok(()) + } + + /// Build the GStreamer pipeline to rip a track. + fn build_pipeline(path: &Path, number: u32) -> Result { + let cdparanoiasrc = ElementFactory::make("cdparanoiasrc", None)?; + cdparanoiasrc.set_property("track", &number)?; + + let queue = ElementFactory::make("queue", None)?; + let audioconvert = ElementFactory::make("audioconvert", None)?; + let flacenc = ElementFactory::make("flacenc", None)?; + + let path_str = path.to_str().ok_or_else(|| { + anyhow!("Failed to convert path '{:?}' to string!", path) + })?; + + let filesink = gstreamer::ElementFactory::make("filesink", None)?; + filesink.set_property("location", &path_str.to_owned())?; + + let pipeline = gstreamer::Pipeline::new(None); + pipeline.add_many(&[&cdparanoiasrc, &queue, &audioconvert, &flacenc, &filesink])?; + + Element::link_many(&[&cdparanoiasrc, &queue, &audioconvert, &flacenc, &filesink])?; + + Ok(pipeline) + } +} diff --git a/musicus/src/import/medium_editor.rs b/musicus/src/import/medium_editor.rs new file mode 100644 index 0000000..e69de29 diff --git a/musicus/src/import/mod.rs b/musicus/src/import/mod.rs new file mode 100644 index 0000000..248a7b7 --- /dev/null +++ b/musicus/src/import/mod.rs @@ -0,0 +1,5 @@ +mod disc_source; +mod medium_editor; +mod source_selector; + +pub use source_selector::SourceSelector; diff --git a/musicus/src/import/source_selector.rs b/musicus/src/import/source_selector.rs new file mode 100644 index 0000000..0743096 --- /dev/null +++ b/musicus/src/import/source_selector.rs @@ -0,0 +1,85 @@ +use super::disc_source::DiscSource; +use crate::backend::Backend; +use crate::widgets::{Navigator, NavigatorScreen}; +use anyhow::Result; +use glib::clone; +use gtk::prelude::*; +use gtk_macros::get_widget; +use std::cell::RefCell; +use std::rc::Rc; + +/// A dialog for starting to import music. +pub struct SourceSelector { + backend: Rc, + widget: gtk::Box, + stack: gtk::Stack, + info_bar: gtk::InfoBar, + navigator: RefCell>>, +} + +impl SourceSelector { + /// Create a new source selector. + pub fn new(backend: Rc) -> Rc { + // Create UI + + let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/source_selector.ui"); + + get_widget!(builder, gtk::Box, widget); + get_widget!(builder, gtk::Button, back_button); + get_widget!(builder, gtk::Stack, stack); + get_widget!(builder, gtk::InfoBar, info_bar); + get_widget!(builder, gtk::Button, import_button); + + let this = Rc::new(Self { + backend, + widget, + stack, + info_bar, + navigator: RefCell::new(None), + }); + + // Connect signals and callbacks + + back_button.connect_clicked(clone!(@strong this => move |_| { + let navigator = this.navigator.borrow().clone(); + if let Some(navigator) = navigator { + navigator.pop(); + } + })); + + import_button.connect_clicked(clone!(@strong this => move |_| { + this.stack.set_visible_child_name("loading"); + + let context = glib::MainContext::default(); + let clone = this.clone(); + context.spawn_local(async move { + match DiscSource::load().await { + Ok(disc) => { + println!("{:?}", disc); + clone.stack.set_visible_child_name("start"); + } + Err(_) => { + clone.info_bar.set_revealed(true); + clone.stack.set_visible_child_name("start"); + } + } + }); + })); + + this + } +} + +impl NavigatorScreen for SourceSelector { + fn attach_navigator(&self, navigator: Rc) { + self.navigator.replace(Some(navigator)); + } + + fn get_widget(&self) -> gtk::Widget { + self.widget.clone().upcast() + } + + fn detach_navigator(&self) { + self.navigator.replace(None); + } +} diff --git a/musicus/src/main.rs b/musicus/src/main.rs index 53a9fcb..49be0f5 100644 --- a/musicus/src/main.rs +++ b/musicus/src/main.rs @@ -12,11 +12,11 @@ use std::cell::RefCell; use std::rc::Rc; mod backend; -mod ripper; mod config; mod database; mod dialogs; mod editors; +mod import; mod player; mod screens; mod selectors; diff --git a/musicus/src/meson.build b/musicus/src/meson.build index 363f0ee..ec71ba2 100644 --- a/musicus/src/meson.build +++ b/musicus/src/meson.build @@ -53,8 +53,6 @@ sources = files( 'database/thread.rs', 'database/works.rs', 'dialogs/about.rs', - 'dialogs/import_disc.rs', - 'dialogs/import_folder.rs', 'dialogs/login_dialog.rs', 'dialogs/mod.rs', 'dialogs/preferences.rs', @@ -95,7 +93,6 @@ sources = files( 'player.rs', 'resources.rs', 'resources.rs.in', - 'ripper.rs', 'window.rs', ) diff --git a/musicus/src/ripper.rs b/musicus/src/ripper.rs deleted file mode 100644 index bbdbde5..0000000 --- a/musicus/src/ripper.rs +++ /dev/null @@ -1,123 +0,0 @@ -use anyhow::{anyhow, bail, Result}; -use discid::DiscId; -use futures_channel::oneshot; -use gstreamer::prelude::*; -use gstreamer::{Element, ElementFactory, Pipeline}; -use std::cell::RefCell; -use std::thread; - -/// A disc that can be ripped. -#[derive(Debug, Clone)] -pub struct RipDisc { - pub discid: String, - pub first_track: u32, - pub last_track: u32, -} - -/// An interface for ripping an audio compact disc. -pub struct Ripper { - path: String, - disc: RefCell>, -} - -impl Ripper { - /// Create a new ripper that stores its tracks within the specified folder. - pub fn new(path: &str) -> Self { - Self { - path: path.to_string(), - disc: RefCell::new(None), - } - } - - /// Load the disc and return its metadata. - pub async fn load_disc(&self) -> Result { - let (sender, receiver) = oneshot::channel(); - - thread::spawn(|| { - let disc = Self::load_disc_priv(); - sender.send(disc).unwrap(); - }); - - let disc = receiver.await??; - self.disc.replace(Some(disc.clone())); - - Ok(disc) - } - - /// Rip one track. - pub async fn rip_track(&self, track: u32) -> Result<()> { - let (sender, receiver) = oneshot::channel(); - - let path = self.path.clone(); - thread::spawn(move || { - let result = Self::rip_track_priv(&path, track); - sender.send(result).unwrap(); - }); - - receiver.await? - } - - /// Load the disc and return its metadata. - fn load_disc_priv() -> Result { - let discid = DiscId::read(None)?; - let id = discid.id(); - let first_track = discid.first_track_num() as u32; - let last_track = discid.last_track_num() as u32; - - let disc = RipDisc { - discid: id, - first_track, - last_track, - }; - - Ok(disc) - } - - /// Rip one track. - fn rip_track_priv(path: &str, track: u32) -> Result<()> { - let pipeline = Self::build_pipeline(path, track)?; - - let bus = pipeline - .get_bus() - .ok_or(anyhow!("Failed to get bus from pipeline!"))?; - - pipeline.set_state(gstreamer::State::Playing)?; - - for msg in bus.iter_timed(gstreamer::CLOCK_TIME_NONE) { - use gstreamer::MessageView::*; - - match msg.view() { - Eos(..) => break, - Error(err) => { - pipeline.set_state(gstreamer::State::Null)?; - bail!("GStreamer error: {:?}!", err); - } - _ => (), - } - } - - pipeline.set_state(gstreamer::State::Null)?; - - Ok(()) - } - - /// Build the GStreamer pipeline to rip a track. - fn build_pipeline(path: &str, track: u32) -> Result { - let cdparanoiasrc = ElementFactory::make("cdparanoiasrc", None)?; - cdparanoiasrc.set_property("track", &track)?; - - let queue = ElementFactory::make("queue", None)?; - let audioconvert = ElementFactory::make("audioconvert", None)?; - let flacenc = ElementFactory::make("flacenc", None)?; - - let filesink = gstreamer::ElementFactory::make("filesink", None)?; - filesink.set_property("location", &format!("{}/track_{:02}.flac", path, track))?; - - let pipeline = gstreamer::Pipeline::new(None); - pipeline.add_many(&[&cdparanoiasrc, &queue, &audioconvert, &flacenc, &filesink])?; - - Element::link_many(&[&cdparanoiasrc, &queue, &audioconvert, &flacenc, &filesink])?; - - Ok(pipeline) - } -} diff --git a/musicus/src/window.rs b/musicus/src/window.rs index 2d3224c..429a870 100644 --- a/musicus/src/window.rs +++ b/musicus/src/window.rs @@ -1,5 +1,6 @@ use crate::backend::*; use crate::dialogs::*; +use crate::import::SourceSelector; use crate::screens::*; use crate::widgets::*; use futures::prelude::*; @@ -93,7 +94,7 @@ impl Window { // let window = NavigatorWindow::new(editor); // window.show(); - let dialog = ImportFolderDialog::new(result.backend.clone()); + let dialog = SourceSelector::new(result.backend.clone()); let window = NavigatorWindow::new(dialog); window.show(); })); @@ -110,15 +111,15 @@ impl Window { result.stack.set_visible_child_name("content"); })); - action!( - result.window, - "import-disc", - clone!(@strong result => move |_, _| { - let dialog = ImportDiscDialog::new(result.backend.clone()); - let window = NavigatorWindow::new(dialog); - window.show(); - }) - ); + // action!( + // result.window, + // "import-disc", + // clone!(@strong result => move |_, _| { + // let dialog = ImportDiscDialog::new(result.backend.clone()); + // let window = NavigatorWindow::new(dialog); + // window.show(); + // }) + // ); action!( result.window,