separate playlist generation
This commit is contained in:
@@ -53,6 +53,14 @@ impl<'a> Specification<'a> {
|
|||||||
pub(crate) fn path_from_id(&self, id: &str) -> Option<&path::Path> {
|
pub(crate) fn path_from_id(&self, id: &str) -> Option<&path::Path> {
|
||||||
self.files.get(id).map(|v| &**v)
|
self.files.get(id).map(|v| &**v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn feed(&self) -> &BTreeMap<chrono::NaiveDateTime, Vec<Episode<'a>>> {
|
||||||
|
&self.feed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn into_feed_and_files(self) -> (BTreeMap<chrono::NaiveDateTime, Vec<Episode<'a>>>, HashMap<Cow<'a, str>, Cow<'a, path::Path>>) {
|
||||||
|
(self.feed, self.files)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
10
src/input.rs
10
src/input.rs
@@ -42,9 +42,15 @@ pub(crate) enum Command {
|
|||||||
#[arg(long, short)]
|
#[arg(long, short)]
|
||||||
podcast: String,
|
podcast: String,
|
||||||
},
|
},
|
||||||
/// Tags files and generates playlists ready for use with an iPod.
|
/// Tags files for use with an iPod, such that they don't show up in albums and artists.
|
||||||
Tag {
|
Tag {
|
||||||
/// The podcast to tag and generate playlists for.
|
/// The podcast to tag.
|
||||||
|
#[arg(long, short)]
|
||||||
|
podcast: Option<String>,
|
||||||
|
},
|
||||||
|
/// Generates playlist for use with an iPod.
|
||||||
|
Playlist {
|
||||||
|
/// The podcast to generate the playlist for.
|
||||||
#[arg(long, short)]
|
#[arg(long, short)]
|
||||||
podcast: Option<String>,
|
podcast: Option<String>,
|
||||||
},
|
},
|
||||||
|
14
src/main.rs
14
src/main.rs
@@ -77,17 +77,25 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
spec.write_to(&spec_file)?;
|
spec.write_to(&spec_file)?;
|
||||||
},
|
},
|
||||||
|
Command::Playlist { podcast } => {
|
||||||
|
if let Some(alias) = podcast {
|
||||||
|
tagging::generate_podcast_m3u(alias.as_str(), root)?;
|
||||||
|
} else {
|
||||||
|
for (alias, _) in &config.podcasts {
|
||||||
|
tagging::generate_podcast_m3u(alias.as_str(), root)?;
|
||||||
|
}
|
||||||
|
tagging::generate_master_m3u(&config, root)?;
|
||||||
|
}
|
||||||
|
},
|
||||||
Command::Tag { podcast } => {
|
Command::Tag { podcast } => {
|
||||||
if let Some(alias) = podcast {
|
if let Some(alias) = podcast {
|
||||||
tagging::generate_m3u(alias.as_str(), root)?;
|
|
||||||
tagging::strip_tags(alias.as_str(), root)?;
|
tagging::strip_tags(alias.as_str(), root)?;
|
||||||
} else {
|
} else {
|
||||||
for (alias, _) in config.podcasts {
|
for (alias, _) in config.podcasts {
|
||||||
tagging::generate_m3u(alias.as_str(), root)?;
|
|
||||||
tagging::strip_tags(alias.as_str(), root)?;
|
tagging::strip_tags(alias.as_str(), root)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@@ -1,12 +1,71 @@
|
|||||||
use std::{fs, path};
|
use std::{fs, path, collections::BTreeMap};
|
||||||
|
|
||||||
use crate::{folders, download};
|
use crate::{input, folders, download};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
|
||||||
use sanitise_file_name::sanitise;
|
use sanitise_file_name::sanitise;
|
||||||
|
|
||||||
pub(crate) fn generate_m3u(
|
pub(crate) fn generate_master_m3u(
|
||||||
|
config: &input::Config,
|
||||||
|
root: &path::Path,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut all_episodes = BTreeMap::<chrono::NaiveDateTime, Vec<_>>::new();
|
||||||
|
|
||||||
|
for (podcast, _) in &config.podcasts {
|
||||||
|
let output = folders::podcast_folder(root, podcast.as_str());
|
||||||
|
let spec_file = output.join("spec.toml");
|
||||||
|
let spec = download::Specification::read_from(&spec_file)?;
|
||||||
|
|
||||||
|
let (feed, files) = spec.into_feed_and_files();
|
||||||
|
|
||||||
|
for (published, episodes) in feed {
|
||||||
|
for episode in episodes {
|
||||||
|
let path = output.join(
|
||||||
|
files.get(episode.id()).unwrap()
|
||||||
|
);
|
||||||
|
let entry = m3u::path_entry({
|
||||||
|
let relative = path.strip_prefix(
|
||||||
|
output.parent().unwrap()
|
||||||
|
).unwrap();
|
||||||
|
path::Path::new("/Podcasts").join(relative)
|
||||||
|
});
|
||||||
|
|
||||||
|
match all_episodes.get_mut(&published) {
|
||||||
|
Some(existing) => {
|
||||||
|
existing.push(entry)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
all_episodes.insert(
|
||||||
|
published,
|
||||||
|
vec![entry]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let playlists_folder = root.join("Playlists");
|
||||||
|
if !playlists_folder.exists() {
|
||||||
|
fs::create_dir(&playlists_folder)
|
||||||
|
.context(format!("failed to create output directory for playlists"))?;
|
||||||
|
}
|
||||||
|
let mut path = playlists_folder.join("_master");
|
||||||
|
path.set_extension("m3u");
|
||||||
|
|
||||||
|
let mut file = fs::File::create(path)?;
|
||||||
|
|
||||||
|
let mut writer = m3u::Writer::new(&mut file);
|
||||||
|
for entry in all_episodes.values().map(|entries| entries.iter()).flatten() {
|
||||||
|
writer.write_entry(entry)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub(crate) fn generate_podcast_m3u(
|
||||||
alias: &str,
|
alias: &str,
|
||||||
root: &path::Path,
|
root: &path::Path,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
@@ -54,6 +113,8 @@ pub(crate) fn strip_tags(
|
|||||||
alias: &str,
|
alias: &str,
|
||||||
root: &path::Path,
|
root: &path::Path,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
println!("[info] retagging podcast {}", alias);
|
||||||
|
|
||||||
let output = folders::podcast_folder(root, alias);
|
let output = folders::podcast_folder(root, alias);
|
||||||
let spec_file = output.join("spec.toml");
|
let spec_file = output.join("spec.toml");
|
||||||
|
|
||||||
@@ -63,13 +124,16 @@ pub(crate) fn strip_tags(
|
|||||||
let path = output.join(
|
let path = output.join(
|
||||||
spec.path_from_id(episode.id()).unwrap()
|
spec.path_from_id(episode.id()).unwrap()
|
||||||
);
|
);
|
||||||
let mut file = audiotags::Tag::new().read_from_path(
|
let file = audiotags::Tag::new().read_from_path(
|
||||||
&path
|
&path
|
||||||
)?;
|
);
|
||||||
|
|
||||||
|
let Ok(mut file) = file else {
|
||||||
|
println!("[warning] failed to read mp3 audio tags file. skipping.");
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
|
||||||
file.remove_title();
|
|
||||||
file.remove_artist();
|
file.remove_artist();
|
||||||
file.remove_year();
|
|
||||||
file.remove_album();
|
file.remove_album();
|
||||||
file.set_genre("Podcast");
|
file.set_genre("Podcast");
|
||||||
file.set_title(episode.title());
|
file.set_title(episode.title());
|
||||||
|
Reference in New Issue
Block a user