/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin */ use crate::{ plugins::{ImportPlugin, PluginContext, PluginInfo}, source_rank::ObjectImportSourceExt, }; use anyhow::Result; use jellycommon::{jellyobject::OBB, *}; use jellydb::RowNum; use jellyremuxer::{ codec_param_bit_depth, matroska::{Segment, TrackType}, }; use std::path::Path; pub struct MediaInfo; impl ImportPlugin for MediaInfo { fn info(&self) -> PluginInfo { PluginInfo { name: "media-info", tag: MSOURCE_MEDIA, handle_media: true, ..Default::default() } } fn media(&self, ct: &PluginContext, row: RowNum, path: &Path, seg: &Segment) -> Result<()> { let size = path.metadata()?.len(); ct.ic.db.transaction(&mut |txn| { let mut node = txn.get(row)?.unwrap(); if let Some(tracks) = &seg.tracks { node = node.extend_object( NO_TRACK, TR_SOURCE.0, &tracks .entries .iter() .map(|t| { let mut track = OBB::new(); track.push(TR_CODEC, &t.codec_id); track.push(TR_LANGUAGE, &t.language); if let Some(name) = &t.name { track.push(TR_NAME, name); } track.push( TR_KIND, match t.track_type { TrackType::Audio => TRKIND_AUDIO, TrackType::Video => TRKIND_VIDEO, TrackType::Subtitle => TRKIND_TEXT, _ => TRKIND_UNKNOWN, }, ); if let Some(v) = &t.video { track.push(TR_PIXEL_WIDTH, v.pixel_width as u32); track.push(TR_PIXEL_HEIGHT, v.pixel_height as u32); if let Some(fr) = v.frame_rate { track.push(TR_RATE, fr); } else if let Some(d) = t.default_duration { track.push(TR_RATE, 1e9 / d as f64) } if let Some(d) = v.colour.as_ref().map(|c| c.bits_per_channel) && d != 0 { track.push(TR_BIT_DEPTH, d as u32); } else if let Some(d) = codec_param_bit_depth(t) { track.push(TR_BIT_DEPTH, d as u32); } else if let Some(p) = v.colour.as_ref().map(|c| c.primaries) && p == 9 { track.push(TR_BIT_DEPTH, 10); // use of BT.2020 is most likely >=10bit } } if let Some(a) = &t.audio { track.push(TR_CHANNELS, a.channels as u32); track.push(TR_RATE, a.sampling_frequency); if let Some(d) = a.bit_depth { track.push(TR_BIT_DEPTH, d as u32); } } let source = OBB::new() .with(TRSOURCE_LOCAL_PATH, &path.to_string_lossy()) .with(TRSOURCE_LOCAL_TRACKNUM, t.track_number) .finish(); track.push(TR_SOURCE, &source); track.finish() }) .collect::>(), ); } if let Some(chapters) = &seg.chapters { node = node.extend_object( NO_CHAPTER, CH_NAME.0, &chapters .edition_entries .iter() .flat_map(|e| &e.chapter_atoms) .map(|cha| { let mut chapter = OBB::new(); chapter.push(CH_START, cha.time_start as f64 * 1e-9); if let Some(end) = cha.time_end { chapter.push(CH_END, end as f64 * 1e-9); } if let Some(display) = cha.displays.first() { chapter.push(CH_NAME, &display.string); } chapter.finish() }) .collect::>(), ); } let runtime = fix_invalid_runtime( seg.info.duration.unwrap_or_default() * seg.info.timestamp_scale as f64 * 1e-9, ); node = node.insert_s(ct.is, NO_DURATION, runtime); node = node.insert_s(ct.is, NO_STORAGE_SIZE, size); txn.update(row, node)?; Ok(()) }) } } fn fix_invalid_runtime(d: f64) -> f64 { match d { // Broken durations found experimentally 359999.999 | 359999.000 | 86399.999 | 86399.99900000001 => 0., x => x, } }