Initial commit: Shared video processing crate
- Event-driven file stability tracking - FFmpeg video conversion with hardware acceleration - NFO file generation for Kodi compatibility - Environment-based configuration - Disk space monitoring and shutdown handling - Comprehensive logging with tracing
This commit is contained in:
commit
a38143c28b
24
.env.example
Normal file
24
.env.example
Normal file
|
@ -0,0 +1,24 @@
|
|||
# FFmpeg Configuration
|
||||
FFMPEG_BINARY=ffmpeg
|
||||
VIDEO_CODEC=av1_qsv
|
||||
HW_ACCEL=qsv
|
||||
HW_DEVICE=qsv=hw
|
||||
FFMPEG_PRESET=4
|
||||
VIDEO_BITRATE=6M
|
||||
MAX_BITRATE=12M
|
||||
BUFFER_SIZE=24M
|
||||
AUDIO_CODEC=copy
|
||||
AUDIO_BITRATE=192k
|
||||
|
||||
# File Stability Settings
|
||||
STABILITY_CHECK_INTERVAL=2
|
||||
STABILITY_REQUIRED_CHECKS=15
|
||||
MAX_STABILITY_WAIT_HOURS=4
|
||||
|
||||
# Directory Settings
|
||||
CREATE_YEAR_MONTH_DIRS=true
|
||||
PRESERVE_ORIGINAL_FILES=true
|
||||
|
||||
# NFO Settings
|
||||
SHOW_TITLE=Videos
|
||||
CREATE_NFO_FILES=true
|
1265
Cargo.lock
generated
Normal file
1265
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "video_processing"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0", features = ["full", "fs", "process", "signal"] }
|
||||
anyhow = "1.0"
|
||||
chrono = "0.4"
|
||||
notify = "6.1"
|
||||
regex = "1.5"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
sysinfo = "0.30"
|
||||
async-trait = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.0"
|
31
README.md
Normal file
31
README.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Video Processing Crate
|
||||
|
||||
A shared Rust crate for video processing tasks including file watching, stability tracking, FFmpeg conversion, and NFO file generation.
|
||||
|
||||
## Features
|
||||
|
||||
- **Configurable FFmpeg conversion** with environment variable support
|
||||
- **Event-driven file stability tracking**
|
||||
- **File system watching** with the `notify` crate
|
||||
- **NFO file generation** for Kodi media center compatibility
|
||||
- **Hardware-accelerated encoding** support (Intel QSV)
|
||||
|
||||
## Environment Variables
|
||||
|
||||
See `.env.example` for all supported configuration options.
|
||||
|
||||
## Usage
|
||||
|
||||
```rust
|
||||
use video_processing::*;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let config = VideoProcessingConfig::from_env();
|
||||
let converter = VideoConverter::new(config.clone());
|
||||
let stability_tracker = StabilityTracker::new(config.clone());
|
||||
|
||||
// Your video processing logic here
|
||||
Ok(())
|
||||
}
|
||||
```
|
83
src/config.rs
Normal file
83
src/config.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VideoProcessingConfig {
|
||||
// FFmpeg settings
|
||||
pub ffmpeg_binary: String,
|
||||
pub video_codec: String,
|
||||
pub hw_accel: String,
|
||||
pub hw_device: String,
|
||||
pub preset: String,
|
||||
pub video_bitrate: String,
|
||||
pub max_bitrate: String,
|
||||
pub buffer_size: String,
|
||||
pub audio_codec: String,
|
||||
pub audio_bitrate: String,
|
||||
|
||||
// File stability settings
|
||||
pub stability_check_interval_secs: u64,
|
||||
pub stability_required_checks: u32,
|
||||
pub max_stability_wait_hours: u64,
|
||||
|
||||
// Directory settings
|
||||
pub create_year_month_dirs: bool,
|
||||
pub preserve_original_files: bool,
|
||||
|
||||
// NFO settings
|
||||
pub show_title: String,
|
||||
pub create_nfo_files: bool,
|
||||
}
|
||||
|
||||
impl VideoProcessingConfig {
|
||||
pub fn from_env() -> Self {
|
||||
Self {
|
||||
// FFmpeg settings with sensible defaults
|
||||
ffmpeg_binary: env::var("FFMPEG_BINARY").unwrap_or_else(|_| "ffmpeg".to_string()),
|
||||
video_codec: env::var("VIDEO_CODEC").unwrap_or_else(|_| "av1_qsv".to_string()),
|
||||
hw_accel: env::var("HW_ACCEL").unwrap_or_else(|_| "qsv".to_string()),
|
||||
hw_device: env::var("HW_DEVICE").unwrap_or_else(|_| "qsv=hw".to_string()),
|
||||
preset: env::var("FFMPEG_PRESET").unwrap_or_else(|_| "4".to_string()),
|
||||
video_bitrate: env::var("VIDEO_BITRATE").unwrap_or_else(|_| "6M".to_string()),
|
||||
max_bitrate: env::var("MAX_BITRATE").unwrap_or_else(|_| "12M".to_string()),
|
||||
buffer_size: env::var("BUFFER_SIZE").unwrap_or_else(|_| "24M".to_string()),
|
||||
audio_codec: env::var("AUDIO_CODEC").unwrap_or_else(|_| "copy".to_string()),
|
||||
audio_bitrate: env::var("AUDIO_BITRATE").unwrap_or_else(|_| "192k".to_string()),
|
||||
|
||||
// File stability settings
|
||||
stability_check_interval_secs: env::var("STABILITY_CHECK_INTERVAL")
|
||||
.unwrap_or_else(|_| "2".to_string())
|
||||
.parse()
|
||||
.unwrap_or(2),
|
||||
stability_required_checks: env::var("STABILITY_REQUIRED_CHECKS")
|
||||
.unwrap_or_else(|_| "15".to_string())
|
||||
.parse()
|
||||
.unwrap_or(15),
|
||||
max_stability_wait_hours: env::var("MAX_STABILITY_WAIT_HOURS")
|
||||
.unwrap_or_else(|_| "4".to_string())
|
||||
.parse()
|
||||
.unwrap_or(4),
|
||||
|
||||
// Directory settings
|
||||
create_year_month_dirs: env::var("CREATE_YEAR_MONTH_DIRS")
|
||||
.unwrap_or_else(|_| "true".to_string())
|
||||
.parse()
|
||||
.unwrap_or(true),
|
||||
preserve_original_files: env::var("PRESERVE_ORIGINAL_FILES")
|
||||
.unwrap_or_else(|_| "true".to_string())
|
||||
.parse()
|
||||
.unwrap_or(true),
|
||||
|
||||
// NFO settings
|
||||
show_title: env::var("SHOW_TITLE").unwrap_or_else(|_| "Videos".to_string()),
|
||||
create_nfo_files: env::var("CREATE_NFO_FILES")
|
||||
.unwrap_or_else(|_| "true".to_string())
|
||||
.parse()
|
||||
.unwrap_or(true),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self::from_env()
|
||||
}
|
||||
}
|
161
src/file_watcher.rs
Normal file
161
src/file_watcher.rs
Normal file
|
@ -0,0 +1,161 @@
|
|||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use anyhow::Result;
|
||||
use notify::{Watcher, RecursiveMode, Event, EventKind};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{info, warn, error, debug};
|
||||
|
||||
pub struct EventDrivenFileWatcher {
|
||||
watcher: Option<notify::RecommendedWatcher>,
|
||||
receiver: mpsc::Receiver<Event>,
|
||||
}
|
||||
|
||||
impl EventDrivenFileWatcher {
|
||||
pub fn new(watch_path: PathBuf) -> Result<Self> {
|
||||
let (tx, rx) = mpsc::channel(1000);
|
||||
|
||||
let mut watcher = notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
|
||||
let tx = tx.clone();
|
||||
match res {
|
||||
Ok(event) => {
|
||||
debug!("File system event: {:?}", event);
|
||||
if let Err(e) = tx.blocking_send(event) {
|
||||
error!("Failed to send file system event: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => error!("File watcher error: {}", e),
|
||||
}
|
||||
})?;
|
||||
|
||||
watcher.watch(&watch_path, RecursiveMode::NonRecursive)?;
|
||||
info!("Started watching directory: {}", watch_path.display());
|
||||
|
||||
Ok(Self {
|
||||
watcher: Some(watcher),
|
||||
receiver: rx,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn next_event(&mut self) -> Option<Event> {
|
||||
self.receiver.recv().await
|
||||
}
|
||||
|
||||
pub fn should_process_event(event: &Event) -> bool {
|
||||
matches!(
|
||||
event.kind,
|
||||
EventKind::Create(_) |
|
||||
EventKind::Modify(notify::event::ModifyKind::Name(notify::event::RenameMode::To))
|
||||
)
|
||||
}
|
||||
|
||||
pub fn extract_mp4_paths(event: &Event) -> Vec<PathBuf> {
|
||||
event.paths.iter()
|
||||
.filter(|path| {
|
||||
// Skip Syncthing temp files
|
||||
if path.to_string_lossy().contains(".syncthing.") {
|
||||
debug!("Skipping Syncthing temp file: {}", path.display());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only process MP4 files
|
||||
if path.extension().and_then(|ext| ext.to_str()) != Some("mp4") {
|
||||
debug!("Skipping non-MP4 file: {}", path.display());
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EventDrivenFileWatcher {
|
||||
fn drop(&mut self) {
|
||||
if let Some(watcher) = self.watcher.take() {
|
||||
drop(watcher);
|
||||
info!("File watcher stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait FileProcessor: Send + Sync {
|
||||
async fn process_file(&self, path: PathBuf) -> Result<()>;
|
||||
async fn should_skip_existing_file(&self, path: &PathBuf) -> bool {
|
||||
false // Default: process all existing files
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FileProcessingService<T: FileProcessor> {
|
||||
processor: Arc<T>,
|
||||
stability_tracker: Arc<crate::StabilityTracker>,
|
||||
}
|
||||
|
||||
impl<T: FileProcessor> FileProcessingService<T> {
|
||||
pub fn new(processor: Arc<T>, stability_tracker: Arc<crate::StabilityTracker>) -> Self {
|
||||
Self {
|
||||
processor,
|
||||
stability_tracker,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn process_existing_files(&self, watch_path: &PathBuf) -> Result<()> {
|
||||
info!("Processing existing files in: {}", watch_path.display());
|
||||
|
||||
let entries = std::fs::read_dir(watch_path)?;
|
||||
for entry in entries {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if path.to_string_lossy().contains(".syncthing.") {
|
||||
debug!("Skipping Syncthing temp file: {}", path.display());
|
||||
continue;
|
||||
}
|
||||
|
||||
if path.extension().and_then(|ext| ext.to_str()) == Some("mp4") {
|
||||
if self.processor.should_skip_existing_file(&path).await {
|
||||
info!("Skipping already processed file: {}", path.display());
|
||||
continue;
|
||||
}
|
||||
|
||||
info!("Processing existing file: {}", path.display());
|
||||
if let Err(e) = self.processor.process_file(path).await {
|
||||
error!("Failed to process existing file: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_file_event(&self, path: PathBuf) -> Result<()> {
|
||||
let canonical_path = match std::fs::canonicalize(&path) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
warn!("Failed to canonicalize path {}: {}", path.display(), e);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
info!("Starting stability tracking for: {}", canonical_path.display());
|
||||
|
||||
// Start tracking the file and wait for it to become stable
|
||||
loop {
|
||||
match self.stability_tracker.on_file_event(&canonical_path)? {
|
||||
true => {
|
||||
info!("File is stable, processing: {}", canonical_path.display());
|
||||
self.processor.process_file(canonical_path.clone()).await?;
|
||||
self.stability_tracker.remove_file_tracker(&canonical_path);
|
||||
break;
|
||||
}
|
||||
false => {
|
||||
// File not stable yet, wait and check again
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
13
src/lib.rs
Normal file
13
src/lib.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
pub mod config;
|
||||
pub mod file_watcher;
|
||||
pub mod stability_tracker;
|
||||
pub mod video_converter;
|
||||
pub mod nfo_generator;
|
||||
pub mod system_monitor;
|
||||
|
||||
pub use config::VideoProcessingConfig;
|
||||
pub use file_watcher::{EventDrivenFileWatcher, FileProcessor, FileProcessingService};
|
||||
pub use stability_tracker::{StabilityTracker, FileStabilityTracker};
|
||||
pub use video_converter::VideoConverter;
|
||||
pub use nfo_generator::NfoGenerator;
|
||||
pub use system_monitor::{SystemMonitor, ShutdownHandler};
|
55
src/nfo_generator.rs
Normal file
55
src/nfo_generator.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use std::path::PathBuf;
|
||||
use anyhow::Result;
|
||||
use chrono::NaiveDate;
|
||||
|
||||
use crate::config::VideoProcessingConfig;
|
||||
|
||||
pub struct NfoGenerator {
|
||||
config: VideoProcessingConfig,
|
||||
}
|
||||
|
||||
impl NfoGenerator {
|
||||
pub fn new(config: VideoProcessingConfig) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
|
||||
pub async fn create_nfo_file(
|
||||
&self,
|
||||
video_path: &PathBuf,
|
||||
title: &str,
|
||||
date: &NaiveDate,
|
||||
tag: Option<&str>,
|
||||
) -> Result<()> {
|
||||
if !self.config.create_nfo_files {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let nfo_path = video_path.with_extension("nfo");
|
||||
|
||||
let nfo_content = format!(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<episodedetails>
|
||||
<title>{}</title>
|
||||
<showtitle>{}</showtitle>
|
||||
<season>{}</season>
|
||||
<episode>{}</episode>
|
||||
<aired>{}</aired>
|
||||
<displayseason>{}</displayseason>
|
||||
<displayepisode>{}</displayepisode>
|
||||
<tag>{}</tag>
|
||||
</episodedetails>"#,
|
||||
title,
|
||||
self.config.show_title,
|
||||
date.format("%Y").to_string(),
|
||||
date.format("%m%d").to_string(),
|
||||
date.format("%Y-%m-%d"),
|
||||
date.format("%Y"),
|
||||
date.format("%m%d"),
|
||||
tag.unwrap_or("Video")
|
||||
);
|
||||
|
||||
tokio::fs::write(&nfo_path, nfo_content).await?;
|
||||
println!("Created NFO file: {}", nfo_path.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
91
src/stability_tracker.rs
Normal file
91
src/stability_tracker.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::time::{Duration, Instant};
|
||||
use anyhow::{Result, anyhow};
|
||||
|
||||
use crate::config::VideoProcessingConfig;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileStabilityTracker {
|
||||
pub size: u64,
|
||||
pub modified: std::time::SystemTime,
|
||||
pub stable_count: u32,
|
||||
pub last_check: Instant,
|
||||
}
|
||||
|
||||
pub struct StabilityTracker {
|
||||
config: VideoProcessingConfig,
|
||||
file_trackers: Arc<Mutex<HashMap<PathBuf, FileStabilityTracker>>>,
|
||||
}
|
||||
|
||||
impl StabilityTracker {
|
||||
pub fn new(config: VideoProcessingConfig) -> Self {
|
||||
Self {
|
||||
config,
|
||||
file_trackers: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_file_event(&self, path: &PathBuf) -> Result<bool> {
|
||||
let metadata = std::fs::metadata(path)?;
|
||||
let current_size = metadata.len();
|
||||
let current_modified = metadata.modified()?;
|
||||
let now = Instant::now();
|
||||
|
||||
let mut trackers = self.file_trackers.lock().unwrap();
|
||||
|
||||
let is_stable = match trackers.get_mut(path) {
|
||||
Some(tracker) => {
|
||||
// Only check stability if enough time has passed since last check
|
||||
let interval = Duration::from_secs(self.config.stability_check_interval_secs);
|
||||
if now.duration_since(tracker.last_check) >= interval {
|
||||
if current_size == tracker.size && current_modified == tracker.modified {
|
||||
tracker.stable_count += 1;
|
||||
tracker.last_check = now;
|
||||
|
||||
println!("File {} stable for {} checks (size: {} bytes)",
|
||||
path.display(), tracker.stable_count, current_size);
|
||||
|
||||
tracker.stable_count >= self.config.stability_required_checks
|
||||
} else {
|
||||
println!("File {} changed: size {} -> {}, modified {:?} -> {:?}",
|
||||
path.display(), tracker.size, current_size, tracker.modified, current_modified);
|
||||
tracker.size = current_size;
|
||||
tracker.modified = current_modified;
|
||||
tracker.stable_count = 0;
|
||||
tracker.last_check = now;
|
||||
false
|
||||
}
|
||||
} else {
|
||||
// Not enough time has passed, don't update stability count
|
||||
false
|
||||
}
|
||||
},
|
||||
None => {
|
||||
// First time seeing this file
|
||||
println!("Started tracking file: {} (size: {} bytes)", path.display(), current_size);
|
||||
trackers.insert(path.clone(), FileStabilityTracker {
|
||||
size: current_size,
|
||||
modified: current_modified,
|
||||
stable_count: 0,
|
||||
last_check: now,
|
||||
});
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
Ok(is_stable)
|
||||
}
|
||||
|
||||
pub fn remove_file_tracker(&self, path: &PathBuf) {
|
||||
let mut trackers = self.file_trackers.lock().unwrap();
|
||||
trackers.remove(path);
|
||||
println!("Removed tracker for file: {}", path.display());
|
||||
}
|
||||
|
||||
pub fn get_tracked_files(&self) -> Vec<PathBuf> {
|
||||
let trackers = self.file_trackers.lock().unwrap();
|
||||
trackers.keys().cloned().collect()
|
||||
}
|
||||
}
|
96
src/system_monitor.rs
Normal file
96
src/system_monitor.rs
Normal file
|
@ -0,0 +1,96 @@
|
|||
use std::path::Path;
|
||||
use anyhow::{Result, anyhow};
|
||||
use sysinfo::Disks;
|
||||
use tracing::{warn, error, info};
|
||||
use tokio::signal;
|
||||
|
||||
pub struct SystemMonitor {
|
||||
min_free_space_gb: u64,
|
||||
}
|
||||
|
||||
impl SystemMonitor {
|
||||
pub fn new(min_free_space_gb: u64) -> Self {
|
||||
Self { min_free_space_gb }
|
||||
}
|
||||
|
||||
pub fn check_disk_space(&self, path: &Path) -> Result<bool> {
|
||||
let disks = Disks::new_with_refreshed_list();
|
||||
|
||||
for disk in &disks {
|
||||
if path.starts_with(disk.mount_point()) {
|
||||
let available_bytes = disk.available_space();
|
||||
let available_gb = available_bytes / (1024 * 1024 * 1024);
|
||||
|
||||
info!("Disk space check for {}: {}GB available", path.display(), available_gb);
|
||||
|
||||
if available_gb < self.min_free_space_gb {
|
||||
warn!(
|
||||
"Low disk space: {}GB available, minimum required: {}GB",
|
||||
available_gb, self.min_free_space_gb
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
error!("Could not find disk for path: {}", path.display());
|
||||
Err(anyhow!("Could not determine disk space for path"))
|
||||
}
|
||||
|
||||
pub fn get_disk_usage(&self, path: &Path) -> Result<(u64, u64)> {
|
||||
let disks = Disks::new_with_refreshed_list();
|
||||
|
||||
for disk in &disks {
|
||||
if path.starts_with(disk.mount_point()) {
|
||||
return Ok((disk.available_space(), disk.total_space()));
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!("Could not find disk for path: {}", path.display()))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShutdownHandler {
|
||||
shutdown_tx: tokio::sync::broadcast::Sender<()>,
|
||||
}
|
||||
|
||||
impl ShutdownHandler {
|
||||
pub fn new() -> (Self, tokio::sync::broadcast::Receiver<()>) {
|
||||
let (shutdown_tx, shutdown_rx) = tokio::sync::broadcast::channel(1);
|
||||
|
||||
let handler = Self { shutdown_tx };
|
||||
(handler, shutdown_rx)
|
||||
}
|
||||
|
||||
pub async fn wait_for_shutdown_signal(&self) {
|
||||
let ctrl_c = async {
|
||||
signal::ctrl_c()
|
||||
.await
|
||||
.expect("failed to install Ctrl+C handler");
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
let terminate = async {
|
||||
signal::unix::signal(signal::unix::SignalKind::terminate())
|
||||
.expect("failed to install signal handler")
|
||||
.recv()
|
||||
.await;
|
||||
};
|
||||
|
||||
#[cfg(not(unix))]
|
||||
let terminate = std::future::pending::<()>();
|
||||
|
||||
tokio::select! {
|
||||
_ = ctrl_c => {
|
||||
info!("Received Ctrl+C, shutting down gracefully...");
|
||||
}
|
||||
_ = terminate => {
|
||||
info!("Received SIGTERM, shutting down gracefully...");
|
||||
}
|
||||
}
|
||||
|
||||
// Send shutdown signal to all components
|
||||
let _ = self.shutdown_tx.send(());
|
||||
}
|
||||
}
|
67
src/video_converter.rs
Normal file
67
src/video_converter.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use std::path::PathBuf;
|
||||
use anyhow::{Result, anyhow};
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::config::VideoProcessingConfig;
|
||||
|
||||
pub struct VideoConverter {
|
||||
config: VideoProcessingConfig,
|
||||
}
|
||||
|
||||
impl VideoConverter {
|
||||
pub fn new(config: VideoProcessingConfig) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
|
||||
pub async fn convert_video(&self, input_path: &PathBuf, output_path: &PathBuf) -> Result<()> {
|
||||
println!("Converting video from {} to {}", input_path.display(), output_path.display());
|
||||
|
||||
let mut cmd = Command::new(&self.config.ffmpeg_binary);
|
||||
|
||||
// Add hardware acceleration if specified
|
||||
if !self.config.hw_device.is_empty() && self.config.hw_accel == "qsv" {
|
||||
cmd.arg("-init_hw_device").arg(&self.config.hw_device)
|
||||
.arg("-filter_hw_device").arg("hw")
|
||||
.arg("-hwaccel").arg(&self.config.hw_accel)
|
||||
.arg("-hwaccel_output_format").arg(&self.config.hw_accel);
|
||||
}
|
||||
|
||||
cmd.arg("-i").arg(input_path)
|
||||
.arg("-c:v").arg(&self.config.video_codec)
|
||||
.arg("-preset").arg(&self.config.preset)
|
||||
.arg("-b:v").arg(&self.config.video_bitrate)
|
||||
.arg("-maxrate").arg(&self.config.max_bitrate)
|
||||
.arg("-bufsize").arg(&self.config.buffer_size);
|
||||
|
||||
// Audio codec handling
|
||||
if self.config.audio_codec == "copy" {
|
||||
cmd.arg("-c:a").arg("copy");
|
||||
} else {
|
||||
cmd.arg("-c:a").arg(&self.config.audio_codec)
|
||||
.arg("-b:a").arg(&self.config.audio_bitrate);
|
||||
}
|
||||
|
||||
// Overwrite policy - never overwrite by default for safety
|
||||
cmd.arg("-n")
|
||||
.arg(output_path);
|
||||
|
||||
let status = cmd.status().await?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(anyhow!("FFmpeg conversion failed with exit code: {:?}", status.code()));
|
||||
}
|
||||
|
||||
// Verify output file exists and has reasonable size
|
||||
if !output_path.exists() {
|
||||
return Err(anyhow!("Output file was not created: {}", output_path.display()));
|
||||
}
|
||||
|
||||
let output_size = tokio::fs::metadata(output_path).await?.len();
|
||||
if output_size == 0 {
|
||||
return Err(anyhow!("Output file is empty: {}", output_path.display()));
|
||||
}
|
||||
|
||||
println!("Successfully converted video: {} ({} bytes)", output_path.display(), output_size);
|
||||
Ok(())
|
||||
}
|
||||
}
|
1
target/.rustc_info.json
Normal file
1
target/.rustc_info.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"rustc_fingerprint":9853977359276756675,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.88.0 (6b00bc388 2025-06-23)\nbinary: rustc\ncommit-hash: 6b00bc3880198600130e1cf62b8f8a93494488cc\ncommit-date: 2025-06-23\nhost: aarch64-apple-darwin\nrelease: 1.88.0\nLLVM version: 20.1.5\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/benjaminslingo/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""}},"successes":{}}
|
3
target/CACHEDIR.TAG
Normal file
3
target/CACHEDIR.TAG
Normal file
|
@ -0,0 +1,3 @@
|
|||
Signature: 8a477f597d28d172789f06886806bc55
|
||||
# This file is a cache directory tag created by cargo.
|
||||
# For information about cache directory tags see https://bford.info/cachedir/
|
0
target/debug/.cargo-lock
Normal file
0
target/debug/.cargo-lock
Normal file
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
422a05ee2db51336
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[\"perf-literal\", \"std\"]","declared_features":"[\"default\", \"logging\", \"perf-literal\", \"std\"]","target":7534583537114156500,"profile":5347358027863023418,"path":17873185266263876512,"deps":[[15932120279885307830,"memchr",false,5040847368577663435]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/aho-corasick-1feeea2846bf9a54/dep-lib-aho_corasick","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
eca644477a4582c2
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[\"perf-literal\", \"std\"]","declared_features":"[\"default\", \"logging\", \"perf-literal\", \"std\"]","target":7534583537114156500,"profile":8276155916380437441,"path":17873185266263876512,"deps":[[15932120279885307830,"memchr",false,14597106110657843588]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/aho-corasick-d5624ace81511cea/dep-lib-aho_corasick","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
|
@ -0,0 +1 @@
|
|||
e611eb13dd996a08
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[\"default\", \"std\"]","declared_features":"[\"backtrace\", \"default\", \"std\"]","target":17883862002600103897,"profile":3033921117576893,"path":12018638796584673167,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/anyhow-10c398d7979ff42c/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
BIN
target/debug/.fingerprint/anyhow-47177cbcd21c3ba7/dep-lib-anyhow
Normal file
BIN
target/debug/.fingerprint/anyhow-47177cbcd21c3ba7/dep-lib-anyhow
Normal file
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
ff666a69f72cb7e0
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[\"default\", \"std\"]","declared_features":"[\"backtrace\", \"default\", \"std\"]","target":16100955855663461252,"profile":5347358027863023418,"path":15627702722116807464,"deps":[[11207653606310558077,"build_script_build",false,10024759104417559992]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/anyhow-47177cbcd21c3ba7/dep-lib-anyhow","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
|
@ -0,0 +1 @@
|
|||
b8f5abc14a191f8b
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[11207653606310558077,"build_script_build",false,606466274635747814]],"local":[{"RerunIfChanged":{"output":"debug/build/anyhow-cdebe9b23a9401c7/output","paths":["src/nightly.rs"]}},{"RerunIfEnvChanged":{"var":"RUSTC_BOOTSTRAP","val":null}}],"rustflags":[],"config":0,"compile_kind":0}
|
BIN
target/debug/.fingerprint/anyhow-df2c9fc02dd1f14f/dep-lib-anyhow
Normal file
BIN
target/debug/.fingerprint/anyhow-df2c9fc02dd1f14f/dep-lib-anyhow
Normal file
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
c2c798e888ce3d28
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[\"default\", \"std\"]","declared_features":"[\"backtrace\", \"default\", \"std\"]","target":16100955855663461252,"profile":8276155916380437441,"path":15627702722116807464,"deps":[[11207653606310558077,"build_script_build",false,10024759104417559992]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/anyhow-df2c9fc02dd1f14f/dep-lib-anyhow","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
0fb42030bacc9e1a
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[]","declared_features":"[]","target":5116616278641129243,"profile":3033921117576893,"path":3384790995447098764,"deps":[[373107762698212489,"proc_macro2",false,13189891092243910014],[17332570067994900305,"syn",false,6373626990557716385],[17990358020177143287,"quote",false,10273071015972613296]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/async-trait-ce527dd91c998959/dep-lib-async_trait","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
6f6f50755696955c
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[]","declared_features":"[]","target":6962977057026645649,"profile":3033921117576893,"path":18288342198970045472,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/autocfg-9e4953921da0651d/dep-lib-autocfg","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
d881a35822cb5907
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[\"std\"]","declared_features":"[\"arbitrary\", \"bytemuck\", \"example_generated\", \"serde\", \"std\"]","target":7691312148208718491,"profile":5347358027863023418,"path":6169628885824995304,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/bitflags-44797e8ab566aa40/dep-lib-bitflags","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
4215e7baf90f351e
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[]","declared_features":"[\"arbitrary\", \"bytemuck\", \"example_generated\", \"serde\", \"std\"]","target":7691312148208718491,"profile":5347358027863023418,"path":6169628885824995304,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/bitflags-80b058638c478bc1/dep-lib-bitflags","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
2aaa512c20161996
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[]","declared_features":"[\"arbitrary\", \"bytemuck\", \"example_generated\", \"serde\", \"std\"]","target":7691312148208718491,"profile":8276155916380437441,"path":6169628885824995304,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/bitflags-b869baeabc91aa74/dep-lib-bitflags","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
BIN
target/debug/.fingerprint/bytes-02008f075e8eeac1/dep-lib-bytes
Normal file
BIN
target/debug/.fingerprint/bytes-02008f075e8eeac1/dep-lib-bytes
Normal file
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
6732f52d11ef6ac2
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"extra-platforms\", \"serde\", \"std\"]","target":15971911772774047941,"profile":7855341030452660939,"path":7133005770400231889,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/bytes-02008f075e8eeac1/dep-lib-bytes","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
BIN
target/debug/.fingerprint/bytes-541ece9d6c88c87e/dep-lib-bytes
Normal file
BIN
target/debug/.fingerprint/bytes-541ece9d6c88c87e/dep-lib-bytes
Normal file
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
d68316615b0c36c5
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"extra-platforms\", \"serde\", \"std\"]","target":15971911772774047941,"profile":3883922691551601380,"path":7133005770400231889,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/bytes-541ece9d6c88c87e/dep-lib-bytes","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
BIN
target/debug/.fingerprint/cfg-if-13bf4fdb10761b79/dep-lib-cfg_if
Normal file
BIN
target/debug/.fingerprint/cfg-if-13bf4fdb10761b79/dep-lib-cfg_if
Normal file
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
a6b43160fb9c1b2a
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[]","declared_features":"[\"core\", \"rustc-dep-of-std\"]","target":13840298032947503755,"profile":8276155916380437441,"path":11177813554324316743,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/cfg-if-13bf4fdb10761b79/dep-lib-cfg_if","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
BIN
target/debug/.fingerprint/cfg-if-a3cf955d8a8f1781/dep-lib-cfg_if
Normal file
BIN
target/debug/.fingerprint/cfg-if-a3cf955d8a8f1781/dep-lib-cfg_if
Normal file
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
46670e454026377a
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[]","declared_features":"[\"core\", \"rustc-dep-of-std\"]","target":13840298032947503755,"profile":5347358027863023418,"path":11177813554324316743,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/cfg-if-a3cf955d8a8f1781/dep-lib-cfg_if","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
BIN
target/debug/.fingerprint/chrono-d105f5085d10753a/dep-lib-chrono
Normal file
BIN
target/debug/.fingerprint/chrono-d105f5085d10753a/dep-lib-chrono
Normal file
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
ae22cff188738420
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[\"alloc\", \"android-tzdata\", \"clock\", \"default\", \"iana-time-zone\", \"js-sys\", \"now\", \"oldtime\", \"std\", \"wasm-bindgen\", \"wasmbind\", \"winapi\", \"windows-link\"]","declared_features":"[\"__internal_bench\", \"alloc\", \"android-tzdata\", \"arbitrary\", \"clock\", \"default\", \"iana-time-zone\", \"js-sys\", \"libc\", \"now\", \"oldtime\", \"pure-rust-locales\", \"rkyv\", \"rkyv-16\", \"rkyv-32\", \"rkyv-64\", \"rkyv-validation\", \"serde\", \"std\", \"unstable-locales\", \"wasm-bindgen\", \"wasmbind\", \"winapi\", \"windows-link\"]","target":15315924755136109342,"profile":8276155916380437441,"path":10066135389468083283,"deps":[[5157631553186200874,"num_traits",false,17950302272165665873],[7910860254152155345,"iana_time_zone",false,18339565706167481346]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/chrono-d105f5085d10753a/dep-lib-chrono","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
BIN
target/debug/.fingerprint/chrono-d5282f3dfcc9f261/dep-lib-chrono
Normal file
BIN
target/debug/.fingerprint/chrono-d5282f3dfcc9f261/dep-lib-chrono
Normal file
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
f35c3381c5730c97
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[\"alloc\", \"android-tzdata\", \"clock\", \"default\", \"iana-time-zone\", \"js-sys\", \"now\", \"oldtime\", \"std\", \"wasm-bindgen\", \"wasmbind\", \"winapi\", \"windows-link\"]","declared_features":"[\"__internal_bench\", \"alloc\", \"android-tzdata\", \"arbitrary\", \"clock\", \"default\", \"iana-time-zone\", \"js-sys\", \"libc\", \"now\", \"oldtime\", \"pure-rust-locales\", \"rkyv\", \"rkyv-16\", \"rkyv-32\", \"rkyv-64\", \"rkyv-validation\", \"serde\", \"std\", \"unstable-locales\", \"wasm-bindgen\", \"wasmbind\", \"winapi\", \"windows-link\"]","target":15315924755136109342,"profile":5347358027863023418,"path":10066135389468083283,"deps":[[5157631553186200874,"num_traits",false,45860613435061929],[7910860254152155345,"iana_time_zone",false,6708066371681310681]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/chrono-d5282f3dfcc9f261/dep-lib-chrono","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
282bd87258c17f63
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[\"default\", \"link\"]","declared_features":"[\"default\", \"link\", \"mac_os_10_7_support\", \"mac_os_10_8_features\"]","target":18224550799097559944,"profile":5347358027863023418,"path":12165334760227237998,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-foundation-sys-219528d4f8c67722/dep-lib-core_foundation_sys","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
484da2638a91fc4f
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[\"default\", \"link\"]","declared_features":"[\"default\", \"link\", \"mac_os_10_7_support\", \"mac_os_10_8_features\"]","target":18224550799097559944,"profile":8276155916380437441,"path":12165334760227237998,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-foundation-sys-451408b2c3c1b3e6/dep-lib-core_foundation_sys","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
8a3973ec83ce2c4d
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"std\"]","target":12076344148867932973,"profile":4644395165371750832,"path":7894717080380973041,"deps":[[4468123440088164316,"crossbeam_utils",false,11415634275527372633]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/crossbeam-channel-10777f2c995447c1/dep-lib-crossbeam_channel","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
d568cad8ddafbf88
|
|
@ -0,0 +1 @@
|
|||
{"rustc":12610991425282158916,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"std\"]","target":12076344148867932973,"profile":4644395165371750832,"path":7894717080380973041,"deps":[[4468123440088164316,"crossbeam_utils",false,2448390509784746602]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/crossbeam-channel-7e50b83bb9fbcc81/dep-lib-crossbeam_channel","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
This file has an mtime of when this was started.
|
|
@ -0,0 +1 @@
|
|||
e0a794e3ba202d76
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue