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';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class AssParser {
|
class AssParser {
|
||||||
static final tagPattern = RegExp(r'('
|
static final tagPattern = RegExp(r'\*?('
|
||||||
r'1a|2a|3a|4a'
|
r'1a|2a|3a|4a'
|
||||||
r'|1c|2c|3c|4c'
|
r'|1c|2c|3c|4c'
|
||||||
r'|alpha|an|a'
|
r'|alpha|an|a'
|
||||||
|
@ -31,8 +31,8 @@ class AssParser {
|
||||||
// (<X>,<Y>)
|
// (<X>,<Y>)
|
||||||
static final multiParamPattern = RegExp('\\((.*)\\)');
|
static final multiParamPattern = RegExp('\\((.*)\\)');
|
||||||
|
|
||||||
// e.g. m 50 0 b 100 0 100 100 50 100 b 0 100 0 0 50 0
|
// 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 final pathPattern = RegExp(r'([mnlbspc])([.\s\d]+)');
|
||||||
|
|
||||||
static const noBreakSpace = '\u00A0';
|
static const noBreakSpace = '\u00A0';
|
||||||
|
|
||||||
|
@ -50,9 +50,10 @@ class AssParser {
|
||||||
final matches = RegExp(r'{(.*?)}').allMatches(text);
|
final matches = RegExp(r'{(.*?)}').allMatches(text);
|
||||||
matches.forEach((m) {
|
matches.forEach((m) {
|
||||||
if (i != m.start) {
|
if (i != m.start) {
|
||||||
|
final spanText = extraStyle.drawingPaths?.isNotEmpty == true ? null : _replaceChars(text.substring(i, m.start));
|
||||||
spans.add(StyledSubtitleSpan(
|
spans.add(StyledSubtitleSpan(
|
||||||
textSpan: TextSpan(
|
textSpan: TextSpan(
|
||||||
text: extraStyle.drawingPaths?.isNotEmpty == true ? null : _replaceChars(text.substring(i, m.start)),
|
text: spanText,
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
),
|
),
|
||||||
extraStyle: extraStyle,
|
extraStyle: extraStyle,
|
||||||
|
@ -230,13 +231,14 @@ class AssParser {
|
||||||
// \p drawing paths
|
// \p drawing paths
|
||||||
final scale = int.tryParse(param);
|
final scale = int.tryParse(param);
|
||||||
if (scale != null) {
|
if (scale != null) {
|
||||||
extraStyle = extraStyle.copyWith(
|
if (scale > 0) {
|
||||||
drawingPaths: scale > 0
|
final start = m.end;
|
||||||
? _parsePaths(
|
final end = text.indexOf('{', start);
|
||||||
text.substring(m.end),
|
final commands = text.substring(start, end == -1 ? null : end);
|
||||||
scale,
|
extraStyle = extraStyle.copyWith(drawingPaths: _parsePaths(commands, scale));
|
||||||
)
|
} else {
|
||||||
: null);
|
extraStyle = extraStyle.copyWith(drawingPaths: null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -307,9 +309,10 @@ class AssParser {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (i != text.length) {
|
if (i != text.length) {
|
||||||
|
final spanText = extraStyle.drawingPaths?.isNotEmpty == true ? null : _replaceChars(text.substring(i));
|
||||||
spans.add(StyledSubtitleSpan(
|
spans.add(StyledSubtitleSpan(
|
||||||
textSpan: TextSpan(
|
textSpan: TextSpan(
|
||||||
text: extraStyle.drawingPaths?.isNotEmpty == true ? null : _replaceChars(text.substring(i)),
|
text: spanText,
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
),
|
),
|
||||||
extraStyle: extraStyle,
|
extraStyle: extraStyle,
|
||||||
|
|
|
@ -9,11 +9,11 @@ class SubtitleStyle with Diagnosticable {
|
||||||
final double? borderWidth, edgeBlur, rotationX, rotationY, rotationZ, scaleX, scaleY, shearX, shearY;
|
final double? borderWidth, edgeBlur, rotationX, rotationY, rotationZ, scaleX, scaleY, shearX, shearY;
|
||||||
final List<Path>? drawingPaths;
|
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({
|
const SubtitleStyle({
|
||||||
this.hAlign,
|
this.hAlign,
|
||||||
|
|
|
@ -73,11 +73,20 @@ class VideoSubtitles extends StatelessWidget {
|
||||||
if (text == null) return const SizedBox();
|
if (text == null) return const SizedBox();
|
||||||
|
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
return Center(
|
return Padding(
|
||||||
child: OutlinedText(
|
padding: const EdgeInsets.only(top: 100.0),
|
||||||
textSpans: [TextSpan(text: text)],
|
child: Align(
|
||||||
outlineWidth: 1,
|
alignment: Alignment.topLeft,
|
||||||
outlineColor: Colors.black,
|
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(
|
return Stack(
|
||||||
children: byExtraStyle.entries.map((kv) {
|
children: byExtraStyle.entries.map((kv) {
|
||||||
final extraStyle = kv.key;
|
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 drawingPaths = extraStyle.drawingPaths;
|
||||||
|
|
||||||
final outlineColor = extraStyle.borderColor ?? Colors.black;
|
Widget child;
|
||||||
var child = drawingPaths != null
|
if (drawingPaths != null) {
|
||||||
? CustomPaint(
|
child = CustomPaint(
|
||||||
painter: SubtitlePathPainter(
|
painter: SubtitlePathPainter(
|
||||||
paths: drawingPaths,
|
paths: drawingPaths,
|
||||||
scale: viewScale,
|
scale: viewScale,
|
||||||
fillColor: spans.firstOrNull?.style?.color ?? Colors.white,
|
fillColor: spans.firstOrNull?.style?.color ?? Colors.white,
|
||||||
strokeColor: outlineColor,
|
strokeColor: extraStyle.borderColor,
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
: OutlinedText(
|
} else {
|
||||||
textSpans: spans,
|
final outlineWidth = extraStyle.borderWidth ?? (extraStyle.edgeBlur != null ? 2 : 1);
|
||||||
outlineWidth: extraStyle.borderWidth ?? (extraStyle.edgeBlur != null ? 2 : 1),
|
child = OutlinedText(
|
||||||
outlineColor: outlineColor,
|
textSpans: spans,
|
||||||
outlineBlurSigma: extraStyle.edgeBlur ?? 0,
|
outlineWidth: outlineWidth * (position != null ? viewScale : 1),
|
||||||
textAlign: extraStyle.hAlign ?? TextAlign.center,
|
outlineColor: extraStyle.borderColor ?? Colors.black,
|
||||||
);
|
outlineBlurSigma: extraStyle.edgeBlur ?? 0,
|
||||||
|
textAlign: extraStyle.hAlign ?? TextAlign.center,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
var transform = Matrix4.identity();
|
var transform = Matrix4.identity();
|
||||||
|
|
||||||
|
@ -220,28 +248,34 @@ class VideoSubtitles extends StatelessWidget {
|
||||||
class SubtitlePathPainter extends CustomPainter {
|
class SubtitlePathPainter extends CustomPainter {
|
||||||
final List<Path> paths;
|
final List<Path> paths;
|
||||||
final double scale;
|
final double scale;
|
||||||
final Color fillColor, strokeColor;
|
final Paint? fillPaint, strokePaint;
|
||||||
|
|
||||||
const SubtitlePathPainter({
|
SubtitlePathPainter({
|
||||||
required this.paths,
|
required this.paths,
|
||||||
required this.scale,
|
required this.scale,
|
||||||
required this.fillColor,
|
required Color? fillColor,
|
||||||
required this.strokeColor,
|
required Color? strokeColor,
|
||||||
});
|
}) : fillPaint = fillColor != null
|
||||||
|
? (Paint()
|
||||||
|
..style = PaintingStyle.fill
|
||||||
|
..color = fillColor)
|
||||||
|
: null,
|
||||||
|
strokePaint = strokeColor != null
|
||||||
|
? (Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..color = strokeColor)
|
||||||
|
: null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
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);
|
canvas.scale(scale, scale);
|
||||||
paths.forEach((path) {
|
paths.forEach((path) {
|
||||||
canvas.drawPath(path, fillPaint);
|
if (fillPaint != null) {
|
||||||
canvas.drawPath(path, strokePaint);
|
canvas.drawPath(path, fillPaint!);
|
||||||
|
}
|
||||||
|
if (strokePaint != null) {
|
||||||
|
canvas.drawPath(path, strokePaint!);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue