mirror of
https://github.com/CCExtractor/ccextractor.git
synced 2026-02-11 21:22:22 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7ebd45d9f | ||
|
|
77abe01885 | ||
|
|
98cec31516 | ||
|
|
46b145a396 | ||
|
|
ccf2a031e9 | ||
|
|
9784cd5bd1 | ||
|
|
5d8dc3b9eb | ||
|
|
a42e847bcb | ||
|
|
b7a1dd1030 | ||
|
|
b18e696c85 | ||
|
|
d58f078c38 | ||
|
|
0bbdfc13ee | ||
|
|
5127da50d1 | ||
|
|
352f035214 | ||
|
|
f04ba8d0c4 |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -2,6 +2,8 @@ Please prefix your issue with one of the following: [BUG], [PROPOSAL], [QUESTION
|
||||
|
||||
To get the version of CCExtractor, you can use `--version`.
|
||||
|
||||
If this issue is related to the flutter GUI, please make the issue on the GUI repo [here](https://github.com/CCExtractor/ccextractorfluttergui/issues/new)
|
||||
|
||||
Please check all that apply and **remove the ones that do not**.
|
||||
|
||||
In the necessary information section, if this is a regression (something that used to work does not work anymore), make sure to specify the last known working version.
|
||||
|
||||
@@ -4,7 +4,7 @@ MAINTAINER = Marc Espie <espie@openbsd.org>
|
||||
CATEGORIES = multimedia
|
||||
COMMENT = closed caption subtitles extractor
|
||||
HOMEPAGE = https://ccextractor.org
|
||||
V = 0.90
|
||||
V = 0.93
|
||||
DISTFILES = ccextractor.${V:S/.//}-src.zip
|
||||
MASTER_SITES = ${MASTER_SITE_SOURCEFORGE:=ccextractor/}
|
||||
DISTNAME = ccextractor-$V
|
||||
|
||||
@@ -38,7 +38,7 @@ This will extract the subtitles.
|
||||
More usage information can be found on our website:
|
||||
|
||||
- [Using the command line tool](https://ccextractor.org/public/general/command_line_usage/)
|
||||
- [Using the Windows GUI](https://ccextractor.org/public/general/win_gui_usage/)
|
||||
- [Using the Flutter GUI](https://ccextractor.org/public/general/flutter_gui/)
|
||||
|
||||
You can also find the list of parameters and their brief description by running `ccextractor` without any arguments.
|
||||
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
0.93 (2021-08-16)
|
||||
-----------------
|
||||
- Minor Rust updates (format, typos, docs)
|
||||
- Updated GUI
|
||||
|
||||
0.92 (2021-08-10)
|
||||
-----------------
|
||||
- Rust updates: Added srt writer
|
||||
- Rust updates:-Added writers for transcripts and SAMI
|
||||
- Added missing DLL to Windows installer
|
||||
- Updated Windows GUI
|
||||
|
||||
0.91 (2021-07-26)
|
||||
-----------------
|
||||
- More Rust in the 708 decoder (Add Pen Presets and timing functions)
|
||||
- Updated GUI
|
||||
|
||||
0.90 (2021-07-14)
|
||||
-----------------
|
||||
- New installer (WiX based)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([CCExtractor], [0.90], [carlos@ccextractor.org])
|
||||
AC_INIT([CCExtractor], [0.93], [carlos@ccextractor.org])
|
||||
AC_CONFIG_AUX_DIR([build-conf])
|
||||
AC_CONFIG_SRCDIR([../src/ccextractor.c])
|
||||
AM_INIT_AUTOMAKE([foreign subdir-objects])
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([CCExtractor], [0.90], [carlos@ccextractor.org])
|
||||
AC_INIT([CCExtractor], [0.93], [carlos@ccextractor.org])
|
||||
AC_CONFIG_AUX_DIR([build-conf])
|
||||
AC_CONFIG_SRCDIR([../src/ccextractor.c])
|
||||
AM_INIT_AUTOMAKE([foreign subdir-objects])
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pkgname=ccextractor
|
||||
pkgver=0.90
|
||||
pkgver=0.93
|
||||
pkgrel=1
|
||||
pkgdesc="A closed captions and teletext subtitles extractor for video streams."
|
||||
arch=('i686' 'x86_64')
|
||||
|
||||
@@ -683,7 +683,7 @@ void dtvcc_process_bs(dtvcc_service_decoder *decoder)
|
||||
window->pen_column++;
|
||||
break;
|
||||
case DTVCC_WINDOW_PD_LEFT_RIGHT:
|
||||
if (decoder->windows->pen_column > 0)
|
||||
if (window->pen_column > 0)
|
||||
window->pen_column--;
|
||||
break;
|
||||
case DTVCC_WINDOW_PD_BOTTOM_TOP:
|
||||
@@ -808,7 +808,7 @@ void dtvcc_process_character(dtvcc_service_decoder *decoder, dtvcc_symbol symbol
|
||||
window->pen_column++;
|
||||
break;
|
||||
case DTVCC_WINDOW_PD_RIGHT_LEFT:
|
||||
if (decoder->windows->pen_column > 0)
|
||||
if (window->pen_column > 0)
|
||||
window->pen_column--;
|
||||
break;
|
||||
case DTVCC_WINDOW_PD_TOP_BOTTOM:
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
#include "utility.h"
|
||||
#include "ccx_common_common.h"
|
||||
|
||||
#if defined(ENABLE_RUST) && defined(WIN32)
|
||||
extern void ccxr_close_handle(void *handle);
|
||||
#endif
|
||||
|
||||
int dtvcc_is_row_empty(dtvcc_tv_screen *tv, int row_index)
|
||||
{
|
||||
for (int j = 0; j < CCX_DTVCC_SCREENGRID_COLUMNS; j++)
|
||||
@@ -434,6 +438,11 @@ void dtvcc_writer_init(dtvcc_writer_ctx *writer,
|
||||
|
||||
char *charset = cfg->all_services_charset ? cfg->all_services_charset : cfg->services_charsets[service_number - 1];
|
||||
|
||||
#ifdef ENABLE_RUST
|
||||
writer->fhandle = NULL;
|
||||
writer->charset = charset;
|
||||
#endif
|
||||
|
||||
if (charset)
|
||||
{
|
||||
writer->cd = iconv_open("UTF-8", charset);
|
||||
@@ -450,6 +459,10 @@ void dtvcc_writer_cleanup(dtvcc_writer_ctx *writer)
|
||||
{
|
||||
if (writer->fd >= 0 && writer->fd != STDOUT_FILENO)
|
||||
close(writer->fd);
|
||||
#if defined(ENABLE_RUST) && defined(WIN32)
|
||||
ccxr_close_handle(writer->fhandle);
|
||||
writer->charset = NULL;
|
||||
#endif
|
||||
free(writer->filename);
|
||||
if (writer->cd == (iconv_t)-1)
|
||||
{
|
||||
|
||||
@@ -195,16 +195,16 @@ int do_cb(struct lib_cc_decode *ctx, unsigned char *cc_block, struct cc_subtitle
|
||||
timeok = 0;
|
||||
ctx->processed_enough = 1;
|
||||
}
|
||||
char temp[4];
|
||||
temp[0] = cc_valid;
|
||||
temp[1] = cc_type;
|
||||
temp[2] = cc_block[1];
|
||||
temp[3] = cc_block[2];
|
||||
if (timeok)
|
||||
{
|
||||
if (ctx->write_format != CCX_OF_RCWT)
|
||||
{
|
||||
#ifndef ENABLE_RUST
|
||||
char temp[4];
|
||||
temp[0] = cc_valid;
|
||||
temp[1] = cc_type;
|
||||
temp[2] = cc_block[1];
|
||||
temp[3] = cc_block[2];
|
||||
dtvcc_process_data(ctx->dtvcc, (const unsigned char *)temp);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -869,6 +869,10 @@ static int init_output_ctx(struct encoder_ctx *ctx, struct encoder_cfg *cfg)
|
||||
if (!cfg->services_enabled[i])
|
||||
{
|
||||
ctx->dtvcc_writers[i].fd = -1;
|
||||
#ifdef ENABLE_RUST
|
||||
ctx->dtvcc_writers[i].fhandle = NULL;
|
||||
ctx->dtvcc_writers[i].charset = NULL;
|
||||
#endif
|
||||
ctx->dtvcc_writers[i].filename = NULL;
|
||||
ctx->dtvcc_writers[i].cd = (iconv_t)-1;
|
||||
continue;
|
||||
@@ -877,6 +881,10 @@ static int init_output_ctx(struct encoder_ctx *ctx, struct encoder_cfg *cfg)
|
||||
if (cfg->cc_to_stdout)
|
||||
{
|
||||
ctx->dtvcc_writers[i].fd = STDOUT_FILENO;
|
||||
#ifdef ENABLE_RUST
|
||||
ctx->dtvcc_writers[i].fhandle = NULL;
|
||||
ctx->dtvcc_writers[i].charset = NULL;
|
||||
#endif
|
||||
ctx->dtvcc_writers[i].filename = NULL;
|
||||
ctx->dtvcc_writers[i].cd = (iconv_t)-1;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,12 @@ if (ctx->buffer == NULL) { fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory for
|
||||
typedef struct dtvcc_writer_ctx
|
||||
{
|
||||
int fd;
|
||||
#ifdef ENABLE_RUST
|
||||
// File handle used to work with files on windows
|
||||
void *fhandle;
|
||||
// Charset of the subtitle
|
||||
char *charset;
|
||||
#endif
|
||||
char *filename;
|
||||
iconv_t cd;
|
||||
} dtvcc_writer_ctx;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef CCX_CCEXTRACTOR_H
|
||||
#define CCX_CCEXTRACTOR_H
|
||||
|
||||
#define VERSION "0.90"
|
||||
#define VERSION "0.93"
|
||||
|
||||
// Load common includes and constants for library usage
|
||||
#include "ccx_common_platform.h"
|
||||
|
||||
@@ -68,40 +68,40 @@ struct list_head {
|
||||
* This is only for internal list manipulation where we know
|
||||
* the prev/next entries already!
|
||||
*/
|
||||
static inline void __list_add(struct list_head *new,
|
||||
static inline void __list_add(struct list_head *elem,
|
||||
struct list_head *prev,
|
||||
struct list_head *next)
|
||||
{
|
||||
next->prev = new;
|
||||
new->next = next;
|
||||
new->prev = prev;
|
||||
prev->next = new;
|
||||
next->prev = elem;
|
||||
elem->next = next;
|
||||
elem->prev = prev;
|
||||
prev->next = elem;
|
||||
}
|
||||
|
||||
/**
|
||||
* list_add - add a new entry
|
||||
* @new: new entry to be added
|
||||
* @elem: new entry to be added
|
||||
* @head: list head to add it after
|
||||
*
|
||||
* Insert a new entry after the specified head.
|
||||
* This is good for implementing stacks.
|
||||
*/
|
||||
static inline void list_add(struct list_head *new, struct list_head *head)
|
||||
static inline void list_add(struct list_head *elem, struct list_head *head)
|
||||
{
|
||||
__list_add(new, head, head->next);
|
||||
__list_add(elem, head, head->next);
|
||||
}
|
||||
|
||||
/**
|
||||
* list_add_tail - add a new entry
|
||||
* @new: new entry to be added
|
||||
* @elem: new entry to be added
|
||||
* @head: list head to add it before
|
||||
*
|
||||
* Insert a new entry before the specified head.
|
||||
* This is useful for implementing queues.
|
||||
*/
|
||||
static inline void list_add_tail(struct list_head *new, struct list_head *head)
|
||||
static inline void list_add_tail(struct list_head *elem, struct list_head *head)
|
||||
{
|
||||
__list_add(new, head->prev, head);
|
||||
__list_add(elem, head->prev, head);
|
||||
}
|
||||
|
||||
|
||||
|
||||
21
src/rust/Cargo.lock
generated
21
src/rust/Cargo.lock
generated
@@ -64,6 +64,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"env_logger",
|
||||
"iconv",
|
||||
"log",
|
||||
]
|
||||
|
||||
@@ -108,6 +109,12 @@ dependencies = [
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn_buf"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74c57ab96715773d9cb9789b38eb7cbf04b3c6f5624a9d98f51761603376767c"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.8.4"
|
||||
@@ -142,6 +149,16 @@ version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "iconv"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e6a7db0df823ef299ef75b6951975c7a1f9019910b3665614bac4161bab1a9"
|
||||
dependencies = [
|
||||
"dyn_buf",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@@ -156,9 +173,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.95"
|
||||
version = "0.2.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36"
|
||||
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
|
||||
@@ -13,6 +13,7 @@ crate-type = ["cdylib"]
|
||||
[dependencies]
|
||||
log = "0.4.0"
|
||||
env_logger = "0.8.4"
|
||||
iconv = "0.1.1"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.58.1"
|
||||
@@ -6,6 +6,7 @@ fn main() {
|
||||
// The input header we would like to generate
|
||||
// bindings for.
|
||||
.header("wrapper.h")
|
||||
.clang_arg("-DENABLE_RUST")
|
||||
// Tell cargo to invalidate the built crate whenever any of the
|
||||
// included header files changed.
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks));
|
||||
@@ -43,4 +44,4 @@ const ALLOWLIST_FUNCTIONS: &[&str] = &[
|
||||
"writercwtdata",
|
||||
];
|
||||
const ALLOWLIST_TYPES: &[&str] = &[".*(?i)_?dtvcc_.*", "encoder_ctx", "lib_cc_decode"];
|
||||
const RUSTIFIED_ENUMS: &[&str] = &["dtvcc_(window|pen)_.*"];
|
||||
const RUSTIFIED_ENUMS: &[&str] = &["dtvcc_(window|pen)_.*", "ccx_output_format"];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Different code sets as defined in CEA-708-E
|
||||
//!
|
||||
//! Refer section 7.1 CEA-708-E
|
||||
//! Refer section 7.1 CEA-708-E.
|
||||
//! Different code sets are:
|
||||
//!
|
||||
//! | Name | Description | Space |
|
||||
@@ -152,3 +152,35 @@ impl C1Command {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle C2 - Code Set - Extended Control Code Set 1
|
||||
pub fn handle_C2(code: u8) -> u8 {
|
||||
match code {
|
||||
// ... Single-byte control bytes (0 additional bytes)
|
||||
0..=0x07 => 1,
|
||||
// ..two-byte control codes (1 additional byte)
|
||||
0x08..=0x0F => 2,
|
||||
// ..three-byte control codes (2 additional bytes)
|
||||
0x10..=0x17 => 3,
|
||||
// 18-1F => four-byte control codes (3 additional bytes)
|
||||
_ => 4,
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle C3 - Code Set - Extended Control Code Set 2
|
||||
pub fn handle_C3(code: u8, next_code: u8) -> u8 {
|
||||
match code {
|
||||
// Five-byte control bytes (4 additional bytes)
|
||||
0x80..=0x87 => 5,
|
||||
// Six-byte control codes (5 additional byte)
|
||||
0x88..=0x8F => 6,
|
||||
// 90-9F variable length commands
|
||||
// Refer Section 7.1.11.2
|
||||
_ => {
|
||||
// next code is the header which specifies additional bytes
|
||||
let length = (next_code & 0x3F) + 1;
|
||||
// + 1 for current code
|
||||
length + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
//! CEA 708 decoder
|
||||
//!
|
||||
//! This module provides a CEA 708 decoder as defined by ANSI/CTA-708-E R-2018
|
||||
//! Provides a CEA 708 decoder as defined by ANSI/CTA-708-E R-2018
|
||||
|
||||
mod commands;
|
||||
mod output;
|
||||
mod service_decoder;
|
||||
mod timing;
|
||||
mod tv_screen;
|
||||
mod window;
|
||||
|
||||
use crate::{bindings::*, utils::is_true};
|
||||
|
||||
use log::{debug, warn};
|
||||
|
||||
use crate::bindings::*;
|
||||
|
||||
const CCX_DTVCC_MAX_PACKET_LENGTH: u8 = 128;
|
||||
const CCX_DTVCC_NO_LAST_SEQUENCE: i32 = -1;
|
||||
const CCX_DTVCC_SCREENGRID_ROWS: u8 = 75;
|
||||
const CCX_DTVCC_SCREENGRID_COLUMNS: u8 = 210;
|
||||
const CCX_DTVCC_MAX_ROWS: u8 = 15;
|
||||
const CCX_DTVCC_MAX_COLUMNS: u8 = 32 * 2;
|
||||
|
||||
/// Stores the context required for processing 708 data
|
||||
/// Context required for processing 708 data
|
||||
pub struct Dtvcc<'a> {
|
||||
pub is_active: bool,
|
||||
pub active_services_count: u8,
|
||||
@@ -33,45 +40,27 @@ pub struct Dtvcc<'a> {
|
||||
impl<'a> Dtvcc<'a> {
|
||||
/// Create a new dtvcc context
|
||||
pub fn new(ctx: &'a mut dtvcc_ctx) -> Self {
|
||||
let mut is_active = false;
|
||||
let mut report_enabled = false;
|
||||
let mut is_header_parsed = false;
|
||||
let mut no_rollup = false;
|
||||
|
||||
if ctx.is_active == 1 {
|
||||
is_active = true;
|
||||
}
|
||||
if ctx.report_enabled == 1 {
|
||||
report_enabled = true;
|
||||
}
|
||||
if ctx.is_current_packet_header_parsed == 1 {
|
||||
is_header_parsed = true;
|
||||
}
|
||||
if ctx.no_rollup == 1 {
|
||||
no_rollup = true;
|
||||
}
|
||||
|
||||
let report = unsafe { &mut *ctx.report };
|
||||
let encoder = unsafe { &mut *(ctx.encoder as *mut encoder_ctx) };
|
||||
let timing = unsafe { &mut *ctx.timing };
|
||||
|
||||
Self {
|
||||
is_active,
|
||||
is_active: is_true(ctx.is_active),
|
||||
active_services_count: ctx.active_services_count as u8,
|
||||
services_active: ctx.services_active.iter().copied().collect(),
|
||||
report_enabled,
|
||||
report_enabled: is_true(ctx.report_enabled),
|
||||
report,
|
||||
decoders: ctx.decoders.iter_mut().collect(),
|
||||
packet: ctx.current_packet.to_vec(),
|
||||
packet_length: ctx.current_packet_length as u8,
|
||||
is_header_parsed,
|
||||
is_header_parsed: is_true(ctx.is_current_packet_header_parsed),
|
||||
last_sequence: ctx.last_sequence,
|
||||
encoder,
|
||||
no_rollup,
|
||||
no_rollup: is_true(ctx.no_rollup),
|
||||
timing,
|
||||
}
|
||||
}
|
||||
/// Process cc data to generate dtvcc packet
|
||||
/// Process cc data and add it to the dtvcc packet
|
||||
pub fn process_cc_data(&mut self, cc_valid: u8, cc_type: u8, data1: u8, data2: u8) {
|
||||
if !self.is_active && !self.report_enabled {
|
||||
return;
|
||||
@@ -97,6 +86,7 @@ impl<'a> Dtvcc<'a> {
|
||||
max_len *= 2;
|
||||
}
|
||||
|
||||
// If packet is complete then process the packet
|
||||
if self.packet_length >= max_len {
|
||||
self.process_current_packet(max_len);
|
||||
}
|
||||
@@ -120,6 +110,7 @@ impl<'a> Dtvcc<'a> {
|
||||
),
|
||||
}
|
||||
}
|
||||
/// Add data to the packet
|
||||
pub fn add_data_to_packet(&mut self, data1: u8, data2: u8) {
|
||||
self.packet[self.packet_length as usize] = data1;
|
||||
self.packet_length += 1;
|
||||
@@ -137,6 +128,8 @@ impl<'a> Dtvcc<'a> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if current sequence is correct
|
||||
// Sequence number is a 2 bit rolling sequence from (0-3)
|
||||
if self.last_sequence != CCX_DTVCC_NO_LAST_SEQUENCE
|
||||
&& (self.last_sequence + 1) % 4 != seq as i32
|
||||
{
|
||||
@@ -172,12 +165,10 @@ impl<'a> Dtvcc<'a> {
|
||||
self.report.services[service_number as usize] = 1;
|
||||
}
|
||||
|
||||
if service_number > 0 && self.services_active[(service_number - 1) as usize] == 1 {
|
||||
if service_number > 0 && is_true(self.services_active[(service_number - 1) as usize]) {
|
||||
let decoder = &mut self.decoders[(service_number - 1) as usize];
|
||||
decoder.process_service_block(
|
||||
&mut self.packet,
|
||||
pos,
|
||||
block_length,
|
||||
&self.packet[pos as usize..(pos + block_length) as usize],
|
||||
self.encoder,
|
||||
self.timing,
|
||||
self.no_rollup,
|
||||
@@ -202,9 +193,29 @@ impl<'a> Dtvcc<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A single character symbol
|
||||
///
|
||||
/// sym stores the symbol
|
||||
/// init is used to know if the symbol is initialized
|
||||
impl dtvcc_symbol {
|
||||
/// Create a new symbol
|
||||
pub fn new(sym: u16) -> dtvcc_symbol {
|
||||
dtvcc_symbol { init: 1, sym }
|
||||
pub fn new(sym: u16) -> Self {
|
||||
Self { init: 1, sym }
|
||||
}
|
||||
/// Create a new 16 bit symbol
|
||||
pub fn new_16(data1: u8, data2: u8) -> Self {
|
||||
let sym = (data1 as u16) << 8 | data2 as u16;
|
||||
Self { init: 1, sym }
|
||||
}
|
||||
/// Check if symbol is initialized
|
||||
pub fn is_set(&self) -> bool {
|
||||
is_true(self.init)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for dtvcc_symbol {
|
||||
/// Create a blank uninitialized symbol
|
||||
fn default() -> Self {
|
||||
Self { sym: 0, init: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
99
src/rust/src/decoder/output.rs
Normal file
99
src/rust/src/decoder/output.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
//! Utilty functions to write captions
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::{FromRawHandle, IntoRawHandle, RawHandle};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Write,
|
||||
os::unix::prelude::{FromRawFd, IntoRawFd},
|
||||
};
|
||||
|
||||
use crate::{bindings::*, utils::is_true};
|
||||
|
||||
use log::debug;
|
||||
|
||||
// Context for writing subtitles to file
|
||||
pub struct Writer<'a> {
|
||||
pub cea_708_counter: &'a mut u32,
|
||||
pub subs_delay: LLONG,
|
||||
pub crlf: String,
|
||||
pub write_format: ccx_output_format,
|
||||
pub writer_ctx: &'a mut dtvcc_writer_ctx,
|
||||
pub no_font_color: bool,
|
||||
pub transcript_settings: &'a ccx_encoders_transcript_format,
|
||||
pub no_bom: i32,
|
||||
}
|
||||
|
||||
impl<'a> Writer<'a> {
|
||||
/// Create a new writer context
|
||||
pub fn new(
|
||||
cea_708_counter: &'a mut u32,
|
||||
subs_delay: LLONG,
|
||||
write_format: ccx_output_format,
|
||||
writer_ctx: &'a mut dtvcc_writer_ctx,
|
||||
no_font_color: i32,
|
||||
transcript_settings: &'a ccx_encoders_transcript_format,
|
||||
no_bom: i32,
|
||||
) -> Self {
|
||||
Self {
|
||||
cea_708_counter,
|
||||
subs_delay,
|
||||
crlf: "\r\n".to_owned(),
|
||||
write_format,
|
||||
writer_ctx,
|
||||
no_font_color: is_true(no_font_color),
|
||||
transcript_settings,
|
||||
no_bom,
|
||||
}
|
||||
}
|
||||
/// Write subtitles to the file
|
||||
///
|
||||
/// File must already exist
|
||||
/// On Unix Platforms, uses the raw fd from context to access the file
|
||||
/// On Windows Platforms, uses the raw handle from context to access the file
|
||||
pub fn write_to_file(&mut self, buf: &[u8]) -> Result<(), String> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let mut file = unsafe { File::from_raw_fd(self.writer_ctx.fd) };
|
||||
file.write_all(buf).map_err(|err| err.to_string())?;
|
||||
self.writer_ctx.fd = file.into_raw_fd();
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut file = unsafe { File::from_raw_handle(self.writer_ctx.fhandle) };
|
||||
file.write_all(buf).map_err(|err| err.to_string())?;
|
||||
self.writer_ctx.fhandle = file.into_raw_handle();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the symbol to the provided buffer
|
||||
///
|
||||
/// If symbol is 8-bit, then it's written to the buffer
|
||||
/// If symbol is 16-bit, then two 8-bit symbols are written to the buffer
|
||||
pub fn write_char(sym: &dtvcc_symbol, buf: &mut Vec<u8>) {
|
||||
if sym.sym >> 8 != 0 {
|
||||
buf.push((sym.sym >> 8) as u8);
|
||||
buf.push((sym.sym & 0xff) as u8);
|
||||
} else {
|
||||
buf.push(sym.sym as u8);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from CEA-708 color representation to hex code
|
||||
///
|
||||
/// Two bits are specified for each red, green, and blue color value which defines the
|
||||
/// intensity of each individual color component.
|
||||
///
|
||||
/// Refer section 8.8 CEA-708-E
|
||||
pub fn color_to_hex(color: u8) -> (u8, u8, u8) {
|
||||
let red = color >> 4;
|
||||
let green = (color >> 2) & 0x3;
|
||||
let blue = color & 0x3;
|
||||
debug!(
|
||||
"Color: {} [{:06x}] {} {} {}",
|
||||
color, color, red, green, blue
|
||||
);
|
||||
(red, green, blue)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
49
src/rust/src/decoder/timing.rs
Normal file
49
src/rust/src/decoder/timing.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
//! Utilty functions to get timing for captions
|
||||
|
||||
use crate::{bindings::*, cb_708, cb_field1, cb_field2};
|
||||
|
||||
use log::{debug, error};
|
||||
|
||||
impl ccx_common_timing_ctx {
|
||||
/// Return the current FTS
|
||||
pub fn get_fts(&self, current_field: u8) -> LLONG {
|
||||
unsafe {
|
||||
match current_field {
|
||||
1 => self.fts_now + self.fts_global + cb_field1 as i64 * 1001 / 30,
|
||||
2 => self.fts_now + self.fts_global + cb_field2 as i64 * 1001 / 30,
|
||||
3 => self.fts_now + self.fts_global + cb_708 as i64 * 1001 / 30,
|
||||
_ => {
|
||||
error!("get_fts: Unknown field");
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Returns the current FTS and saves it so it can be used by [get_visible_start][Self::get_visible_start()]
|
||||
pub fn get_visible_end(&mut self, current_field: u8) -> LLONG {
|
||||
let fts = self.get_fts(current_field);
|
||||
if fts > self.minimum_fts {
|
||||
self.minimum_fts = fts;
|
||||
}
|
||||
debug!("Visible End time={}", get_time_str(fts));
|
||||
fts
|
||||
}
|
||||
/// Returns a FTS that is guaranteed to be at least 1 ms later than the end of the previous screen, so that there's no timing overlap
|
||||
pub fn get_visible_start(self, current_field: u8) -> LLONG {
|
||||
let mut fts = self.get_fts(current_field);
|
||||
if fts <= self.minimum_fts {
|
||||
fts = self.minimum_fts + 1;
|
||||
}
|
||||
debug!("Visible Start time={}", get_time_str(fts));
|
||||
fts
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a hh:mm:ss,ms string of time
|
||||
pub fn get_time_str(time: LLONG) -> String {
|
||||
let hh = time / 1000 / 60 / 60;
|
||||
let mm = time / 1000 / 60 - 60 * hh;
|
||||
let ss = time / 1000 - 60 * (mm + 60 * hh);
|
||||
let ms = time - 1000 * (ss + 60 * (mm + 60 * hh));
|
||||
format!("{:02}:{:02}:{:02},{:03}", hh, mm, ss, ms)
|
||||
}
|
||||
478
src/rust/src/decoder/tv_screen.rs
Normal file
478
src/rust/src/decoder/tv_screen.rs
Normal file
@@ -0,0 +1,478 @@
|
||||
//! TV screen to be displayed
|
||||
//!
|
||||
//! TV screen contains the captions to be displayed.
|
||||
//! Captions are added to TV screen from a window when any of DSW, HDW, TGW, DLW or CR commands are received
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::IntoRawHandle;
|
||||
use std::{ffi::CStr, fs::File, os::unix::prelude::IntoRawFd};
|
||||
|
||||
use super::output::{color_to_hex, write_char, Writer};
|
||||
use super::timing::get_time_str;
|
||||
use super::{CCX_DTVCC_SCREENGRID_COLUMNS, CCX_DTVCC_SCREENGRID_ROWS};
|
||||
use crate::{
|
||||
bindings::*,
|
||||
utils::{is_false, is_true},
|
||||
};
|
||||
|
||||
use log::{debug, warn};
|
||||
|
||||
impl dtvcc_tv_screen {
|
||||
/// Clear all text from TV screen
|
||||
pub fn clear(&mut self) {
|
||||
for row in 0..CCX_DTVCC_SCREENGRID_ROWS as usize {
|
||||
self.chars[row].fill(dtvcc_symbol::default());
|
||||
}
|
||||
self.time_ms_hide = -1;
|
||||
self.time_ms_show = -1;
|
||||
}
|
||||
|
||||
/// Update TV screen show time
|
||||
pub fn update_time_show(&mut self, time: LLONG) {
|
||||
let prev_time_str = get_time_str(self.time_ms_show);
|
||||
let curr_time_str = get_time_str(time);
|
||||
debug!("Screen show time: {} -> {}", prev_time_str, curr_time_str);
|
||||
if self.time_ms_show == -1 || self.time_ms_show > time {
|
||||
self.time_ms_show = time;
|
||||
}
|
||||
}
|
||||
|
||||
/// Update TV screen hide time
|
||||
pub fn update_time_hide(&mut self, time: LLONG) {
|
||||
let prev_time_str = get_time_str(self.time_ms_hide);
|
||||
let curr_time_str = get_time_str(time);
|
||||
debug!("Screen hide time: {} -> {}", prev_time_str, curr_time_str);
|
||||
if self.time_ms_hide == -1 || self.time_ms_hide < time {
|
||||
self.time_ms_hide = time;
|
||||
}
|
||||
}
|
||||
|
||||
/// Write captions to the output file
|
||||
///
|
||||
/// Opens a new file when called for the first time.
|
||||
/// Uses the already open file on subsequent calls.
|
||||
pub fn writer_output(&self, writer: &mut Writer) -> Result<(), String> {
|
||||
debug!(
|
||||
"dtvcc_writer_output: writing... [{:?}][{}]",
|
||||
unsafe { CStr::from_ptr(writer.writer_ctx.filename) },
|
||||
writer.writer_ctx.fd
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
if writer.writer_ctx.filename.is_null() && writer.writer_ctx.fd < 0 {
|
||||
return Err("Filename missing".to_owned());
|
||||
} else if writer.writer_ctx.fd < 0 {
|
||||
let filename = unsafe {
|
||||
CStr::from_ptr(writer.writer_ctx.filename)
|
||||
.to_str()
|
||||
.map_err(|err| err.to_string())
|
||||
}?;
|
||||
debug!("dtvcc_writer_output: creating {}", filename);
|
||||
let file = File::create(filename).map_err(|err| err.to_string())?;
|
||||
|
||||
if is_false(writer.no_bom) {
|
||||
let BOM = [0xef, 0xbb, 0xbf];
|
||||
writer.write_to_file(&BOM)?;
|
||||
}
|
||||
|
||||
writer.writer_ctx.fd = file.into_raw_fd();
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
if writer.writer_ctx.filename.is_null() && writer.fhandle.is_null() {
|
||||
return Err("Filename missing".to_owned())?;
|
||||
} else if writer.fhandle.is_null() {
|
||||
let filename = unsafe {
|
||||
CStr::from_ptr(writer.writer_ctx.filename)
|
||||
.to_str()
|
||||
.map_err(|err| err.to_string())
|
||||
}?;
|
||||
debug!("dtvcc_writer_output: creating {}", filename);
|
||||
let file = File::create(filename).map_err(|err| err.to_string())?;
|
||||
|
||||
if is_false(writer.no_bom) {
|
||||
let BOM = [0xef, 0xbb, 0xbf];
|
||||
writer.write_to_file(&BOM)?;
|
||||
}
|
||||
writer.fhandle = file.into_raw_handle();
|
||||
}
|
||||
|
||||
self.write(writer);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the bounds in which captions are present
|
||||
pub fn get_write_interval(&self, row_index: usize) -> (usize, usize) {
|
||||
let mut first = 0;
|
||||
let mut last = CCX_DTVCC_SCREENGRID_COLUMNS as usize - 1;
|
||||
for col in 0..CCX_DTVCC_SCREENGRID_COLUMNS as usize {
|
||||
if self.chars[row_index][col].is_set() {
|
||||
first = col;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for col in (0..(CCX_DTVCC_SCREENGRID_COLUMNS as usize - 1)).rev() {
|
||||
if self.chars[row_index][col].is_set() {
|
||||
last = col;
|
||||
break;
|
||||
}
|
||||
}
|
||||
(first, last)
|
||||
}
|
||||
|
||||
/// Write captions according to the output file type
|
||||
///
|
||||
/// Calls the respective function for the output file type
|
||||
pub fn write(&self, writer: &mut Writer) {
|
||||
let result = match writer.write_format {
|
||||
ccx_output_format::CCX_OF_SRT => self.write_srt(writer),
|
||||
ccx_output_format::CCX_OF_SAMI => self.write_sami(writer),
|
||||
ccx_output_format::CCX_OF_TRANSCRIPT => self.write_transcript(writer),
|
||||
_ => {
|
||||
self.write_debug();
|
||||
Err("Unsupported write format".to_owned())
|
||||
}
|
||||
};
|
||||
if let Err(err) = result {
|
||||
warn!("{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
/// Write all captions from the row to the output file
|
||||
///
|
||||
/// If use_colors is 'true' then <font color="xxx"></font> tags are added to the output
|
||||
pub fn write_row(
|
||||
&self,
|
||||
writer: &mut Writer,
|
||||
row_index: usize,
|
||||
use_colors: bool,
|
||||
) -> Result<(), String> {
|
||||
let mut buf = Vec::new();
|
||||
let mut pen_color = dtvcc_pen_color::default();
|
||||
let mut pen_attribs = dtvcc_pen_attribs::default();
|
||||
let (first, last) = self.get_write_interval(row_index);
|
||||
debug!("First: {}, Last: {}", first, last);
|
||||
|
||||
for i in 0..last + 1 {
|
||||
if use_colors {
|
||||
self.change_pen_color(
|
||||
&pen_color,
|
||||
writer.no_font_color,
|
||||
row_index,
|
||||
i,
|
||||
false,
|
||||
&mut buf,
|
||||
);
|
||||
}
|
||||
self.change_pen_attribs(
|
||||
&pen_attribs,
|
||||
writer.no_font_color,
|
||||
row_index,
|
||||
i,
|
||||
false,
|
||||
&mut buf,
|
||||
);
|
||||
self.change_pen_attribs(
|
||||
&pen_attribs,
|
||||
writer.no_font_color,
|
||||
row_index,
|
||||
i,
|
||||
true,
|
||||
&mut buf,
|
||||
);
|
||||
if use_colors {
|
||||
self.change_pen_color(
|
||||
&pen_color,
|
||||
writer.no_font_color,
|
||||
row_index,
|
||||
i,
|
||||
true,
|
||||
&mut buf,
|
||||
)
|
||||
}
|
||||
pen_color = self.pen_colors[row_index][i];
|
||||
pen_attribs = self.pen_attribs[row_index][i];
|
||||
if i < first {
|
||||
buf.push(b' ');
|
||||
} else {
|
||||
write_char(&self.chars[row_index][i], &mut buf)
|
||||
}
|
||||
}
|
||||
// there can be unclosed tags or colors after the last symbol in a row
|
||||
if use_colors {
|
||||
self.change_pen_color(
|
||||
&pen_color,
|
||||
writer.no_font_color,
|
||||
row_index,
|
||||
CCX_DTVCC_SCREENGRID_COLUMNS as usize,
|
||||
false,
|
||||
&mut buf,
|
||||
)
|
||||
}
|
||||
self.change_pen_attribs(
|
||||
&pen_attribs,
|
||||
writer.no_font_color,
|
||||
row_index,
|
||||
CCX_DTVCC_SCREENGRID_COLUMNS as usize,
|
||||
false,
|
||||
&mut buf,
|
||||
);
|
||||
// Tags can still be crossed e.g <f><i>text</f></i>, but testing HTML code has shown that they still are handled correctly.
|
||||
if writer.writer_ctx.cd != (-1_isize) as iconv_t {
|
||||
if writer.writer_ctx.charset.is_null() {
|
||||
debug!("Charset: null");
|
||||
} else {
|
||||
let charset = unsafe {
|
||||
CStr::from_ptr(writer.writer_ctx.charset)
|
||||
.to_str()
|
||||
.map_err(|err| err.to_string())?
|
||||
};
|
||||
debug!("Charset: {}", charset);
|
||||
|
||||
let op = iconv::decode(&buf, charset).map_err(|err| err.to_string())?;
|
||||
writer.write_to_file(op.as_bytes())?;
|
||||
}
|
||||
} else {
|
||||
writer.write_to_file(&buf)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write captions in SRT format
|
||||
pub fn write_srt(&self, writer: &mut Writer) -> Result<(), String> {
|
||||
if self.is_screen_empty(writer) {
|
||||
return Ok(());
|
||||
}
|
||||
if self.time_ms_show + writer.subs_delay < 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let time_show = get_time_str(self.time_ms_show);
|
||||
let time_hide = get_time_str(self.time_ms_hide);
|
||||
|
||||
let counter = *writer.cea_708_counter;
|
||||
let line = format!(
|
||||
"{}{}{} --> {}{}",
|
||||
counter, "\r\n", time_show, time_hide, "\r\n"
|
||||
);
|
||||
writer.write_to_file(line.as_bytes())?;
|
||||
|
||||
for row_index in 0..CCX_DTVCC_SCREENGRID_ROWS as usize {
|
||||
if !self.is_row_empty(row_index) {
|
||||
self.write_row(writer, row_index, true)?;
|
||||
writer.write_to_file(b"\r\n")?;
|
||||
}
|
||||
}
|
||||
writer.write_to_file(b"\r\n")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write captions in Transcripts format
|
||||
pub fn write_transcript(&self, writer: &mut Writer) -> Result<(), String> {
|
||||
if self.is_screen_empty(writer) {
|
||||
return Ok(());
|
||||
}
|
||||
if self.time_ms_show + writer.subs_delay < 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let time_show = get_time_str(self.time_ms_show);
|
||||
let time_hide = get_time_str(self.time_ms_hide);
|
||||
|
||||
for row_index in 0..CCX_DTVCC_SCREENGRID_ROWS as usize {
|
||||
if !self.is_row_empty(row_index) {
|
||||
let mut buf = String::new();
|
||||
|
||||
if is_true(writer.transcript_settings.showStartTime) {
|
||||
buf.push_str(&time_show);
|
||||
buf.push('|');
|
||||
}
|
||||
if is_true(writer.transcript_settings.showEndTime) {
|
||||
buf.push_str(&time_hide);
|
||||
buf.push('|');
|
||||
}
|
||||
if is_true(writer.transcript_settings.showCC) {
|
||||
buf.push_str("CC1|"); //always CC1 because CEA-708 is field-independent
|
||||
}
|
||||
if is_true(writer.transcript_settings.showMode) {
|
||||
buf.push_str("POP|"); //TODO caption mode(pop, rollup, etc.)
|
||||
}
|
||||
writer.write_to_file(buf.as_bytes())?;
|
||||
self.write_row(writer, row_index, false)?;
|
||||
writer.write_to_file(b"\r\n")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write captions in SAMI format
|
||||
pub fn write_sami(&self, writer: &mut Writer) -> Result<(), String> {
|
||||
if self.is_screen_empty(writer) {
|
||||
return Err("Sami:- Screen is empty".to_owned());
|
||||
}
|
||||
if self.time_ms_show + writer.subs_delay < 0 {
|
||||
return Err(format!(
|
||||
"Sami:- timing is -ve, {}:{}",
|
||||
self.time_ms_show, writer.subs_delay
|
||||
));
|
||||
}
|
||||
if self.cc_count == 1 {
|
||||
self.write_sami_header(writer)?;
|
||||
}
|
||||
let buf = format!(
|
||||
"<sync start={}><p class=\"unknowncc\">\r\n",
|
||||
self.time_ms_show + writer.subs_delay
|
||||
);
|
||||
writer.write_to_file(buf.as_bytes())?;
|
||||
|
||||
for row_index in 0..CCX_DTVCC_SCREENGRID_ROWS as usize {
|
||||
if !self.is_row_empty(row_index) {
|
||||
self.write_row(writer, row_index, true)?;
|
||||
writer.write_to_file("<br>\r\n".as_bytes())?;
|
||||
}
|
||||
}
|
||||
let buf = format!(
|
||||
"<sync start={}><p class=\"unknowncc\"> </p></sync>\r\n\r\n",
|
||||
self.time_ms_hide + writer.subs_delay
|
||||
);
|
||||
writer.write_to_file(buf.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes the header according to the SAMI format
|
||||
pub fn write_sami_header(&self, writer: &mut Writer) -> Result<(), String> {
|
||||
let buf = b"<sami>\r\n\
|
||||
<head>\r\n\
|
||||
<style type=\"text/css\">\r\n\
|
||||
<!--\r\n\
|
||||
p {margin-left: 16pt; margin-right: 16pt; margin-bottom: 16pt; margin-top: 16pt;\r\n\
|
||||
text-align: center; font-size: 18pt; font-family: arial; font-weight: bold; color: #f0f0f0;}\r\n\
|
||||
.unknowncc {Name:Unknown; lang:en-US; SAMIType:CC;}\r\n\
|
||||
-->\r\n\
|
||||
</style>\r\n\
|
||||
</head>\r\n\r\n\
|
||||
<body>\r\n";
|
||||
|
||||
writer.write_to_file(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write debug messages
|
||||
///
|
||||
/// Write all characters,show and hide time as a debug log
|
||||
pub fn write_debug(&self) {
|
||||
let time_show = get_time_str(self.time_ms_show);
|
||||
let time_hide = get_time_str(self.time_ms_hide);
|
||||
debug!("{} --> {}", time_show, time_hide);
|
||||
|
||||
for row_index in 0..CCX_DTVCC_SCREENGRID_ROWS as usize {
|
||||
if !self.is_row_empty(row_index) {
|
||||
let mut buf = String::new();
|
||||
let (first, last) = self.get_write_interval(row_index);
|
||||
for sym in self.chars[row_index][first..=last].iter() {
|
||||
buf.push_str(&format!("{:04X},", sym.sym));
|
||||
}
|
||||
debug!("{}", buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if TV screen has no text
|
||||
///
|
||||
/// If any text is found then 708 counter is incremented
|
||||
pub fn is_screen_empty(&self, writer: &mut Writer) -> bool {
|
||||
for index in 0..CCX_DTVCC_SCREENGRID_ROWS as usize {
|
||||
if !self.is_row_empty(index) {
|
||||
// we will write subtitle
|
||||
*writer.cea_708_counter += 1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns `true` if row has no text
|
||||
pub fn is_row_empty(&self, row_index: usize) -> bool {
|
||||
for col_index in 0..CCX_DTVCC_SCREENGRID_COLUMNS as usize {
|
||||
if self.chars[row_index][col_index].is_set() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Add underline(<u>) and italic(<i>) tags according to the pen attributes
|
||||
///
|
||||
/// Open specifies if tag is an opening or closing tag
|
||||
pub fn change_pen_attribs(
|
||||
&self,
|
||||
pen_attribs: &dtvcc_pen_attribs,
|
||||
no_font_color: bool,
|
||||
row_index: usize,
|
||||
col_index: usize,
|
||||
open: bool,
|
||||
buf: &mut Vec<u8>,
|
||||
) {
|
||||
if no_font_color {
|
||||
return;
|
||||
}
|
||||
let new_pen_attribs = if col_index >= CCX_DTVCC_SCREENGRID_COLUMNS as usize {
|
||||
dtvcc_pen_attribs::default()
|
||||
} else {
|
||||
self.pen_attribs[row_index][col_index]
|
||||
};
|
||||
|
||||
if pen_attribs.italic != new_pen_attribs.italic {
|
||||
if is_true(pen_attribs.italic) && !open {
|
||||
buf.extend_from_slice(b"</i>");
|
||||
} else if is_false(pen_attribs.italic) && open {
|
||||
buf.extend_from_slice(b"<i>");
|
||||
}
|
||||
}
|
||||
if pen_attribs.underline != new_pen_attribs.underline {
|
||||
if is_true(pen_attribs.underline) && !open {
|
||||
buf.extend_from_slice(b"</u>");
|
||||
} else if is_false(pen_attribs.underline) && open {
|
||||
buf.extend_from_slice(b"<u>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add font(<font color="xxx">) tag according to the pen color
|
||||
///
|
||||
/// Open specifies if tag is an opening or closing tag
|
||||
pub fn change_pen_color(
|
||||
&self,
|
||||
pen_color: &dtvcc_pen_color,
|
||||
no_font_color: bool,
|
||||
row_index: usize,
|
||||
col_index: usize,
|
||||
open: bool,
|
||||
buf: &mut Vec<u8>,
|
||||
) {
|
||||
if no_font_color {
|
||||
return;
|
||||
}
|
||||
let new_pen_color = if col_index >= CCX_DTVCC_SCREENGRID_COLUMNS as usize {
|
||||
dtvcc_pen_color::default()
|
||||
} else {
|
||||
self.pen_colors[row_index][col_index]
|
||||
};
|
||||
if pen_color.fg_color != new_pen_color.fg_color {
|
||||
if pen_color.fg_color != 0x3F && !open {
|
||||
// should close older non-white color
|
||||
buf.extend_from_slice(b"</font>");
|
||||
} else if new_pen_color.fg_color != 0x3F && open {
|
||||
debug!("Colors: {}", col_index);
|
||||
let (mut red, mut green, mut blue) = color_to_hex(new_pen_color.fg_color as u8);
|
||||
red *= 255 / 3;
|
||||
green *= 255 / 3;
|
||||
blue *= 255 / 3;
|
||||
let font_tag = format!("<font color=\"{:02x}{:02x}{:02x}\">", red, green, blue);
|
||||
buf.extend_from_slice(font_tag.as_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,224 @@
|
||||
use crate::bindings::*;
|
||||
//! Caption windows
|
||||
//!
|
||||
//! Refer Section 8.4 CEA-708-E
|
||||
//!
|
||||
//! All caption text is displayed and manipulated in the context of caption windows. There are 8
|
||||
//! possible windows per service in which caption providers may write caption text.
|
||||
//! Each of the eight windows is uniquely addressed by its window ID. Window ID
|
||||
//! numbers range from 0 to 7.
|
||||
//!
|
||||
//! At any time there is a current window to which all subsequent
|
||||
//! window/pen commands are directed. All caption text is written to the current window
|
||||
|
||||
use std::{
|
||||
alloc::{alloc_zeroed, dealloc, Layout},
|
||||
intrinsics::copy_nonoverlapping,
|
||||
};
|
||||
|
||||
use super::timing::get_time_str;
|
||||
use super::{
|
||||
CCX_DTVCC_MAX_COLUMNS, CCX_DTVCC_MAX_ROWS, CCX_DTVCC_SCREENGRID_COLUMNS,
|
||||
CCX_DTVCC_SCREENGRID_ROWS,
|
||||
};
|
||||
use crate::{bindings::*, utils::is_true};
|
||||
|
||||
use log::{debug, error};
|
||||
|
||||
impl dtvcc_window {
|
||||
/// Sets the window style according to the window preset
|
||||
pub fn set_style(&mut self, preset: WindowPreset) {
|
||||
let style_id = preset as i32;
|
||||
let window_style = WindowStyle::new(preset);
|
||||
self.win_style = style_id;
|
||||
self.attribs.border_color = window_style.border_color as i32;
|
||||
self.attribs.border_type = window_style.border_type as i32;
|
||||
self.attribs.display_effect = window_style.display_effect as i32;
|
||||
self.attribs.effect_direction = window_style.effect_direction as i32;
|
||||
self.attribs.effect_speed = window_style.effect_speed as i32;
|
||||
self.attribs.fill_color = window_style.fill_color as i32;
|
||||
self.attribs.fill_opacity = window_style.fill_opacity as i32;
|
||||
self.attribs.justify = window_style.justify as i32;
|
||||
self.attribs.print_direction = window_style.print_direction as i32;
|
||||
self.attribs.scroll_direction = window_style.scroll_direction as i32;
|
||||
self.attribs.word_wrap = window_style.word_wrap as i32;
|
||||
}
|
||||
/// Sets the pen style according to the pen preset
|
||||
pub fn set_pen_style(&mut self, preset: PenPreset) {
|
||||
let pen_style = PenStyle::new(preset);
|
||||
let pen = &mut self.pen_attribs_pattern;
|
||||
pen.pen_size = pen_style.pen_size as i32;
|
||||
pen.offset = pen_style.offset as i32;
|
||||
pen.edge_type = pen_style.edge_type as i32;
|
||||
pen.underline = pen_style.underline as i32;
|
||||
pen.italic = pen_style.italics as i32;
|
||||
|
||||
let pen_color = &mut self.pen_color_pattern;
|
||||
pen_color.fg_color = pen_style.color.fg_color as i32;
|
||||
pen_color.fg_opacity = pen_style.color.fg_opacity as i32;
|
||||
pen_color.bg_color = pen_style.color.bg_color as i32;
|
||||
pen_color.bg_opacity = pen_style.color.bg_opacity as i32;
|
||||
pen_color.edge_color = pen_style.color.edge_color as i32;
|
||||
}
|
||||
/// Update the show time for the window
|
||||
pub fn update_time_show(&mut self, timing: &mut ccx_common_timing_ctx) {
|
||||
self.time_ms_show = timing.get_visible_start(3);
|
||||
let time = get_time_str(self.time_ms_show);
|
||||
debug!("[W-{}] show time updated to {}", self.number, time);
|
||||
}
|
||||
/// Update the hide time for the window
|
||||
pub fn update_time_hide(&mut self, timing: &mut ccx_common_timing_ctx) {
|
||||
self.time_ms_hide = timing.get_visible_end(3);
|
||||
let time = get_time_str(self.time_ms_hide);
|
||||
debug!("[W-{}] hide time updated to {}", self.number, time);
|
||||
}
|
||||
/// Get dimensions of the window
|
||||
///
|
||||
/// The dimensions of a unique window specify an area on the screen which may contain caption text.
|
||||
pub fn get_dimensions(&self) -> Result<(i32, i32, i32, i32), String> {
|
||||
let anchor = dtvcc_pen_anchor_point::new(self.anchor_point)?;
|
||||
let (mut x1, mut x2, mut y1, mut y2) = match anchor {
|
||||
dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_TOP_LEFT => (
|
||||
self.anchor_vertical,
|
||||
self.anchor_vertical + self.row_count,
|
||||
self.anchor_horizontal,
|
||||
self.anchor_horizontal + self.col_count,
|
||||
),
|
||||
dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_TOP_CENTER => (
|
||||
self.anchor_vertical,
|
||||
self.anchor_vertical + self.row_count,
|
||||
self.anchor_horizontal - self.col_count,
|
||||
self.anchor_horizontal + self.col_count / 2,
|
||||
),
|
||||
dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_TOP_RIGHT => (
|
||||
self.anchor_vertical,
|
||||
self.anchor_vertical + self.row_count,
|
||||
self.anchor_horizontal - self.col_count,
|
||||
self.anchor_horizontal,
|
||||
),
|
||||
dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_MIDDLE_LEFT => (
|
||||
self.anchor_vertical - self.row_count / 2,
|
||||
self.anchor_vertical + self.row_count / 2,
|
||||
self.anchor_horizontal,
|
||||
self.anchor_horizontal + self.col_count,
|
||||
),
|
||||
dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_MIDDLE_CENTER => (
|
||||
self.anchor_vertical - self.row_count / 2,
|
||||
self.anchor_vertical + self.row_count / 2,
|
||||
self.anchor_horizontal - self.col_count / 2,
|
||||
self.anchor_horizontal + self.col_count / 2,
|
||||
),
|
||||
dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_MIDDLE_RIGHT => (
|
||||
self.anchor_vertical - self.row_count / 2,
|
||||
self.anchor_vertical + self.row_count / 2,
|
||||
self.anchor_horizontal - self.col_count,
|
||||
self.anchor_horizontal,
|
||||
),
|
||||
dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_BOTTOM_LEFT => (
|
||||
self.anchor_vertical - self.row_count,
|
||||
self.anchor_vertical,
|
||||
self.anchor_horizontal,
|
||||
self.anchor_horizontal + self.col_count,
|
||||
),
|
||||
dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_BOTTOM_CENTER => (
|
||||
self.anchor_vertical - self.row_count,
|
||||
self.anchor_vertical,
|
||||
self.anchor_horizontal - self.col_count / 2,
|
||||
self.anchor_horizontal + self.col_count / 2,
|
||||
),
|
||||
dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_BOTTOM_RIGHT => (
|
||||
self.anchor_vertical - self.row_count,
|
||||
self.anchor_vertical,
|
||||
self.anchor_horizontal - self.col_count,
|
||||
self.anchor_horizontal,
|
||||
),
|
||||
};
|
||||
if x1 < 0 {
|
||||
x1 = 0
|
||||
}
|
||||
if y1 < 0 {
|
||||
y1 = 0
|
||||
}
|
||||
if x2 > CCX_DTVCC_SCREENGRID_ROWS as i32 {
|
||||
x2 = CCX_DTVCC_SCREENGRID_ROWS as i32
|
||||
}
|
||||
if y2 > CCX_DTVCC_SCREENGRID_COLUMNS as i32 {
|
||||
y2 = CCX_DTVCC_SCREENGRID_COLUMNS as i32
|
||||
}
|
||||
Ok((x1, x2, y1, y2))
|
||||
}
|
||||
/// Clear all text from the window
|
||||
pub fn clear_text(&mut self) {
|
||||
// Set pen color to default value
|
||||
self.pen_color_pattern = dtvcc_pen_color::default();
|
||||
// Set pen attributes to default value
|
||||
self.pen_attribs_pattern = dtvcc_pen_attribs::default();
|
||||
for row in 0..CCX_DTVCC_MAX_ROWS as usize {
|
||||
self.clear_row(row);
|
||||
}
|
||||
self.is_empty = 1;
|
||||
}
|
||||
/// Clear text from the selected row
|
||||
pub fn clear_row(&mut self, row_index: usize) {
|
||||
if is_true(self.memory_reserved) {
|
||||
unsafe {
|
||||
let layout = Layout::array::<dtvcc_symbol>(CCX_DTVCC_MAX_COLUMNS as usize);
|
||||
if let Err(e) = layout {
|
||||
error!("clear_row: Incorrect Layout, {}", e);
|
||||
} else {
|
||||
let layout = layout.unwrap();
|
||||
// deallocate previous memory
|
||||
dealloc(self.rows[row_index] as *mut u8, layout);
|
||||
|
||||
// allocate new zero initialized memory
|
||||
let ptr = alloc_zeroed(layout);
|
||||
if ptr.is_null() {
|
||||
error!("clear_row: Not enough memory",);
|
||||
}
|
||||
self.rows[row_index] = ptr as *mut dtvcc_symbol;
|
||||
}
|
||||
}
|
||||
for col in 0..CCX_DTVCC_MAX_COLUMNS as usize {
|
||||
// Set pen attributes to default value
|
||||
self.pen_attribs[row_index][col] = dtvcc_pen_attribs {
|
||||
pen_size: dtvcc_pen_size::DTVCC_PEN_SIZE_STANDART as i32,
|
||||
offset: 0,
|
||||
text_tag: dtvcc_pen_text_tag::DTVCC_PEN_TEXT_TAG_UNDEFINED_12 as i32,
|
||||
font_tag: 0,
|
||||
edge_type: dtvcc_pen_edge::DTVCC_PEN_EDGE_NONE as i32,
|
||||
underline: 0,
|
||||
italic: 0,
|
||||
};
|
||||
// Set pen color to default value
|
||||
self.pen_colors[row_index][col] = dtvcc_pen_color {
|
||||
fg_color: 0x3F,
|
||||
fg_opacity: 0,
|
||||
bg_color: 0,
|
||||
bg_opacity: 0,
|
||||
edge_color: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Roll-up the captions
|
||||
///
|
||||
/// Scroll all the rows above by 1 to achieve the rollup effect
|
||||
pub fn rollup(&mut self) {
|
||||
debug!("roller");
|
||||
for row_index in 0..(self.row_count - 1) as usize {
|
||||
let curr_row = self.rows[row_index];
|
||||
let next_row = self.rows[row_index + 1] as *const dtvcc_symbol;
|
||||
unsafe { copy_nonoverlapping(next_row, curr_row, CCX_DTVCC_MAX_COLUMNS as usize) };
|
||||
for col_index in 0..CCX_DTVCC_MAX_COLUMNS as usize {
|
||||
self.pen_colors[row_index][col_index] = self.pen_colors[row_index + 1][col_index];
|
||||
self.pen_attribs[row_index][col_index] = self.pen_attribs[row_index + 1][col_index];
|
||||
}
|
||||
}
|
||||
self.clear_row((self.row_count - 1) as usize);
|
||||
}
|
||||
}
|
||||
|
||||
impl dtvcc_window_pd {
|
||||
/// Create new window direction
|
||||
pub fn new(direction: i32) -> Result<Self, String> {
|
||||
match direction {
|
||||
0 => Ok(dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT),
|
||||
@@ -11,3 +229,322 @@ impl dtvcc_window_pd {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl dtvcc_pen_anchor_point {
|
||||
/// Create new pen anchor point
|
||||
pub fn new(anchor: i32) -> Result<Self, String> {
|
||||
match anchor {
|
||||
0 => Ok(dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_TOP_LEFT),
|
||||
1 => Ok(dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_TOP_CENTER),
|
||||
2 => Ok(dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_TOP_RIGHT),
|
||||
3 => Ok(dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_MIDDLE_LEFT),
|
||||
4 => Ok(dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_MIDDLE_CENTER),
|
||||
5 => Ok(dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_MIDDLE_RIGHT),
|
||||
6 => Ok(dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_BOTTOM_LEFT),
|
||||
7 => Ok(dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_BOTTOM_CENTER),
|
||||
8 => Ok(dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_BOTTOM_RIGHT),
|
||||
_ => Err(String::from("Invalid pen anchor")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Window style for a specific window preset
|
||||
struct WindowStyle {
|
||||
justify: dtvcc_window_justify,
|
||||
print_direction: dtvcc_window_pd,
|
||||
scroll_direction: dtvcc_window_sd,
|
||||
word_wrap: u8,
|
||||
display_effect: dtvcc_window_sde,
|
||||
effect_direction: u8,
|
||||
effect_speed: u8,
|
||||
fill_color: u8,
|
||||
fill_opacity: dtvcc_window_fo,
|
||||
border_type: dtvcc_window_border,
|
||||
border_color: u8,
|
||||
}
|
||||
|
||||
impl WindowStyle {
|
||||
/// Create new window style using a window preset
|
||||
pub fn new(preset: WindowPreset) -> Self {
|
||||
// All styles have these common attributes
|
||||
let effect_direction = 0;
|
||||
let effect_speed = 0;
|
||||
let fill_color = 0;
|
||||
let border_color = 0;
|
||||
let display_effect = dtvcc_window_sde::DTVCC_WINDOW_SDE_SNAP;
|
||||
let border_type = dtvcc_window_border::DTVCC_WINDOW_BORDER_NONE;
|
||||
|
||||
let (justify, pd, sd, word_wrap, fill_opacity) = match preset {
|
||||
WindowPreset::NtscPopup => (
|
||||
dtvcc_window_justify::DTVCC_WINDOW_JUSTIFY_LEFT,
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT,
|
||||
dtvcc_window_sd::DTVCC_WINDOW_SD_BOTTOM_TOP,
|
||||
0,
|
||||
dtvcc_window_fo::DTVCC_WINDOW_FO_SOLID,
|
||||
),
|
||||
WindowPreset::Popup => (
|
||||
dtvcc_window_justify::DTVCC_WINDOW_JUSTIFY_LEFT,
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT,
|
||||
dtvcc_window_sd::DTVCC_WINDOW_SD_BOTTOM_TOP,
|
||||
0,
|
||||
dtvcc_window_fo::DTVCC_WINDOW_FO_TRANSPARENT,
|
||||
),
|
||||
WindowPreset::NtscCenteredPopup => (
|
||||
dtvcc_window_justify::DTVCC_WINDOW_JUSTIFY_CENTER,
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT,
|
||||
dtvcc_window_sd::DTVCC_WINDOW_SD_BOTTOM_TOP,
|
||||
0,
|
||||
dtvcc_window_fo::DTVCC_WINDOW_FO_SOLID,
|
||||
),
|
||||
WindowPreset::NtscRollup => (
|
||||
dtvcc_window_justify::DTVCC_WINDOW_JUSTIFY_LEFT,
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT,
|
||||
dtvcc_window_sd::DTVCC_WINDOW_SD_BOTTOM_TOP,
|
||||
1,
|
||||
dtvcc_window_fo::DTVCC_WINDOW_FO_SOLID,
|
||||
),
|
||||
WindowPreset::Rollup => (
|
||||
dtvcc_window_justify::DTVCC_WINDOW_JUSTIFY_LEFT,
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT,
|
||||
dtvcc_window_sd::DTVCC_WINDOW_SD_BOTTOM_TOP,
|
||||
1,
|
||||
dtvcc_window_fo::DTVCC_WINDOW_FO_TRANSPARENT,
|
||||
),
|
||||
WindowPreset::NtscCenteredRollup => (
|
||||
dtvcc_window_justify::DTVCC_WINDOW_JUSTIFY_CENTER,
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT,
|
||||
dtvcc_window_sd::DTVCC_WINDOW_SD_BOTTOM_TOP,
|
||||
1,
|
||||
dtvcc_window_fo::DTVCC_WINDOW_FO_SOLID,
|
||||
),
|
||||
WindowPreset::TickerTape => (
|
||||
dtvcc_window_justify::DTVCC_WINDOW_JUSTIFY_LEFT,
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM,
|
||||
dtvcc_window_sd::DTVCC_WINDOW_SD_RIGHT_LEFT,
|
||||
0,
|
||||
dtvcc_window_fo::DTVCC_WINDOW_FO_SOLID,
|
||||
),
|
||||
};
|
||||
Self {
|
||||
justify,
|
||||
print_direction: pd,
|
||||
scroll_direction: sd,
|
||||
word_wrap,
|
||||
display_effect,
|
||||
effect_direction,
|
||||
effect_speed,
|
||||
fill_color,
|
||||
fill_opacity,
|
||||
border_type,
|
||||
border_color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Predefined window style ids
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum WindowPreset {
|
||||
/// #1 NTSC Style Popup Captions
|
||||
NtscPopup = 1,
|
||||
/// #2 Popup Captions w/o Black Background
|
||||
Popup = 2,
|
||||
/// #3 NTSC Style Centered Popup Captions
|
||||
NtscCenteredPopup = 3,
|
||||
/// #4 NTSC Style Rollup Captions
|
||||
NtscRollup = 4,
|
||||
/// #4 Rollup Captions w/o Black Background
|
||||
Rollup = 5,
|
||||
/// #6 NTSC Style Centered Rollup Captions
|
||||
NtscCenteredRollup = 6,
|
||||
/// #7 Ticker Tape
|
||||
TickerTape = 7,
|
||||
}
|
||||
|
||||
impl WindowPreset {
|
||||
/// Returns a 'WindowPreset' according to the style id
|
||||
pub fn get_style(style_id: u8) -> Result<Self, String> {
|
||||
match style_id {
|
||||
1 => Ok(WindowPreset::NtscPopup),
|
||||
2 => Ok(WindowPreset::Popup),
|
||||
3 => Ok(WindowPreset::NtscCenteredPopup),
|
||||
4 => Ok(WindowPreset::NtscRollup),
|
||||
5 => Ok(WindowPreset::Rollup),
|
||||
6 => Ok(WindowPreset::NtscCenteredRollup),
|
||||
7 => Ok(WindowPreset::TickerTape),
|
||||
_ => Err("Invalid style".to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Predefined pen style ids
|
||||
#[derive(PartialEq)]
|
||||
pub enum PenPreset {
|
||||
/// #1 Default NTSC Style
|
||||
NtscStyle,
|
||||
/// #2 NTSC Style Mono w/ Serif
|
||||
NtscStyleMonoSerif,
|
||||
/// #3 NTSC Style Prop w/ Serif
|
||||
NtscStylePropSerif,
|
||||
/// #4 NTSC Style Mono w/o Serif
|
||||
NtscStyleMono,
|
||||
/// #5 NTSC Style Prop w/o Serif
|
||||
NtscStyleProp,
|
||||
/// #6 Mono w/o Serif, Bordered Text, No bg
|
||||
MonoBordered,
|
||||
/// #7 Prop w/o Serif, Bordered Text, No bg
|
||||
PropBordered,
|
||||
}
|
||||
|
||||
impl PenPreset {
|
||||
/// Returns a 'PenPreset' according to the style id
|
||||
pub fn get_style(style_id: u8) -> Result<Self, String> {
|
||||
match style_id {
|
||||
1 => Ok(PenPreset::NtscStyle),
|
||||
2 => Ok(PenPreset::NtscStyleMonoSerif),
|
||||
3 => Ok(PenPreset::NtscStylePropSerif),
|
||||
4 => Ok(PenPreset::NtscStyleMono),
|
||||
5 => Ok(PenPreset::NtscStyleProp),
|
||||
6 => Ok(PenPreset::MonoBordered),
|
||||
7 => Ok(PenPreset::PropBordered),
|
||||
_ => Err("Invalid style".to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pen style for a specific pen preset
|
||||
pub struct PenStyle {
|
||||
/// always standard pen size
|
||||
pen_size: dtvcc_pen_size,
|
||||
/// Font style, ranged from 1-7
|
||||
/// Not being used current in the C code(bindings)
|
||||
_font_style: dtvcc_pen_font_style,
|
||||
offset: dtvcc_pen_offset,
|
||||
/// always no, i.e. 0
|
||||
italics: u8,
|
||||
/// always no, i.e. 0
|
||||
underline: u8,
|
||||
edge_type: dtvcc_pen_edge,
|
||||
color: PenColor,
|
||||
}
|
||||
|
||||
impl PenStyle {
|
||||
/// Create new pen style using a pen preset
|
||||
pub fn new(preset: PenPreset) -> Self {
|
||||
// All styles have these common attributes
|
||||
let pen_size = dtvcc_pen_size::DTVCC_PEN_SIZE_STANDART;
|
||||
let offset = dtvcc_pen_offset::DTVCC_PEN_OFFSET_NORMAL;
|
||||
let italics = 0;
|
||||
let underline = 0;
|
||||
let bg_opacity = match preset {
|
||||
PenPreset::MonoBordered | PenPreset::PropBordered => Opacity::Transparent,
|
||||
_ => Opacity::Solid,
|
||||
};
|
||||
|
||||
let color = PenColor {
|
||||
/// White(2,2,2) i.e 10,10,10 i.e 42
|
||||
fg_color: 42,
|
||||
fg_opacity: Opacity::Solid,
|
||||
/// Either N/A or black, still always 0
|
||||
bg_color: 0,
|
||||
bg_opacity,
|
||||
/// Either N/A or black, still always 0
|
||||
edge_color: 0,
|
||||
};
|
||||
|
||||
let (font_style, edge_type) = match preset {
|
||||
PenPreset::NtscStyle => (
|
||||
dtvcc_pen_font_style::DTVCC_PEN_FONT_STYLE_DEFAULT_OR_UNDEFINED,
|
||||
dtvcc_pen_edge::DTVCC_PEN_EDGE_UNIFORM,
|
||||
),
|
||||
PenPreset::NtscStyleMonoSerif => (
|
||||
dtvcc_pen_font_style::DTVCC_PEN_FONT_STYLE_MONOSPACED_WITH_SERIFS,
|
||||
dtvcc_pen_edge::DTVCC_PEN_EDGE_UNIFORM,
|
||||
),
|
||||
PenPreset::NtscStylePropSerif => (
|
||||
dtvcc_pen_font_style::DTVCC_PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITH_SERIFS,
|
||||
dtvcc_pen_edge::DTVCC_PEN_EDGE_UNIFORM,
|
||||
),
|
||||
PenPreset::NtscStyleMono => (
|
||||
dtvcc_pen_font_style::DTVCC_PEN_FONT_STYLE_MONOSPACED_WITHOUT_SERIFS,
|
||||
dtvcc_pen_edge::DTVCC_PEN_EDGE_UNIFORM,
|
||||
),
|
||||
PenPreset::NtscStyleProp => (
|
||||
dtvcc_pen_font_style::DTVCC_PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITHOUT_SERIFS,
|
||||
dtvcc_pen_edge::DTVCC_PEN_EDGE_UNIFORM,
|
||||
),
|
||||
PenPreset::MonoBordered => (
|
||||
dtvcc_pen_font_style::DTVCC_PEN_FONT_STYLE_MONOSPACED_WITHOUT_SERIFS,
|
||||
dtvcc_pen_edge::DTVCC_PEN_EDGE_UNIFORM,
|
||||
),
|
||||
PenPreset::PropBordered => (
|
||||
dtvcc_pen_font_style::DTVCC_PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITHOUT_SERIFS,
|
||||
dtvcc_pen_edge::DTVCC_PEN_EDGE_UNIFORM,
|
||||
),
|
||||
};
|
||||
|
||||
Self {
|
||||
pen_size,
|
||||
_font_style: font_style,
|
||||
offset,
|
||||
italics,
|
||||
underline,
|
||||
edge_type,
|
||||
color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pen Color attributes
|
||||
///
|
||||
/// Set by the SPC(SetPenColor) command. Refer Section 8.10.5.10 CEA-708-E
|
||||
///
|
||||
/// Text written to the current window will have the color attributes specified by
|
||||
/// the most recent SetPenColor command written to the window.
|
||||
struct PenColor {
|
||||
/// Color of text forground body
|
||||
fg_color: u8,
|
||||
/// Opacity of text foreground body
|
||||
fg_opacity: Opacity,
|
||||
/// Color of background box surrounding the text
|
||||
bg_color: u8,
|
||||
/// Opacity of background box surrounding the text
|
||||
bg_opacity: Opacity,
|
||||
/// Color of the outlined edges of text
|
||||
edge_color: u8,
|
||||
}
|
||||
|
||||
/// Opacity of the window/pen colors
|
||||
enum Opacity {
|
||||
Solid = 0,
|
||||
_Flash = 1,
|
||||
_Translucent = 2,
|
||||
Transparent = 3,
|
||||
}
|
||||
|
||||
impl Default for dtvcc_pen_color {
|
||||
/// Returns the default pen color
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fg_color: 0x3F,
|
||||
fg_opacity: 0,
|
||||
bg_color: 0,
|
||||
bg_opacity: 0,
|
||||
edge_color: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for dtvcc_pen_attribs {
|
||||
/// Returns the default pen attributes
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pen_size: dtvcc_pen_size::DTVCC_PEN_SIZE_STANDART as i32,
|
||||
offset: 0,
|
||||
text_tag: dtvcc_pen_text_tag::DTVCC_PEN_TEXT_TAG_UNDEFINED_12 as i32,
|
||||
font_tag: 0,
|
||||
edge_type: dtvcc_pen_edge::DTVCC_PEN_EDGE_NONE as i32,
|
||||
underline: 0,
|
||||
italic: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,25 +7,32 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use decoder::Dtvcc;
|
||||
use env_logger::{builder, Target};
|
||||
use log::{warn, LevelFilter};
|
||||
use std::{io::Write, os::raw::c_int};
|
||||
|
||||
/// CCExtractor C bindings generated by bindgen
|
||||
#[allow(clippy::all)]
|
||||
pub mod bindings {
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
}
|
||||
|
||||
pub mod decoder;
|
||||
pub mod utils;
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::{FromRawHandle, RawHandle};
|
||||
use std::{io::Write, os::raw::c_int};
|
||||
|
||||
use bindings::*;
|
||||
use decoder::Dtvcc;
|
||||
use utils::is_true;
|
||||
|
||||
use env_logger::{builder, Target};
|
||||
use log::{warn, LevelFilter};
|
||||
|
||||
extern "C" {
|
||||
static mut cb_708: c_int;
|
||||
static mut cb_field1: c_int;
|
||||
static mut cb_field2: c_int;
|
||||
}
|
||||
|
||||
/// Initialize env logger
|
||||
/// Initialize env logger with custom format, using stdout as target
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ccxr_init_logger() {
|
||||
builder()
|
||||
@@ -54,7 +61,7 @@ extern "C" fn ccxr_process_cc_data(
|
||||
let dtvcc_ctx = unsafe { &mut *dec_ctx.dtvcc };
|
||||
let mut dtvcc = Dtvcc::new(dtvcc_ctx);
|
||||
for cc_block in cc_data.chunks_exact_mut(3) {
|
||||
if validate_cc_pair(cc_block) {
|
||||
if !validate_cc_pair(cc_block) {
|
||||
continue;
|
||||
}
|
||||
let success = do_cb(dec_ctx, &mut dtvcc, cc_block);
|
||||
@@ -65,21 +72,49 @@ extern "C" fn ccxr_process_cc_data(
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn validate_cc_pair(cc_block: &[u8]) -> bool {
|
||||
/// Returns `true` if cc_block pair is valid
|
||||
///
|
||||
/// For CEA-708 data, only cc_valid is checked
|
||||
/// For CEA-608 data, parity is also checked
|
||||
pub fn validate_cc_pair(cc_block: &mut [u8]) -> bool {
|
||||
let cc_valid = (cc_block[0] & 4) >> 2;
|
||||
let cc_type = cc_block[0] & 3;
|
||||
if cc_valid == 0 {
|
||||
return false;
|
||||
}
|
||||
if cc_type == 0 || cc_type == 1 {
|
||||
// For CEA-608 data we verify parity.
|
||||
if verify_parity(cc_block[2]) {
|
||||
// If the second byte doesn't pass parity, ignore pair
|
||||
return false;
|
||||
}
|
||||
if verify_parity(cc_block[1]) {
|
||||
// If the first byte doesn't pass parity,
|
||||
// we replace it with a solid blank and process the pair.
|
||||
cc_block[1] = 0x7F;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns `true` if data has odd parity
|
||||
///
|
||||
/// CC uses odd parity (i.e., # of 1's in byte is odd.)
|
||||
pub fn verify_parity(data: u8) -> bool {
|
||||
if data.count_ones() & 1 == 1 {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Process CC data according to its type
|
||||
pub fn do_cb(ctx: &mut lib_cc_decode, dtvcc: &mut Dtvcc, cc_block: &[u8]) -> bool {
|
||||
let cc_valid = (cc_block[0] & 4) >> 2;
|
||||
let cc_type = cc_block[0] & 3;
|
||||
let mut timeok = true;
|
||||
|
||||
if ctx.write_format != ccx_output_format_CCX_OF_DVDRAW
|
||||
&& ctx.write_format != ccx_output_format_CCX_OF_RAW
|
||||
if ctx.write_format != ccx_output_format::CCX_OF_DVDRAW
|
||||
&& ctx.write_format != ccx_output_format::CCX_OF_RAW
|
||||
&& (cc_block[0] == 0xFA || cc_block[0] == 0xFC || cc_block[0] == 0xFD)
|
||||
&& (cc_block[1] & 0x7F) == 0
|
||||
&& (cc_block[2] & 0x7F) == 0
|
||||
@@ -90,20 +125,25 @@ pub fn do_cb(ctx: &mut lib_cc_decode, dtvcc: &mut Dtvcc, cc_block: &[u8]) -> boo
|
||||
if cc_valid == 1 || cc_type == 3 {
|
||||
ctx.cc_stats[cc_type as usize] += 1;
|
||||
match cc_type {
|
||||
// Type 0 and 1 are for CEA-608 data. Handled by C code, do nothing
|
||||
0 | 1 => {}
|
||||
// Type 2 and 3 are for CEA-708 data.
|
||||
2 | 3 => {
|
||||
let current_time = unsafe { get_fts(ctx.timing, ctx.current_field) };
|
||||
let current_time = unsafe { (*ctx.timing).get_fts(ctx.current_field as u8) };
|
||||
ctx.current_field = 3;
|
||||
if ctx.extraction_start.set == 1 && current_time < ctx.extraction_start.time_in_ms {
|
||||
|
||||
// Check whether current time is within start and end bounds
|
||||
if is_true(ctx.extraction_start.set)
|
||||
&& current_time < ctx.extraction_start.time_in_ms
|
||||
{
|
||||
timeok = false;
|
||||
}
|
||||
|
||||
if ctx.extraction_end.set == 1 && current_time > ctx.extraction_end.time_in_ms {
|
||||
if is_true(ctx.extraction_end.set) && current_time > ctx.extraction_end.time_in_ms {
|
||||
timeok = false;
|
||||
ctx.processed_enough = 1;
|
||||
}
|
||||
|
||||
if timeok && ctx.write_format != ccx_output_format_CCX_OF_RAW {
|
||||
if timeok && ctx.write_format != ccx_output_format::CCX_OF_RAW {
|
||||
dtvcc.process_cc_data(cc_valid, cc_type, cc_block[1], cc_block[2]);
|
||||
}
|
||||
unsafe { cb_708 += 1 }
|
||||
@@ -113,3 +153,17 @@ pub fn do_cb(ctx: &mut lib_cc_decode, dtvcc: &mut Dtvcc, cc_block: &[u8]) -> boo
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[no_mangle]
|
||||
extern "C" fn ccxr_close_handle(handle: RawHandle) {
|
||||
use std::fs::File;
|
||||
|
||||
if handle.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
// File will close automatically (due to Drop) once it goes out of scope
|
||||
let file = from_raw_handle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
11
src/rust/src/utils.rs
Normal file
11
src/rust/src/utils.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//! Some utility functions to deal with values from C bindings
|
||||
|
||||
/// Check if the value is true (Set to 1). Use only for values from C bindings
|
||||
pub fn is_true<T: Into<i32>>(val: T) -> bool {
|
||||
val.into() == 1
|
||||
}
|
||||
|
||||
/// Check if the value is false (Set to 0). Use only for values from C bindings
|
||||
pub fn is_false<T: Into<i32>>(val: T) -> bool {
|
||||
val.into() == 0
|
||||
}
|
||||
@@ -2,5 +2,6 @@
|
||||
#include "../lib_ccx/ccx_decoders_common.h"
|
||||
#include "../lib_ccx/ccx_dtvcc.h"
|
||||
#include "../lib_ccx/ccx_decoders_708_output.h"
|
||||
#include "../lib_ccx/ccx_decoders_708_encoding.h"
|
||||
#include "../lib_ccx/ccx_common_timing.h"
|
||||
#include "../lib_ccx/lib_ccx.h"
|
||||
@@ -122,6 +122,9 @@
|
||||
<Component Guid="{1B37F14A-3BA6-4837-8A6F-6EA01A25DA26}">
|
||||
<File Source="./installer/file_selector_windows_plugin.dll" KeyPath="yes"/>
|
||||
</Component>
|
||||
<Component Guid="{B276C96D-9737-4B8C-B55B-60F392DED331}">
|
||||
<File Source="./installer/url_launcher_windows_plugin.dll" KeyPath="yes"/>
|
||||
</Component>
|
||||
<Component Guid="{4B627AA9-55DD-40ED-99F9-54F67EC73887}">
|
||||
<File Source="./installer/flutter_windows.dll" KeyPath="yes"/>
|
||||
</Component>
|
||||
|
||||
Reference in New Issue
Block a user