unlistened feeds
This commit is contained in:
@@ -80,10 +80,12 @@ fn main() -> anyhow::Result<()> {
|
|||||||
},
|
},
|
||||||
Command::Playlist { podcast } => {
|
Command::Playlist { podcast } => {
|
||||||
if let Some(alias) = podcast {
|
if let Some(alias) = podcast {
|
||||||
playlist::generate_podcast_m3u(alias.as_str(), root)?;
|
playlist::generate_podcast_m3u(alias.as_str(), root, false)?;
|
||||||
|
playlist::generate_podcast_m3u(alias.as_str(), root, true)?;
|
||||||
} else {
|
} else {
|
||||||
for (alias, _) in &config.podcasts {
|
for (alias, _) in &config.podcasts {
|
||||||
playlist::generate_podcast_m3u(alias.as_str(), root)?;
|
playlist::generate_podcast_m3u(alias.as_str(), root, true)?;
|
||||||
|
playlist::generate_podcast_m3u(alias.as_str(), root, false)?;
|
||||||
}
|
}
|
||||||
playlist::generate_master_m3u(&config, root)?;
|
playlist::generate_master_m3u(&config, root)?;
|
||||||
}
|
}
|
||||||
|
169
src/playlist.rs
169
src/playlist.rs
@@ -1,4 +1,9 @@
|
|||||||
use std::{fs, path, collections::BTreeMap};
|
use std::{
|
||||||
|
fs,
|
||||||
|
iter,
|
||||||
|
path,
|
||||||
|
collections::BTreeMap,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
input,
|
input,
|
||||||
@@ -10,11 +15,86 @@ use anyhow::Context;
|
|||||||
|
|
||||||
use sanitise_file_name::sanitise;
|
use sanitise_file_name::sanitise;
|
||||||
|
|
||||||
|
struct Playlist<'a> {
|
||||||
|
root: &'a path::Path,
|
||||||
|
files: BTreeMap<chrono::NaiveDateTime, Vec<m3u::Entry>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Playlist<'a> {
|
||||||
|
fn new(root: &'a path::Path) -> Self {
|
||||||
|
Self {
|
||||||
|
root,
|
||||||
|
files: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_as(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
reverse: bool,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let playlists_folder = self.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(sanitise(name));
|
||||||
|
path.set_extension("m3u");
|
||||||
|
|
||||||
|
let mut file = fs::File::create(path)?;
|
||||||
|
|
||||||
|
let mut writer = m3u::Writer::new(&mut file);
|
||||||
|
let entries =
|
||||||
|
self
|
||||||
|
.files
|
||||||
|
.values()
|
||||||
|
.map(|entries| entries.iter())
|
||||||
|
.flatten();
|
||||||
|
|
||||||
|
if reverse {
|
||||||
|
for entry in entries.rev() {
|
||||||
|
writer.write_entry(entry)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for entry in entries {
|
||||||
|
writer.write_entry(entry)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(
|
||||||
|
&mut self,
|
||||||
|
published: chrono::NaiveDateTime,
|
||||||
|
absolute_path: &path::Path,
|
||||||
|
) {
|
||||||
|
let entry = m3u::path_entry({
|
||||||
|
let relative = absolute_path.strip_prefix(
|
||||||
|
&self.root.join(folders::PODCASTS_DIR)
|
||||||
|
).unwrap();
|
||||||
|
path::Path::new("/Podcasts").join(relative)
|
||||||
|
});
|
||||||
|
|
||||||
|
match self.files.get_mut(&published) {
|
||||||
|
Some(existing) => {
|
||||||
|
existing.push(entry)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
self.files.insert(
|
||||||
|
published,
|
||||||
|
vec![entry]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn generate_master_m3u(
|
pub(crate) fn generate_master_m3u(
|
||||||
config: &input::Config,
|
config: &input::Config,
|
||||||
root: &path::Path,
|
root: &path::Path,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let mut all_episodes = BTreeMap::<chrono::NaiveDateTime, Vec<_>>::new();
|
let mut playlist = Playlist::new(root);
|
||||||
|
|
||||||
for (podcast, _) in &config.podcasts {
|
for (podcast, _) in &config.podcasts {
|
||||||
let output = folders::podcast_folder(root, podcast.as_str());
|
let output = folders::podcast_folder(root, podcast.as_str());
|
||||||
@@ -28,87 +108,54 @@ pub(crate) fn generate_master_m3u(
|
|||||||
let path = output.join(
|
let path = output.join(
|
||||||
files.get(episode.id()).unwrap()
|
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) {
|
playlist.insert(
|
||||||
Some(existing) => {
|
|
||||||
existing.push(entry)
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
all_episodes.insert(
|
|
||||||
published,
|
published,
|
||||||
vec![entry]
|
&path
|
||||||
);
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let playlists_folder = root.join("Playlists");
|
playlist.write_as("[Podcast Master Feed]", true)
|
||||||
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(
|
pub(crate) fn generate_podcast_m3u(
|
||||||
alias: &str,
|
alias: &str,
|
||||||
root: &path::Path,
|
root: &path::Path,
|
||||||
|
unlistened_only: bool,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
let mut playlist = Playlist::new(root);
|
||||||
|
|
||||||
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");
|
||||||
|
|
||||||
let spec = Specification::read_from(&spec_file)?;
|
let spec = Specification::read_from(&spec_file)?;
|
||||||
|
|
||||||
let mut playlist = Vec::new();
|
let episodes =
|
||||||
|
spec
|
||||||
|
.feed_iter()
|
||||||
|
.map(|(published, eps)|
|
||||||
|
iter::repeat(published).zip(eps.iter())
|
||||||
|
)
|
||||||
|
.flatten()
|
||||||
|
.filter(|episode|
|
||||||
|
!unlistened_only || (unlistened_only && !episode.1.listened)
|
||||||
|
);
|
||||||
|
|
||||||
for episode in spec.feed_iter().map(|(_, eps)| eps.iter()).flatten() {
|
for (published, episode) in episodes {
|
||||||
let path = output.join(
|
let path = output.join(
|
||||||
spec.path_from_id(episode.id()).unwrap()
|
spec.path_from_id(episode.id()).unwrap()
|
||||||
);
|
);
|
||||||
playlist.push(m3u::path_entry({
|
playlist.insert(
|
||||||
let relative = path.strip_prefix(
|
published.clone(),
|
||||||
output.parent().unwrap()
|
&path,
|
||||||
).unwrap();
|
);
|
||||||
path::Path::new("/Podcasts").join(relative)
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the playlist file
|
if unlistened_only {
|
||||||
{
|
playlist.write_as(&format!("{} (unlistened)", alias), false)
|
||||||
let playlists_folder = root.join("Playlists");
|
} else {
|
||||||
if !playlists_folder.exists() {
|
playlist.write_as(alias, false)
|
||||||
fs::create_dir(&playlists_folder)
|
|
||||||
.context(format!("failed to create output directory for playlists"))?;
|
|
||||||
}
|
}
|
||||||
let mut path = playlists_folder.join(sanitise(&alias));
|
|
||||||
path.set_extension("m3u");
|
|
||||||
|
|
||||||
let mut file = fs::File::create(path)?;
|
|
||||||
|
|
||||||
let mut writer = m3u::Writer::new(&mut file);
|
|
||||||
for entry in &playlist {
|
|
||||||
writer.write_entry(entry)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user