video: ass subtitle format (WIP)
This commit is contained in:
parent
867447832a
commit
2d32b782bc
3 changed files with 89 additions and 52 deletions
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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!);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue