mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 19:57:25 +01:00
Initial ripping from new source selector
This commit is contained in:
parent
4aa858602d
commit
18600c310f
17 changed files with 277 additions and 688 deletions
|
|
@ -4,12 +4,10 @@
|
||||||
<file preprocess="xml-stripblanks">ui/ensemble_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/ensemble_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/ensemble_screen.ui</file>
|
<file preprocess="xml-stripblanks">ui/ensemble_screen.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/ensemble_selector.ui</file>
|
<file preprocess="xml-stripblanks">ui/ensemble_selector.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/import_dialog.ui</file>
|
|
||||||
<file preprocess="xml-stripblanks">ui/import_disc_dialog.ui</file>
|
|
||||||
<file preprocess="xml-stripblanks">ui/import_folder_dialog.ui</file>
|
|
||||||
<file preprocess="xml-stripblanks">ui/instrument_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/instrument_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/instrument_selector.ui</file>
|
<file preprocess="xml-stripblanks">ui/instrument_selector.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/login_dialog.ui</file>
|
<file preprocess="xml-stripblanks">ui/login_dialog.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/medium_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/performance_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/performance_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/person_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/person_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/person_list.ui</file>
|
<file preprocess="xml-stripblanks">ui/person_list.ui</file>
|
||||||
|
|
@ -25,6 +23,7 @@
|
||||||
<file preprocess="xml-stripblanks">ui/recording_selector_screen.ui</file>
|
<file preprocess="xml-stripblanks">ui/recording_selector_screen.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/selector.ui</file>
|
<file preprocess="xml-stripblanks">ui/selector.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/server_dialog.ui</file>
|
<file preprocess="xml-stripblanks">ui/server_dialog.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/source_selector.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/track_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/track_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/track_selector.ui</file>
|
<file preprocess="xml-stripblanks">ui/track_selector.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/track_set_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/track_set_editor.ui</file>
|
||||||
|
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<interface>
|
|
||||||
<requires lib="gtk+" version="3.24"/>
|
|
||||||
<requires lib="libhandy" version="1.0"/>
|
|
||||||
<object class="GtkBox" id="widget">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<child>
|
|
||||||
<object class="HdyHeaderBar">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="title" translatable="yes">Import folder</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="back_button">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkImage">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="icon-name">go-previous-symbolic</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="halign">center</property>
|
|
||||||
<property name="valign">center</property>
|
|
||||||
<property name="vexpand">True</property>
|
|
||||||
<property name="border-width">18</property>
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<property name="spacing">18</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkImage">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="opacity">0.5</property>
|
|
||||||
<property name="pixel-size">80</property>
|
|
||||||
<property name="icon-name">folder-symbolic</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="opacity">0.5</property>
|
|
||||||
<property name="label" translatable="yes">Import from a folder</property>
|
|
||||||
<attributes>
|
|
||||||
<attribute name="size" value="16384"/>
|
|
||||||
</attributes>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="opacity">0.5</property>
|
|
||||||
<property name="label" translatable="yes">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.</property>
|
|
||||||
<property name="justify">center</property>
|
|
||||||
<property name="wrap">True</property>
|
|
||||||
<property name="max-width-chars">40</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="import_button">
|
|
||||||
<property name="label" translatable="yes">Select</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="halign">center</property>
|
|
||||||
<style>
|
|
||||||
<class name="suggested-action"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</interface>
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- Generated with glade 3.38.1 -->
|
|
||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk+" version="3.24"/>
|
<requires lib="gtk+" version="3.24"/>
|
||||||
<requires lib="libhandy" version="1.0"/>
|
<requires lib="libhandy" version="1.0"/>
|
||||||
|
|
@ -11,7 +10,7 @@
|
||||||
<object class="HdyHeaderBar">
|
<object class="HdyHeaderBar">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="title" translatable="yes">Import CD</property>
|
<property name="title" translatable="yes">Import music</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="back_button">
|
<object class="GtkButton" id="back_button">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
|
@ -27,11 +26,6 @@
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkStack" id="stack">
|
<object class="GtkStack" id="stack">
|
||||||
|
|
@ -49,21 +43,6 @@
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="message-type">error</property>
|
<property name="message-type">error</property>
|
||||||
<property name="revealed">False</property>
|
<property name="revealed">False</property>
|
||||||
<child internal-child="action_area">
|
|
||||||
<object class="GtkButtonBox">
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="spacing">6</property>
|
|
||||||
<property name="layout-style">end</property>
|
|
||||||
<child>
|
|
||||||
<placeholder/>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">False</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child internal-child="content_area">
|
<child internal-child="content_area">
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
|
|
@ -75,33 +54,16 @@
|
||||||
<property name="label" translatable="yes">Failed to load the CD. Make sure you have inserted it into your drive.</property>
|
<property name="label" translatable="yes">Failed to load the CD. Make sure you have inserted it into your drive.</property>
|
||||||
<property name="wrap">True</property>
|
<property name="wrap">True</property>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">False</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<placeholder/>
|
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
<property name="halign">center</property>
|
<property name="halign">center</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
<property name="border-width">18</property>
|
<property name="border-width">18</property>
|
||||||
|
|
@ -193,55 +155,7 @@
|
||||||
<property name="position">1</property>
|
<property name="position">1</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
|
||||||
<object class="GtkScrolledWindow">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkViewport">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="shadow-type">none</property>
|
|
||||||
<child>
|
|
||||||
<object class="HdyClamp">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="maximum-size">500</property>
|
|
||||||
<property name="tightening-threshold">300</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkFrame" id="frame">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="margin-start">6</property>
|
|
||||||
<property name="margin-end">6</property>
|
|
||||||
<property name="margin-top">12</property>
|
|
||||||
<property name="margin-bottom">6</property>
|
|
||||||
<property name="label-xalign">0</property>
|
|
||||||
<property name="shadow-type">in</property>
|
|
||||||
<child>
|
|
||||||
<placeholder/>
|
|
||||||
</child>
|
|
||||||
<child type="label_item">
|
|
||||||
<placeholder/>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="name">content</property>
|
|
||||||
<property name="position">2</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
|
||||||
<property name="expand">True</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">1</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
@ -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<Backend>,
|
|
||||||
source: Rc<TrackSource>,
|
|
||||||
widget: gtk::Box,
|
|
||||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImportDialog {
|
|
||||||
/// Create a new import dialog.
|
|
||||||
pub fn new(backend: Rc<Backend>, source: Rc<TrackSource>) -> Rc<Self> {
|
|
||||||
// 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<Navigator>) {
|
|
||||||
self.navigator.replace(Some(navigator));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_widget(&self) -> gtk::Widget {
|
|
||||||
self.widget.clone().upcast()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn detach_navigator(&self) {
|
|
||||||
self.navigator.replace(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<Backend>,
|
|
||||||
widget: gtk::Box,
|
|
||||||
stack: gtk::Stack,
|
|
||||||
info_bar: gtk::InfoBar,
|
|
||||||
list: Rc<List<RipTrack>>,
|
|
||||||
ripper: Ripper,
|
|
||||||
tracks: RefCell<Vec<RipTrack>>,
|
|
||||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImportDiscDialog {
|
|
||||||
/// Create a new import disc dialog.
|
|
||||||
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
|
|
||||||
// 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::<RipTrack>::new("No tracks found.");
|
|
||||||
frame.add(&list.widget);
|
|
||||||
|
|
||||||
let mut tmp_dir = glib::get_tmp_dir().unwrap();
|
|
||||||
let dir_name = format!("musicus-{}", rand::random::<u64>());
|
|
||||||
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::<RipTrack>::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<Navigator>) {
|
|
||||||
self.navigator.replace(Some(navigator));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_widget(&self) -> gtk::Widget {
|
|
||||||
self.widget.clone().upcast()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn detach_navigator(&self) {
|
|
||||||
self.navigator.replace(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<Backend>,
|
|
||||||
widget: gtk::Box,
|
|
||||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImportFolderDialog {
|
|
||||||
/// Create a new import folderdialog.
|
|
||||||
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
|
|
||||||
// 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<Navigator>) {
|
|
||||||
self.navigator.replace(Some(navigator));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_widget(&self) -> gtk::Widget {
|
|
||||||
self.widget.clone().upcast()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn detach_navigator(&self) {
|
|
||||||
self.navigator.replace(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 mod about;
|
||||||
pub use about::*;
|
pub use about::*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,6 @@ pub use person::*;
|
||||||
pub mod recording;
|
pub mod recording;
|
||||||
pub use recording::*;
|
pub use recording::*;
|
||||||
|
|
||||||
pub mod track_set;
|
|
||||||
pub use track_set::*;
|
|
||||||
|
|
||||||
pub mod track_source;
|
|
||||||
pub use track_source::*;
|
|
||||||
|
|
||||||
pub mod work;
|
pub mod work;
|
||||||
pub use work::*;
|
pub use work::*;
|
||||||
|
|
||||||
|
|
|
||||||
171
musicus/src/import/disc_source.rs
Normal file
171
musicus/src/import/disc_source.rs
Normal file
|
|
@ -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<TrackSource>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<Self> {
|
||||||
|
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<Self> {
|
||||||
|
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<PathBuf> {
|
||||||
|
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::<u64>());
|
||||||
|
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<Pipeline> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
0
musicus/src/import/medium_editor.rs
Normal file
0
musicus/src/import/medium_editor.rs
Normal file
5
musicus/src/import/mod.rs
Normal file
5
musicus/src/import/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
mod disc_source;
|
||||||
|
mod medium_editor;
|
||||||
|
mod source_selector;
|
||||||
|
|
||||||
|
pub use source_selector::SourceSelector;
|
||||||
85
musicus/src/import/source_selector.rs
Normal file
85
musicus/src/import/source_selector.rs
Normal file
|
|
@ -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<Backend>,
|
||||||
|
widget: gtk::Box,
|
||||||
|
stack: gtk::Stack,
|
||||||
|
info_bar: gtk::InfoBar,
|
||||||
|
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceSelector {
|
||||||
|
/// Create a new source selector.
|
||||||
|
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
|
||||||
|
// 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<Navigator>) {
|
||||||
|
self.navigator.replace(Some(navigator));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_widget(&self) -> gtk::Widget {
|
||||||
|
self.widget.clone().upcast()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detach_navigator(&self) {
|
||||||
|
self.navigator.replace(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,11 +12,11 @@ use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
mod backend;
|
mod backend;
|
||||||
mod ripper;
|
|
||||||
mod config;
|
mod config;
|
||||||
mod database;
|
mod database;
|
||||||
mod dialogs;
|
mod dialogs;
|
||||||
mod editors;
|
mod editors;
|
||||||
|
mod import;
|
||||||
mod player;
|
mod player;
|
||||||
mod screens;
|
mod screens;
|
||||||
mod selectors;
|
mod selectors;
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,6 @@ sources = files(
|
||||||
'database/thread.rs',
|
'database/thread.rs',
|
||||||
'database/works.rs',
|
'database/works.rs',
|
||||||
'dialogs/about.rs',
|
'dialogs/about.rs',
|
||||||
'dialogs/import_disc.rs',
|
|
||||||
'dialogs/import_folder.rs',
|
|
||||||
'dialogs/login_dialog.rs',
|
'dialogs/login_dialog.rs',
|
||||||
'dialogs/mod.rs',
|
'dialogs/mod.rs',
|
||||||
'dialogs/preferences.rs',
|
'dialogs/preferences.rs',
|
||||||
|
|
@ -95,7 +93,6 @@ sources = files(
|
||||||
'player.rs',
|
'player.rs',
|
||||||
'resources.rs',
|
'resources.rs',
|
||||||
'resources.rs.in',
|
'resources.rs.in',
|
||||||
'ripper.rs',
|
|
||||||
'window.rs',
|
'window.rs',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<Option<RipDisc>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<RipDisc> {
|
|
||||||
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<RipDisc> {
|
|
||||||
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<Pipeline> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::backend::*;
|
use crate::backend::*;
|
||||||
use crate::dialogs::*;
|
use crate::dialogs::*;
|
||||||
|
use crate::import::SourceSelector;
|
||||||
use crate::screens::*;
|
use crate::screens::*;
|
||||||
use crate::widgets::*;
|
use crate::widgets::*;
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
|
|
@ -93,7 +94,7 @@ impl Window {
|
||||||
// let window = NavigatorWindow::new(editor);
|
// let window = NavigatorWindow::new(editor);
|
||||||
// window.show();
|
// window.show();
|
||||||
|
|
||||||
let dialog = ImportFolderDialog::new(result.backend.clone());
|
let dialog = SourceSelector::new(result.backend.clone());
|
||||||
let window = NavigatorWindow::new(dialog);
|
let window = NavigatorWindow::new(dialog);
|
||||||
window.show();
|
window.show();
|
||||||
}));
|
}));
|
||||||
|
|
@ -110,15 +111,15 @@ impl Window {
|
||||||
result.stack.set_visible_child_name("content");
|
result.stack.set_visible_child_name("content");
|
||||||
}));
|
}));
|
||||||
|
|
||||||
action!(
|
// action!(
|
||||||
result.window,
|
// result.window,
|
||||||
"import-disc",
|
// "import-disc",
|
||||||
clone!(@strong result => move |_, _| {
|
// clone!(@strong result => move |_, _| {
|
||||||
let dialog = ImportDiscDialog::new(result.backend.clone());
|
// let dialog = ImportDiscDialog::new(result.backend.clone());
|
||||||
let window = NavigatorWindow::new(dialog);
|
// let window = NavigatorWindow::new(dialog);
|
||||||
window.show();
|
// window.show();
|
||||||
})
|
// })
|
||||||
);
|
// );
|
||||||
|
|
||||||
action!(
|
action!(
|
||||||
result.window,
|
result.window,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue