video: ass subtitle format (WIP)

This commit is contained in:
Thibault Deckers 2021-06-21 19:17:07 +09:00
parent 867447832a
commit 2d32b782bc
3 changed files with 89 additions and 52 deletions

View file

@ -4,7 +4,7 @@ import 'package:aves/widgets/viewer/visual/subtitle/style.dart';
import 'package:flutter/material.dart';
class AssParser {
static final tagPattern = RegExp(r'('
static final tagPattern = RegExp(r'\*?('
r'1a|2a|3a|4a'
r'|1c|2c|3c|4c'
r'|alpha|an|a'
@ -31,8 +31,8 @@ class AssParser {
// (<X>,<Y>)
static final multiParamPattern = RegExp('\\((.*)\\)');
// e.g. m 50 0 b 100 0 100 100 50 100 b 0 100 0 0 50 0
static final pathPattern = RegExp(r'([mnlbspc])([\s\d]+)');
// e.g. m 937.5 472.67 b 937.5 472.67 937.25 501.25 960 501.5 960 501.5 937.5 500.33 937.5 529.83
static final pathPattern = RegExp(r'([mnlbspc])([.\s\d]+)');
static const noBreakSpace = '\u00A0';
@ -50,9 +50,10 @@ class AssParser {
final matches = RegExp(r'{(.*?)}').allMatches(text);
matches.forEach((m) {
if (i != m.start) {
final spanText = extraStyle.drawingPaths?.isNotEmpty == true ? null : _replaceChars(text.substring(i, m.start));
spans.add(StyledSubtitleSpan(
textSpan: TextSpan(
text: extraStyle.drawingPaths?.isNotEmpty == true ? null : _replaceChars(text.substring(i, m.start)),
text: spanText,
style: textStyle,
),
extraStyle: extraStyle,
@ -230,13 +231,14 @@ class AssParser {
// \p drawing paths
final scale = int.tryParse(param);
if (scale != null) {
extraStyle = extraStyle.copyWith(
drawingPaths: scale > 0
? _parsePaths(
text.substring(m.end),
scale,
)
: null);
if (scale > 0) {
final start = m.end;
final end = text.indexOf('{', start);
final commands = text.substring(start, end == -1 ? null : end);
extraStyle = extraStyle.copyWith(drawingPaths: _parsePaths(commands, scale));
} else {
extraStyle = extraStyle.copyWith(drawingPaths: null);
}
}
break;
}
@ -307,9 +309,10 @@ class AssParser {
});
});
if (i != text.length) {
final spanText = extraStyle.drawingPaths?.isNotEmpty == true ? null : _replaceChars(text.substring(i));
spans.add(StyledSubtitleSpan(
textSpan: TextSpan(
text: extraStyle.drawingPaths?.isNotEmpty == true ? null : _replaceChars(text.substring(i)),
text: spanText,
style: textStyle,
),
extraStyle: extraStyle,

View file

@ -9,11 +9,11 @@ class SubtitleStyle with Diagnosticable {
final double? borderWidth, edgeBlur, rotationX, rotationY, rotationZ, scaleX, scaleY, shearX, shearY;
final List<Path>? drawingPaths;
bool get rotating => (rotationX ?? 0) > 0 || (rotationY ?? 0) > 0 || (rotationZ ?? 0) > 0;
bool get rotating => (rotationX ?? 0) != 0 || (rotationY ?? 0) != 0 || (rotationZ ?? 0) != 0;
bool get scaling => (scaleX ?? 0) > 0 || (scaleY ?? 0) > 0;
bool get scaling => (scaleX ?? 1) != 1 || (scaleY ?? 1) != 1;
bool get shearing => (shearX ?? 0) > 0 || (shearY ?? 0) > 0;
bool get shearing => (shearX ?? 0) != 0 || (shearY ?? 0) != 0;
const SubtitleStyle({
this.hAlign,

View file

@ -73,11 +73,20 @@ class VideoSubtitles extends StatelessWidget {
if (text == null) return const SizedBox();
if (debugMode) {
return Center(
child: OutlinedText(
textSpans: [TextSpan(text: text)],
outlineWidth: 1,
outlineColor: Colors.black,
return Padding(
padding: const EdgeInsets.only(top: 100.0),
child: Align(
alignment: Alignment.topLeft,
child: OutlinedText(
textSpans: [
TextSpan(
text: text,
style: const TextStyle(fontSize: 14),
)
],
outlineWidth: 1,
outlineColor: Colors.black,
),
),
);
}
@ -90,26 +99,45 @@ class VideoSubtitles extends StatelessWidget {
return Stack(
children: byExtraStyle.entries.map((kv) {
final extraStyle = kv.key;
final spans = kv.value.map((v) => v.textSpan).toList();
final spans = kv.value.map((v) {
final span = v.textSpan;
final style = span.style;
return position != null && style != null
? TextSpan(
text: span.text,
style: style.copyWith(
shadows: style.shadows
?.map((v) => Shadow(
color: v.color,
offset: v.offset * viewScale,
blurRadius: v.blurRadius * viewScale,
))
.toList()),
)
: span;
}).toList();
final drawingPaths = extraStyle.drawingPaths;
final outlineColor = extraStyle.borderColor ?? Colors.black;
var child = drawingPaths != null
? CustomPaint(
painter: SubtitlePathPainter(
paths: drawingPaths,
scale: viewScale,
fillColor: spans.firstOrNull?.style?.color ?? Colors.white,
strokeColor: outlineColor,
),
)
: OutlinedText(
textSpans: spans,
outlineWidth: extraStyle.borderWidth ?? (extraStyle.edgeBlur != null ? 2 : 1),
outlineColor: outlineColor,
outlineBlurSigma: extraStyle.edgeBlur ?? 0,
textAlign: extraStyle.hAlign ?? TextAlign.center,
);
Widget child;
if (drawingPaths != null) {
child = CustomPaint(
painter: SubtitlePathPainter(
paths: drawingPaths,
scale: viewScale,
fillColor: spans.firstOrNull?.style?.color ?? Colors.white,
strokeColor: extraStyle.borderColor,
),
);
} else {
final outlineWidth = extraStyle.borderWidth ?? (extraStyle.edgeBlur != null ? 2 : 1);
child = OutlinedText(
textSpans: spans,
outlineWidth: outlineWidth * (position != null ? viewScale : 1),
outlineColor: extraStyle.borderColor ?? Colors.black,
outlineBlurSigma: extraStyle.edgeBlur ?? 0,
textAlign: extraStyle.hAlign ?? TextAlign.center,
);
}
var transform = Matrix4.identity();
@ -220,28 +248,34 @@ class VideoSubtitles extends StatelessWidget {
class SubtitlePathPainter extends CustomPainter {
final List<Path> paths;
final double scale;
final Color fillColor, strokeColor;
final Paint? fillPaint, strokePaint;
const SubtitlePathPainter({
SubtitlePathPainter({
required this.paths,
required this.scale,
required this.fillColor,
required this.strokeColor,
});
required Color? fillColor,
required Color? strokeColor,
}) : fillPaint = fillColor != null
? (Paint()
..style = PaintingStyle.fill
..color = fillColor)
: null,
strokePaint = strokeColor != null
? (Paint()
..style = PaintingStyle.stroke
..color = strokeColor)
: null;
@override
void paint(Canvas canvas, Size size) {
final fillPaint = Paint()
..style = PaintingStyle.fill
..color = fillColor;
final strokePaint = Paint()
..style = PaintingStyle.stroke
..color = strokeColor;
canvas.scale(scale, scale);
paths.forEach((path) {
canvas.drawPath(path, fillPaint);
canvas.drawPath(path, strokePaint);
if (fillPaint != null) {
canvas.drawPath(path, fillPaint!);
}
if (strokePaint != null) {
canvas.drawPath(path, strokePaint!);
}
});
}