aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/src/lib.rs5
-rw-r--r--remuxer/src/import/mod.rs25
-rw-r--r--remuxer/src/lib.rs235
-rw-r--r--server/src/library.rs44
-rw-r--r--server/src/routes/ui/style/directorypage.css1
-rw-r--r--server/src/routes/ui/style/player.css2
-rw-r--r--tools/src/bin/import.rs24
7 files changed, 210 insertions, 126 deletions
diff --git a/common/src/lib.rs b/common/src/lib.rs
index a4380c6..90cbd3e 100644
--- a/common/src/lib.rs
+++ b/common/src/lib.rs
@@ -12,13 +12,14 @@ pub struct DirectoryInfo {
pub struct ItemInfo {
pub title: String,
pub duration: f64, // in seconds
- pub path: PathBuf,
pub banner: Option<PathBuf>,
- pub tracks: BTreeMap<u64, SourceTrack>,
+ pub tracks: BTreeMap<usize, SourceTrack>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SourceTrack {
+ pub path: PathBuf,
+ pub track_number: u64,
pub kind: SourceTrackKind,
pub name: String,
pub codec: String,
diff --git a/remuxer/src/import/mod.rs b/remuxer/src/import/mod.rs
index 5147a05..505fbe4 100644
--- a/remuxer/src/import/mod.rs
+++ b/remuxer/src/import/mod.rs
@@ -1,3 +1,5 @@
+use std::path::PathBuf;
+
use anyhow::{anyhow, bail, Result};
use jellycommon::{ItemInfo, SourceTrack, SourceTrackKind};
use jellymatroska::{
@@ -8,7 +10,7 @@ use jellymatroska::{
};
use log::{debug, error, info, trace, warn};
-pub fn import_read(input: &mut EbmlReader, iteminfo: &mut ItemInfo) -> Result<()> {
+pub fn import_read(path: &PathBuf, input: &mut EbmlReader, iteminfo: &mut ItemInfo) -> Result<()> {
// TODO dont traverse the entire file, if the tracks are listed at the end
while let Some(item) = input.next() {
let item = match item {
@@ -35,7 +37,7 @@ pub fn import_read(input: &mut EbmlReader, iteminfo: &mut ItemInfo) -> Result<()
MatroskaTag::Segment(_) => {
info!("segment start");
let mut children = Unflatten::new_with_end(input, item);
- import_read_segment(&mut children, iteminfo)?;
+ import_read_segment(path, &mut children, iteminfo)?;
info!("segment end");
}
_ => debug!("(r) tag ignored: {item:?}"),
@@ -45,7 +47,11 @@ pub fn import_read(input: &mut EbmlReader, iteminfo: &mut ItemInfo) -> Result<()
Ok(())
}
-fn import_read_segment(children: &mut Unflatten, iteminfo: &mut ItemInfo) -> Result<()> {
+fn import_read_segment(
+ path: &PathBuf,
+ children: &mut Unflatten,
+ iteminfo: &mut ItemInfo,
+) -> Result<()> {
let (mut timestamp_scale, mut duration) = (None, None);
while let Some(Ok(Unflat { children, item })) = children.next() {
match item {
@@ -99,7 +105,11 @@ fn import_read_segment(children: &mut Unflatten, iteminfo: &mut ItemInfo) -> Res
match item {
MatroskaTag::CuePoint(_) => {
let mut children = children.unwrap();
- while let Some(Ok(Unflat { children: _, item: _ })) = children.next() {
+ while let Some(Ok(Unflat {
+ children: _,
+ item: _,
+ })) = children.next()
+ {
// error!("{item:?}")
}
}
@@ -170,7 +180,7 @@ fn import_read_segment(children: &mut Unflatten, iteminfo: &mut ItemInfo) -> Res
_ => (),
}
}
- // let itrack_index = iteminfo.tracks.len();
+ let itrack_index = iteminfo.tracks.len();
let mtrack_index = index.unwrap();
let kind = match kind.ok_or(anyhow!("track type required"))? {
1 => SourceTrackKind::Video {
@@ -186,10 +196,11 @@ fn import_read_segment(children: &mut Unflatten, iteminfo: &mut ItemInfo) -> Res
17 => SourceTrackKind::Subtitles,
_ => bail!("invalid track type"),
};
- // track_mapping.insert(mtrack_index, itrack_index);
iteminfo.tracks.insert(
- mtrack_index,
+ itrack_index,
SourceTrack {
+ track_number: mtrack_index,
+ path: path.to_owned(),
default_duration,
codec_private,
name: name.unwrap_or_else(|| "unnamed".to_string()),
diff --git a/remuxer/src/lib.rs b/remuxer/src/lib.rs
index 5536a12..fabde10 100644
--- a/remuxer/src/lib.rs
+++ b/remuxer/src/lib.rs
@@ -1,7 +1,7 @@
pub mod format;
pub mod import;
-use anyhow::Result;
+use anyhow::{anyhow, Result};
use jellycommon::{ItemInfo, SourceTrack, SourceTrackKind};
use jellymatroska::{
block::Block,
@@ -11,7 +11,7 @@ use jellymatroska::{
Master, MatroskaTag,
};
use log::{debug, info, trace, warn};
-use std::{collections::BTreeMap, fs::File, io::Write, path::PathBuf, sync::Arc};
+use std::{collections::VecDeque, fs::File, io::Write, path::PathBuf, sync::Arc};
pub struct RemuxerContext {}
@@ -26,24 +26,44 @@ impl RemuxerContext {
_offset: usize,
path_base: PathBuf,
iteminfo: ItemInfo,
- selection: Vec<u64>,
+ selection: Vec<usize>,
webm: bool,
) -> anyhow::Result<()> {
- let source_path = path_base.join(&iteminfo.path);
- info!("remuxing {source_path:?} to have tracks {selection:?}");
-
- let input = File::open(source_path)?;
- let mut input = EbmlReader::new(input);
+ info!("remuxing {:?} to have tracks {selection:?}", iteminfo.title);
let mut output = EbmlWriter::new(writer, 0);
- // maps original to remuxed
- let mapping = BTreeMap::from_iter(
- selection
- .iter()
- .enumerate()
- .map(|(i, e)| (*e, i as u64 + 1)),
- );
- info!("track mapping: {mapping:?}");
+ struct ReaderC {
+ info: SourceTrack,
+ reader: EbmlReader,
+ mapped: u64,
+ }
+
+ let mut inputs = selection
+ .iter()
+ .enumerate()
+ .map(|(index, sel)| {
+ let info = iteminfo
+ .tracks
+ .get(sel)
+ .ok_or(anyhow!("track not available"))?
+ .to_owned();
+ let source_path = path_base.join(&info.path);
+ let mapped = index as u64 + 1;
+ info!(
+ "\t- {sel} {source_path:?} ({} => {mapped})",
+ info.track_number
+ );
+ info!("\t {}", info);
+ let file = File::open(source_path)?;
+ let reader = EbmlReader::new(file);
+ Ok(ReaderC {
+ reader,
+ info,
+ mapped,
+ })
+ })
+ .into_iter()
+ .collect::<anyhow::Result<Vec<_>>>()?;
output.write_tag(&MatroskaTag::Ebml(Master::Collected(vec![
MatroskaTag::EbmlVersion(1),
@@ -71,101 +91,152 @@ impl RemuxerContext {
output.write_tag(&MatroskaTag::Tags(Master::Collected(vec![])))?;
// output.write_tag(&MatroskaTag::Cues(Master::Collected(vec![])))?;
- let tracks_header = MatroskaTag::Tracks(Master::Collected(
- mapping
- .iter()
- .map(|(norig, nrem)| track_to_ebml(*nrem, &iteminfo.tracks.get(norig).unwrap()))
- .collect(),
- ));
- info!("track header: {tracks_header:?}");
- output.write_tag(&tracks_header)?;
+ let tracks_header = inputs
+ .iter()
+ .map(|rc| track_to_ebml(rc.mapped, &rc.info))
+ .collect();
+ output.write_tag(&MatroskaTag::Tracks(Master::Collected(tracks_header)))?;
+
+ struct ReaderD<'a> {
+ info: SourceTrack,
+ peek: Option<AbsoluteBlock>,
+ stream: SegmentExtractIter<'a>,
+ mapped: u64,
+ }
- while let Some(item) = input.next() {
- let item = match item {
- Ok(item) => item,
- Err(e) => {
- warn!("{e}");
+ // read until start of the segment
+ let mut ks = vec![];
+ for i in &mut inputs {
+ loop {
+ let t = i.reader.next().ok_or(anyhow!("early eof"))??;
+ if let MatroskaTag::Segment(Master::Start) = t {
break;
}
- };
- match item {
- MatroskaTag::Ebml(_) => {
- Unflatten::new_with_end(&mut input, item);
- }
- MatroskaTag::Segment(_) => {
- info!("segment start");
- let mut children = Unflatten::new_with_end(&mut input, item);
- filter_segment(&mut children, &mut output, &iteminfo, &mapping)?;
- info!("segment end");
- }
- _ => debug!("(r) tag ignored: {item:?}"),
}
+ let mut stream = SegmentExtractIter {
+ segment: Unflatten::new_with_end(
+ &mut i.reader,
+ MatroskaTag::Segment(Master::Start),
+ ),
+ extract: i.info.track_number,
+ emission_queue: VecDeque::new(),
+ };
+ ks.push(ReaderD {
+ mapped: i.mapped,
+ peek: Some(stream.next()?),
+ stream,
+ info: i.info.clone(),
+ });
}
- output.write_tag(&MatroskaTag::Segment(Master::End))?;
+ loop {
+ let next_index = ks
+ .iter()
+ .enumerate()
+ .fold((0, u64::MAX), |(bi, bpts), (i, r)| {
+ if let Some(peek) = &r.peek {
+ let pts = peek.pts();
+ if pts < bpts {
+ return (i, pts);
+ }
+ }
+ (bi, bpts)
+ })
+ .0;
+
+ let kn = &mut ks[next_index];
+ let mut next = kn.peek.replace(kn.stream.next()?).unwrap(); //.ok_or(anyhow!("eof?"));
- Ok(())
+ let pts = next.pts();
+ next.block.track = kn.mapped;
+ next.block.timestamp_off = 0;
+ let buf = next.block.dump();
+ output.write_tag(&MatroskaTag::Cluster(Master::Collected(vec![
+ MatroskaTag::Timestamp(pts),
+ MatroskaTag::SimpleBlock(buf),
+ ])))?;
+ }
+
+ // output.write_tag(&MatroskaTag::Segment(Master::End))?;
+ // Ok(())
}
}
-fn filter_segment(
- children: &mut Unflatten,
- writer: &mut EbmlWriter,
- _iteminfo: &ItemInfo,
- mapping: &BTreeMap<u64, u64>,
-) -> Result<()> {
- while let Some(Ok(Unflat { children, item })) = children.next() {
+struct AbsoluteBlock {
+ pts_base: u64,
+ block: Block,
+}
+
+struct SegmentExtractIter<'a> {
+ segment: Unflatten<'a>,
+ extract: u64,
+ emission_queue: VecDeque<AbsoluteBlock>,
+}
+
+impl AbsoluteBlock {
+ pub fn pts(&self) -> u64 {
+ self.block.timestamp_off as u64 + self.pts_base
+ }
+}
+
+impl SegmentExtractIter<'_> {
+ pub fn next(&mut self) -> Result<AbsoluteBlock> {
+ loop {
+ if let Some(b) = self.emission_queue.pop_front() {
+ break Ok(b);
+ }
+ self.read()?;
+ }
+ }
+
+ pub fn read(&mut self) -> Result<()> {
+ let Unflat { children, item } = self.segment.next().ok_or(anyhow!("eof"))??;
+ let mut pts_base = 0;
match item {
MatroskaTag::SeekHead(_) => {}
MatroskaTag::Info(_) => {}
MatroskaTag::Cluster(_) => {
- let mut cluster = vec![];
let mut children = children.unwrap();
while let Some(Ok(Unflat { children, item })) = children.next() {
match item {
MatroskaTag::Crc32(_) => (),
MatroskaTag::Timestamp(ts) => {
info!("ts={ts}");
- cluster.push(MatroskaTag::Timestamp(ts));
+ pts_base = ts;
}
MatroskaTag::BlockGroup(_) => {
debug!("group");
let mut children = children.unwrap();
- let mut group = vec![];
+
+ let mut duration = None;
+ let mut block = None;
+
while let Some(Ok(Unflat { children: _, item })) = children.next() {
match item {
- MatroskaTag::Block(buf) => {
- let mut block = Block::parse(&buf)?;
- if let Some(outnum) = mapping.get(&block.track) {
- block.track = *outnum;
- trace!(
- "block: track={} tso={}",
- block.track,
- block.timestamp_off
- );
- group.push(MatroskaTag::Block(block.dump()));
- }
- }
- MatroskaTag::BlockDuration(v) => {
- group.push(MatroskaTag::BlockDuration(v));
- }
+ MatroskaTag::Block(buf) => block = Some(buf),
+ MatroskaTag::BlockDuration(v) => duration = Some(v),
_ => debug!("ignored {item:?}"),
}
}
- cluster.push(MatroskaTag::BlockGroup(Master::Collected(group)));
+ // TODO duration
+ let block = Block::parse(&block.unwrap())?;
+ if block.track == self.extract {
+ trace!("block: track={} tso={}", block.track, block.timestamp_off);
+ self.emission_queue
+ .push_back(AbsoluteBlock { pts_base, block });
+ }
}
MatroskaTag::SimpleBlock(buf) => {
- let mut block = Block::parse(&buf)?;
- if let Some(outnum) = mapping.get(&block.track) {
- block.track = *outnum;
+ let block = Block::parse(&buf)?;
+ if block.track == self.extract {
trace!("block: track={} tso={}", block.track, block.timestamp_off);
- cluster.push(MatroskaTag::SimpleBlock(block.dump()));
+ self.emission_queue
+ .push_back(AbsoluteBlock { pts_base, block });
}
}
_ => warn!("(rsc) tag ignored: {item:?}"),
}
}
- writer.write_tag(&MatroskaTag::Cluster(Master::Collected(cluster)))?;
}
MatroskaTag::Tags(_) => {}
MatroskaTag::Cues(_) => {}
@@ -173,8 +244,8 @@ fn filter_segment(
MatroskaTag::Tracks(_) => {}
_ => debug!("(rs) tag ignored: {item:?}"),
}
+ Ok(())
}
- Ok(())
}
pub fn track_to_ebml(number: u64, track: &SourceTrack) -> MatroskaTag {
@@ -194,7 +265,6 @@ pub fn track_to_ebml(number: u64, track: &SourceTrack) -> MatroskaTag {
fps: _,
} => {
els.push(MatroskaTag::TrackType(1));
- els.push(MatroskaTag::DefaultDuration(41708333)); // TODO pls not hardcoded
els.push(MatroskaTag::Video(Master::Collected(vec![
MatroskaTag::PixelWidth(width),
MatroskaTag::PixelHeight(height),
@@ -221,16 +291,3 @@ pub fn track_to_ebml(number: u64, track: &SourceTrack) -> MatroskaTag {
}
MatroskaTag::TrackEntry(Master::Collected(els))
}
-
-// pub struct SendWriter(pub Sender<Vec<u8>>);
-
-// impl Write for SendWriter {
-// fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
-// self.0.blocking_send(buf.to_owned()).unwrap();
-// Ok(buf.len())
-// }
-
-// fn flush(&mut self) -> std::io::Result<()> {
-// Ok(()) // TODO should we actually do something here?
-// }
-// }
diff --git a/server/src/library.rs b/server/src/library.rs
index a48e746..ebafa9d 100644
--- a/server/src/library.rs
+++ b/server/src/library.rs
@@ -36,7 +36,9 @@ pub struct Item {
impl Library {
pub fn open(path: &PathBuf) -> anyhow::Result<Self> {
Ok(Self {
- root: Node::from_path(path.clone(), PathBuf::new(), true).context("indexing root")?,
+ root: Node::from_path(path.clone(), PathBuf::new(), true)
+ .context("indexing root")?
+ .ok_or(anyhow!("root need directory.json"))?,
})
}
pub fn nested_path(&self, path: &Path) -> anyhow::Result<Arc<Node>> {
@@ -86,9 +88,12 @@ impl Node {
path: PathBuf,
mut lib_path: PathBuf,
root: bool,
- ) -> anyhow::Result<Arc<Node>> {
+ ) -> anyhow::Result<Option<Arc<Node>>> {
if path.is_dir() {
let mpath = path.join("directory.json");
+ if !mpath.exists() {
+ return Ok(None);
+ }
let data: DirectoryInfo =
serde_json::from_reader(File::open(mpath).context("metadata missing")?)?;
@@ -109,20 +114,23 @@ impl Node {
None
}
})
- .map(|e| {
+ .filter_map(|e| {
Node::from_path(e.clone(), lib_path.clone(), false)
.context(format!("loading {e:?}"))
+ .transpose()
})
.into_iter()
.collect::<anyhow::Result<Vec<_>>>()?;
- Ok(Node::Directory(Arc::new(Directory {
- lib_path,
- children,
- data,
- identifier,
- }))
- .into())
+ Ok(Some(
+ Node::Directory(Arc::new(Directory {
+ lib_path,
+ children,
+ data,
+ identifier,
+ }))
+ .into(),
+ ))
} else if path.is_file() {
info!("loading {path:?}");
let datafile = File::open(path.clone()).context("cant load metadata")?;
@@ -134,13 +142,15 @@ impl Node {
.to_str()
.unwrap()
.to_string();
- Ok(Node::Item(Arc::new(Item {
- fs_path: path.clone(),
- lib_path: lib_path.join(identifier.clone()),
- info: data,
- identifier,
- }))
- .into())
+ Ok(Some(
+ Node::Item(Arc::new(Item {
+ fs_path: path.clone(),
+ lib_path: lib_path.join(identifier.clone()),
+ info: data,
+ identifier,
+ }))
+ .into(),
+ ))
} else {
bail!("did somebody really put a fifo or socket in the library?!")
}
diff --git a/server/src/routes/ui/style/directorypage.css b/server/src/routes/ui/style/directorypage.css
index 8c6d5d9..2773e3c 100644
--- a/server/src/routes/ui/style/directorypage.css
+++ b/server/src/routes/ui/style/directorypage.css
@@ -1,4 +1,3 @@
-
.page.dir {
padding: 1em;
padding-left: 3em;
diff --git a/server/src/routes/ui/style/player.css b/server/src/routes/ui/style/player.css
index b34653e..8d3488f 100644
--- a/server/src/routes/ui/style/player.css
+++ b/server/src/routes/ui/style/player.css
@@ -42,9 +42,11 @@ input[type="radio"] {
width: 1.2em;
height: 1.2em;
border-radius: 8px;
+ padding: 2px;
background-clip: content-box;
border: 2px solid var(--font);
background-color: transparent;
+ transition: background-color 0.3s;
}
input[type="radio"]:checked {
background-color: var(--accent-light);
diff --git a/tools/src/bin/import.rs b/tools/src/bin/import.rs
index 4af6ac7..d38925b 100644
--- a/tools/src/bin/import.rs
+++ b/tools/src/bin/import.rs
@@ -20,7 +20,7 @@ struct Args {
dry: bool,
#[clap(short = 'i', long)]
- input: PathBuf,
+ input: Option<PathBuf>,
}
fn main() -> anyhow::Result<()> {
@@ -37,20 +37,25 @@ fn main() -> anyhow::Result<()> {
warn!("using the default instead");
ItemInfo {
duration: 0.0,
- path: args.input.clone(),
- banner: args.banner,
- title: args
- .title
- .unwrap_or(args.item.to_str().unwrap().to_string()),
+ banner: None,
+ title: args.item.to_str().unwrap().to_string(),
tracks: Default::default(),
}
}
};
- let input = File::open(args.input.clone()).unwrap();
- let mut input = EbmlReader::new(input);
+ if let Some(title) = args.title {
+ iteminfo.title = title;
+ }
+ if let Some(banner) = args.banner {
+ iteminfo.banner = Some(banner);
+ }
- import_read(&mut input, &mut iteminfo)?;
+ if let Some(input_path) = args.input {
+ let input = File::open(input_path.clone()).unwrap();
+ let mut input = EbmlReader::new(input);
+ import_read(&input_path, &mut input, &mut iteminfo)?;
+ }
let k = serde_json::to_string_pretty(&iteminfo)?;
if args.dry {
@@ -59,6 +64,5 @@ fn main() -> anyhow::Result<()> {
let mut f = File::create(args.item)?;
f.write_all(k.as_bytes())?;
}
-
Ok(())
}