use crate::{CanvasConfig, DrawEffect, Drawable};
use anyhow::Result;
use std::io::{BufWriter, Write};
use std::{fmt, fs::File};
struct TimePoint {
t: f64,
}
impl fmt::Display for TimePoint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let secs = self.t.floor() as u32;
let hour = secs / 3600;
let minutes = (secs % 3600) / 60;
let left = self.t - (hour * 3600) as f64 - (minutes * 60) as f64;
write!(f, "{hour}:{minutes:02}:{left:05.2}")
}
}
struct AssEffect {
effect: DrawEffect,
}
impl fmt::Display for AssEffect {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.effect {
DrawEffect::Move { start, end } => {
let (x0, y0) = start;
let (x1, y1) = end;
write!(f, "\\move({x0}, {y0}, {x1}, {y1})")
}
DrawEffect::Fixed {} => {
error!("应该不会出现固定弹幕的");
fmt::Result::Err(fmt::Error)
}
}
}
}
impl super::CanvasConfig {
pub fn ass_styles(&self) -> Vec<String> {
vec![
format!(
"Style: Float,{font},{font_size},&H{a:02x}FFFFFF,&H00FFFFFF,&H{a:02x}000000,&H00000000,\
-1, 0, 0, 0, 100, 100, 0.00, 0.00, 1, \
0.5, 0, 7, 0, 0, 0, 0",
a = self.opacity,
font = self.font,
font_size = 25,
),
format!(
"Style: Bottom,{font},{font_size},&H{a:02x}FFFFFF,&H00FFFFFF,&H{a:02x}000000,&H00000000,\
1, 0, 0, 0, 100, 100, 0.00, 0.00, 1, \
1, 0, 7, 0, 0, 0, 0",
a = self.opacity,
font = self.font,
font_size = 25,
),
format!(
"Style: Top,{font},{font_size},&H{a:02x}FFFFFF,&H00FFFFFF,&H{a:02x}000000,&H00000000,\
1, 0, 0, 0, 100, 100, 0.00, 0.00, 1, \
1, 0, 7, 0, 0, 0, 0",
a = self.opacity,
font = self.font,
font_size = 25,
),
]
}
}
struct CanvasStyles(Vec<String>);
impl fmt::Display for CanvasStyles {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for style in &self.0 {
writeln!(f, "{}", style)?;
}
Ok(())
}
}
pub struct AssWriter {
f: BufWriter<File>,
canvas_config: CanvasConfig,
}
impl AssWriter {
pub fn new(f: File, canvas_config: CanvasConfig) -> Result<Self> {
let mut this = AssWriter {
f: BufWriter::new(f),
canvas_config,
};
this.init()?;
Ok(this)
}
pub fn init(&mut self) -> Result<()> {
write!(
self.f,
"\
[Script Info]\n\
; Script generated by danmu2ass\n\
Script Updated By: danmu2ass (https://github.com/gwy15/danmu2ass)\n\
ScriptType: v4.00+\n\
PlayResX: {width}\n\
PlayResY: {height}\n\
Aspect Ratio: {width}:{height}\n\
Collisions: Normal\n\
WrapStyle: 2\n\
ScaledBorderAndShadow: yes\n\
YCbCr Matrix: TV.601\n\
\n\
\n\
[V4+ Styles]\n\
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, \
Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, \
Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n\
{styles}\
\n\
[Events]\n\
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n\
",
width = self.canvas_config.width,
height = self.canvas_config.height,
styles = CanvasStyles(self.canvas_config.ass_styles()),
)?;
Ok(())
}
pub fn write(&mut self, drawable: Drawable) -> Result<()> {
writeln!(
self.f,
"Dialogue: 2,{start},{end},{style},,0,0,0,,{{{effect}\\c&H{b:02x}{g:02x}{r:02x}&}}{text}",
start = TimePoint {
t: drawable.danmu.timeline_s
},
end = TimePoint {
t: drawable.danmu.timeline_s + drawable.duration
},
style = drawable.style_name,
effect = AssEffect {
effect: drawable.effect
},
b = drawable.danmu.rgb.2,
g = drawable.danmu.rgb.1,
r = drawable.danmu.rgb.0,
text = drawable.danmu.content,
)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn time_point_fmt() {
assert_eq!(format!("{}", TimePoint { t: 0.0 }), "0:00:00.00");
assert_eq!(format!("{}", TimePoint { t: 1.0 }), "0:00:01.00");
assert_eq!(format!("{}", TimePoint { t: 60.0 }), "0:01:00.00");
assert_eq!(format!("{}", TimePoint { t: 3600.0 }), "1:00:00.00");
assert_eq!(format!("{}", TimePoint { t: 3600.0 + 60.0 }), "1:01:00.00");
assert_eq!(
format!(
"{}",
TimePoint {
t: 3600.0 + 60.0 + 1.0
}
),
"1:01:01.00"
);
assert_eq!(
format!(
"{}",
TimePoint {
t: 3600.0 + 60.0 + 1.0 + 0.5
}
),
"1:01:01.50"
);
assert_eq!(
format!(
"{}",
TimePoint {
t: 3600.0 + 1.0 + 0.01234
}
),
"1:00:01.01"
);
}
}