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:
Benjamin Slingo 2025-09-06 18:26:58 -04:00
commit a38143c28b
2149 changed files with 6067 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

24
.env.example Normal file
View 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

File diff suppressed because it is too large Load diff

18
Cargo.toml Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View file

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
422a05ee2db51336

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
eca644477a4582c2

View file

@ -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}

View file

@ -0,0 +1 @@
e611eb13dd996a08

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
ff666a69f72cb7e0

View file

@ -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}

View file

@ -0,0 +1 @@
b8f5abc14a191f8b

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
c2c798e888ce3d28

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
0fb42030bacc9e1a

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
6f6f50755696955c

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
d881a35822cb5907

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
4215e7baf90f351e

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
2aaa512c20161996

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
6732f52d11ef6ac2

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
d68316615b0c36c5

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
a6b43160fb9c1b2a

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
46670e454026377a

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
ae22cff188738420

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
f35c3381c5730c97

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
8a3973ec83ce2c4d

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
d568cad8ddafbf88

View file

@ -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}

View file

@ -0,0 +1 @@
This file has an mtime of when this was started.

View file

@ -0,0 +1 @@
e0a794e3ba202d76

Some files were not shown because too many files have changed in this diff Show more