mirror of
https://github.com/CCExtractor/ccextractor.git
synced 2026-02-14 05:25:44 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7ebd45d9f | ||
|
|
77abe01885 | ||
|
|
98cec31516 | ||
|
|
46b145a396 |
@@ -4,7 +4,7 @@ MAINTAINER = Marc Espie <espie@openbsd.org>
|
||||
CATEGORIES = multimedia
|
||||
COMMENT = closed caption subtitles extractor
|
||||
HOMEPAGE = https://ccextractor.org
|
||||
V = 0.92
|
||||
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,8 @@
|
||||
0.93 (2021-08-16)
|
||||
-----------------
|
||||
- Minor Rust updates (format, typos, docs)
|
||||
- Updated GUI
|
||||
|
||||
0.92 (2021-08-10)
|
||||
-----------------
|
||||
- Rust updates: Added srt writer
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([CCExtractor], [0.92], [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.92], [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.92
|
||||
pkgver=0.93
|
||||
pkgrel=1
|
||||
pkgdesc="A closed captions and teletext subtitles extractor for video streams."
|
||||
arch=('i686' 'x86_64')
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef CCX_CCEXTRACTOR_H
|
||||
#define CCX_CCEXTRACTOR_H
|
||||
|
||||
#define VERSION "0.92"
|
||||
#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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! 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;
|
||||
@@ -20,7 +20,7 @@ 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,
|
||||
@@ -60,7 +60,7 @@ impl<'a> Dtvcc<'a> {
|
||||
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;
|
||||
@@ -86,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);
|
||||
}
|
||||
@@ -109,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;
|
||||
@@ -126,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
|
||||
{
|
||||
@@ -189,6 +193,10 @@ 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) -> Self {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Utilty functions to write captions
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::{FromRawHandle, IntoRawHandle, RawHandle};
|
||||
use std::{
|
||||
@@ -23,6 +25,7 @@ pub struct Writer<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Writer<'a> {
|
||||
/// Create a new writer context
|
||||
pub fn new(
|
||||
cea_708_counter: &'a mut u32,
|
||||
subs_delay: LLONG,
|
||||
@@ -43,6 +46,11 @@ impl<'a> Writer<'a> {
|
||||
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)]
|
||||
{
|
||||
@@ -60,6 +68,10 @@ impl<'a> Writer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
@@ -69,6 +81,12 @@ pub fn write_char(sym: &dtvcc_symbol, buf: &mut Vec<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;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
//! Caption Service Decoder
|
||||
//!
|
||||
//! Caption Service decoder processes service blocks and handles the different [commands][super::commands] received
|
||||
|
||||
use std::{
|
||||
alloc::{alloc, dealloc, Layout},
|
||||
os::raw::c_uchar,
|
||||
@@ -52,7 +56,10 @@ impl dtvcc_service_decoder {
|
||||
i += consumed as usize;
|
||||
}
|
||||
}
|
||||
/// Handle C0 - Code Set - Miscellaneous Control Codes
|
||||
|
||||
// -------------------------- C0 Commands-------------------------
|
||||
|
||||
/// C0 - Code Set - Miscellaneous Control Codes
|
||||
pub fn handle_C0(
|
||||
&mut self,
|
||||
block: &[u8],
|
||||
@@ -86,7 +93,179 @@ impl dtvcc_service_decoder {
|
||||
}
|
||||
length as i32
|
||||
}
|
||||
/// Handle C1 - Code Set - Captioning Command Control Codes
|
||||
|
||||
/// Process Carriage Return(CR)
|
||||
///
|
||||
/// Refer Section 7.1.4.1 and 8.4.9.2 CEA-708-E
|
||||
///
|
||||
/// Carriage Return (CR) moves the current entry point to the beginning of the next row. If the next row is
|
||||
/// below the visible window, the window “rolls up"
|
||||
pub fn process_cr(
|
||||
&mut self,
|
||||
encoder: &mut encoder_ctx,
|
||||
timing: &mut ccx_common_timing_ctx,
|
||||
no_rollup: bool,
|
||||
) {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_process_cr: Window has to be defined first");
|
||||
return;
|
||||
}
|
||||
let window = &mut self.windows[self.current_window as usize];
|
||||
let mut rollup_required = false;
|
||||
let pd = match dtvcc_window_pd::new(window.attribs.print_direction) {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
warn!("{}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
match pd {
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT => {
|
||||
window.pen_column = 0;
|
||||
if window.pen_row + 1 < window.row_count {
|
||||
window.pen_row += 1;
|
||||
} else {
|
||||
rollup_required = true;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_RIGHT_LEFT => {
|
||||
window.pen_column = window.col_count;
|
||||
if window.pen_row + 1 < window.row_count {
|
||||
window.pen_row += 1;
|
||||
} else {
|
||||
rollup_required = true;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM => {
|
||||
window.pen_row = 0;
|
||||
if window.pen_column + 1 < window.col_count {
|
||||
window.pen_column += 1;
|
||||
} else {
|
||||
rollup_required = true;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_BOTTOM_TOP => {
|
||||
window.pen_row = window.row_count;
|
||||
if window.pen_column + 1 < window.col_count {
|
||||
window.pen_column += 1;
|
||||
} else {
|
||||
rollup_required = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if is_true(window.is_defined) {
|
||||
debug!("dtvcc_process_cr: rolling uper");
|
||||
|
||||
let pen_row = window.pen_row;
|
||||
window.update_time_hide(timing);
|
||||
self.copy_to_screen(&self.windows[self.current_window as usize]);
|
||||
self.screen_print(encoder, timing);
|
||||
|
||||
if rollup_required {
|
||||
if no_rollup {
|
||||
self.windows[self.current_window as usize].clear_row(pen_row as usize);
|
||||
} else {
|
||||
self.windows[self.current_window as usize].rollup();
|
||||
}
|
||||
}
|
||||
self.windows[self.current_window as usize].update_time_show(timing);
|
||||
}
|
||||
}
|
||||
|
||||
/// Process Horizontal Carriage Return (HCR)
|
||||
///
|
||||
/// Refer Section 7.1.4.1 CEA-708-E
|
||||
///
|
||||
/// Horizontal Carriage Return (HCR) moves the current entry point to the beginning of the current row
|
||||
/// without row increment or decrement. It shall erase all text on the row
|
||||
pub fn process_hcr(&mut self) {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_process_hcr: Window has to be defined first");
|
||||
return;
|
||||
}
|
||||
let window = &mut self.windows[self.current_window as usize];
|
||||
window.pen_column = 0;
|
||||
window.clear_row(window.pen_row as usize);
|
||||
}
|
||||
|
||||
/// Process Form Feed (FF)
|
||||
///
|
||||
/// Refer Section 7.1.4.1 CEA-708-E
|
||||
///
|
||||
/// Form Feed (FF) erases all text in the window and moves the cursor to the first position (0,0)
|
||||
pub fn process_ff(&mut self) {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_process_ff: Window has to be defined first");
|
||||
return;
|
||||
}
|
||||
let window = &mut self.windows[self.current_window as usize];
|
||||
window.pen_column = 0;
|
||||
window.pen_row = 0;
|
||||
window.clear_text();
|
||||
}
|
||||
|
||||
/// Process Backspace (BS)
|
||||
///
|
||||
/// Backspace (BS) moves the cursor back by one position in the print direction
|
||||
pub fn process_bs(&mut self) {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_process_bs: Window has to be defined first");
|
||||
return;
|
||||
}
|
||||
//it looks strange, but in some videos (rarely) we have a backspace command
|
||||
//we just print one character over another
|
||||
let window = &mut self.windows[self.current_window as usize];
|
||||
let pd = match dtvcc_window_pd::new(window.attribs.print_direction) {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
warn!("{}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
match pd {
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT => {
|
||||
if window.pen_column > 0 {
|
||||
window.pen_column -= 1;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_RIGHT_LEFT => {
|
||||
if window.pen_column + 1 < window.col_count {
|
||||
window.pen_column += 1;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM => {
|
||||
if window.pen_row > 0 {
|
||||
window.pen_row -= 1;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_BOTTOM_TOP => {
|
||||
if window.pen_row + 1 < window.row_count {
|
||||
window.pen_row += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Process P16
|
||||
///
|
||||
/// Used for Code space extension for 16 bit charsets
|
||||
pub fn process_p16(&mut self, block: &[c_uchar]) {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_process_p16: Window has to be defined first");
|
||||
return;
|
||||
}
|
||||
let sym = dtvcc_symbol::new_16(block[0], block[1]);
|
||||
debug!("dtvcc_process_p16: [{:4X}]", sym.sym);
|
||||
self.process_character(sym);
|
||||
}
|
||||
|
||||
/// Process End of Text (ETX)
|
||||
pub fn process_etx(&mut self) {}
|
||||
|
||||
// -------------------------- C1 Commands-------------------------
|
||||
|
||||
/// C1 - Code Set - Captioning Command Control Codes
|
||||
pub fn handle_C1(
|
||||
&mut self,
|
||||
block: &[c_uchar],
|
||||
@@ -148,69 +327,11 @@ impl dtvcc_service_decoder {
|
||||
};
|
||||
length as i32
|
||||
}
|
||||
/// Handle G0 - Code Set - ASCII printable characters
|
||||
pub fn handle_G0(&mut self, block: &[c_uchar]) -> i32 {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_handle_G0: Window has to be defined first");
|
||||
return block.len() as i32;
|
||||
}
|
||||
|
||||
let character = block[0];
|
||||
debug!("G0: [{:2X}] ({})", character, character as char);
|
||||
let sym = if character == 0x7F {
|
||||
dtvcc_symbol::new(CCX_DTVCC_MUSICAL_NOTE_CHAR)
|
||||
} else {
|
||||
dtvcc_symbol::new(character as u16)
|
||||
};
|
||||
self.process_character(sym);
|
||||
1
|
||||
}
|
||||
/// Handle G1 - Code Set - ISO 8859-1 LATIN-1 Character Set
|
||||
pub fn handle_G1(&mut self, block: &[c_uchar]) -> i32 {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_handle_G1: Window has to be defined first");
|
||||
return block.len() as i32;
|
||||
}
|
||||
|
||||
let character = block[0];
|
||||
debug!("G1: [{:2X}] ({})", character, character as char);
|
||||
let sym = dtvcc_symbol::new(character as u16);
|
||||
self.process_character(sym);
|
||||
1
|
||||
}
|
||||
/// Handle extended codes (EXT1 + code), from the extended sets
|
||||
/// G2 (20-7F) => Mostly unmapped, except for a few characters.
|
||||
/// G3 (A0-FF) => A0 is the CC symbol, everything else reserved for future expansion in EIA708
|
||||
/// C2 (00-1F) => Reserved for future extended misc. control and captions command codes
|
||||
/// C3 (80-9F) => Reserved for future extended misc. control and captions command codes
|
||||
/// WARN: This code is completely untested due to lack of samples. Just following specs!
|
||||
/// Returns number of used bytes, usually 1 (since EXT1 is not counted).
|
||||
pub fn handle_extended_char(&mut self, block: &[c_uchar]) -> u8 {
|
||||
let code = block[0];
|
||||
debug!(
|
||||
"dtvcc_handle_extended_char, first data code: [{}], length: [{}]",
|
||||
code as char,
|
||||
block.len()
|
||||
);
|
||||
|
||||
match code {
|
||||
0..=0x1F => commands::handle_C2(code),
|
||||
0x20..=0x7F => {
|
||||
let val = unsafe { dtvcc_get_internal_from_G2(code) };
|
||||
let sym = dtvcc_symbol::new(val as u16);
|
||||
self.process_character(sym);
|
||||
1
|
||||
}
|
||||
0x80..=0x9F => commands::handle_C3(code, block[1]),
|
||||
_ => {
|
||||
let val = unsafe { dtvcc_get_internal_from_G3(code) };
|
||||
let sym = dtvcc_symbol::new(val as u16);
|
||||
self.process_character(sym);
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Handle CLW Clear Windows
|
||||
/// CLW Clear Windows
|
||||
///
|
||||
/// Clear text from all windows specified by bitmap. If window had content then
|
||||
/// all text is copied to the TV screen
|
||||
pub fn handle_clear_windows(
|
||||
&mut self,
|
||||
mut windows_bitmap: u8,
|
||||
@@ -243,7 +364,11 @@ impl dtvcc_service_decoder {
|
||||
self.screen_print(encoder, timing);
|
||||
}
|
||||
}
|
||||
/// Handle HDW Hide Windows
|
||||
|
||||
/// HDW Hide Windows
|
||||
///
|
||||
/// Hide all windows specified by bitmap. If window was not empty then
|
||||
/// all text is copied to the TV screen
|
||||
pub fn handle_hide_windows(
|
||||
&mut self,
|
||||
mut windows_bitmap: u8,
|
||||
@@ -275,7 +400,11 @@ impl dtvcc_service_decoder {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Handle TGW Toggle Windows
|
||||
|
||||
/// TGW Toggle Windows
|
||||
///
|
||||
/// Toggle visibility of all windows specified by bitmap. If window was not empty then
|
||||
/// all text is copied to the TV screen
|
||||
pub fn handle_toggle_windows(
|
||||
&mut self,
|
||||
mut windows_bitmap: u8,
|
||||
@@ -311,7 +440,11 @@ impl dtvcc_service_decoder {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Handle DLW Delete Windows
|
||||
|
||||
/// DLW Delete Windows
|
||||
///
|
||||
/// Delete all windows specified by bitmap. If window had content then
|
||||
/// all text is copied to the TV screen
|
||||
pub fn handle_delete_windows(
|
||||
&mut self,
|
||||
mut windows_bitmap: u8,
|
||||
@@ -354,7 +487,10 @@ impl dtvcc_service_decoder {
|
||||
self.screen_print(encoder, timing);
|
||||
}
|
||||
}
|
||||
/// Handle DSW Display Windows
|
||||
|
||||
/// DSW Display Windows
|
||||
///
|
||||
/// Display all windows specified by bitmap.
|
||||
pub fn handle_display_windows(
|
||||
&mut self,
|
||||
mut windows_bitmap: u8,
|
||||
@@ -381,7 +517,10 @@ impl dtvcc_service_decoder {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Handle DFx Define Windows
|
||||
|
||||
/// DFx Define Windows
|
||||
///
|
||||
/// Define a new window with the provided attributes. New window is now the current window
|
||||
pub fn handle_define_windows(
|
||||
&mut self,
|
||||
window_id: u8,
|
||||
@@ -543,7 +682,10 @@ impl dtvcc_service_decoder {
|
||||
// ...also makes the defined windows the current window
|
||||
self.handle_set_current_window(window_id);
|
||||
}
|
||||
/// Handle SPA Set Pen Attributes
|
||||
|
||||
/// SPA Set Pen Attributes
|
||||
///
|
||||
/// Change pen attributes
|
||||
pub fn handle_set_pen_attributes(&mut self, block: &[c_uchar]) {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_handle_SPA_SetPenAttributes: Window has to be defined first");
|
||||
@@ -582,7 +724,10 @@ impl dtvcc_service_decoder {
|
||||
pen.underline = underline as i32;
|
||||
pen.italic = italic as i32;
|
||||
}
|
||||
/// Handle SPC Set Pen Color
|
||||
|
||||
/// SPC Set Pen Color
|
||||
///
|
||||
/// Change pen color
|
||||
pub fn handle_set_pen_color(&mut self, block: &[c_uchar]) {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_handle_SPC_SetPenColor: Window has to be defined first");
|
||||
@@ -618,7 +763,10 @@ impl dtvcc_service_decoder {
|
||||
color.bg_opacity = bg_opacity as i32;
|
||||
color.edge_color = edge_color as i32;
|
||||
}
|
||||
/// Handle SPL Set Pen Location
|
||||
|
||||
/// SPL Set Pen Location
|
||||
///
|
||||
/// Change pen location
|
||||
pub fn handle_set_pen_location(&mut self, block: &[c_uchar]) {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_handle_SPL_SetPenLocation: Window has to be defined first");
|
||||
@@ -634,7 +782,10 @@ impl dtvcc_service_decoder {
|
||||
window.pen_row = row as i32;
|
||||
window.pen_column = col as i32;
|
||||
}
|
||||
/// Handle SWA Set Window Attributes
|
||||
|
||||
/// SWA Set Window Attributes
|
||||
///
|
||||
/// Change window attributes
|
||||
pub fn handle_set_window_attributes(&mut self, block: &[c_uchar]) {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_handle_SWA_SetWindowAttributes: Window has to be defined first");
|
||||
@@ -680,7 +831,10 @@ impl dtvcc_service_decoder {
|
||||
window_attribts.effect_direction = effect_dir as i32;
|
||||
window_attribts.effect_speed = effect_speed as i32;
|
||||
}
|
||||
/// handle CWx Set Current Window
|
||||
|
||||
/// CWx Set Current Window
|
||||
///
|
||||
/// Change current window to the window id provided
|
||||
pub fn handle_set_current_window(&mut self, window_id: u8) {
|
||||
debug!("dtvcc_handle_CWx_SetCurrentWindow: [{}]", window_id);
|
||||
if is_true(self.windows[window_id as usize].is_defined) {
|
||||
@@ -692,167 +846,23 @@ impl dtvcc_service_decoder {
|
||||
);
|
||||
}
|
||||
}
|
||||
/// Process Carriage Return(CR)
|
||||
///
|
||||
/// Refer Section 7.1.4.1 and 8.4.9.2 CEA-708-E
|
||||
pub fn process_cr(
|
||||
&mut self,
|
||||
encoder: &mut encoder_ctx,
|
||||
timing: &mut ccx_common_timing_ctx,
|
||||
no_rollup: bool,
|
||||
) {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_process_cr: Window has to be defined first");
|
||||
return;
|
||||
}
|
||||
let window = &mut self.windows[self.current_window as usize];
|
||||
let mut rollup_required = false;
|
||||
let pd = match dtvcc_window_pd::new(window.attribs.print_direction) {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
warn!("{}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
match pd {
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT => {
|
||||
window.pen_column = 0;
|
||||
if window.pen_row + 1 < window.row_count {
|
||||
window.pen_row += 1;
|
||||
} else {
|
||||
rollup_required = true;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_RIGHT_LEFT => {
|
||||
window.pen_column = window.col_count;
|
||||
if window.pen_row + 1 < window.row_count {
|
||||
window.pen_row += 1;
|
||||
} else {
|
||||
rollup_required = true;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM => {
|
||||
window.pen_row = 0;
|
||||
if window.pen_column + 1 < window.col_count {
|
||||
window.pen_column += 1;
|
||||
} else {
|
||||
rollup_required = true;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_BOTTOM_TOP => {
|
||||
window.pen_row = window.row_count;
|
||||
if window.pen_column + 1 < window.col_count {
|
||||
window.pen_column += 1;
|
||||
} else {
|
||||
rollup_required = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if is_true(window.is_defined) {
|
||||
debug!("dtvcc_process_cr: rolling uper");
|
||||
|
||||
let pen_row = window.pen_row;
|
||||
window.update_time_hide(timing);
|
||||
self.copy_to_screen(&self.windows[self.current_window as usize]);
|
||||
self.screen_print(encoder, timing);
|
||||
|
||||
if rollup_required {
|
||||
if no_rollup {
|
||||
self.windows[self.current_window as usize].clear_row(pen_row as usize);
|
||||
} else {
|
||||
self.windows[self.current_window as usize].rollup();
|
||||
}
|
||||
}
|
||||
self.windows[self.current_window as usize].update_time_show(timing);
|
||||
}
|
||||
}
|
||||
/// Process Horizontal Carriage Return (HCR)
|
||||
pub fn process_hcr(&mut self) {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_process_hcr: Window has to be defined first");
|
||||
return;
|
||||
}
|
||||
let window = &mut self.windows[self.current_window as usize];
|
||||
window.pen_column = 0;
|
||||
window.clear_row(window.pen_row as usize);
|
||||
}
|
||||
/// Process Form Feed (FF)
|
||||
pub fn process_ff(&mut self) {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_process_ff: Window has to be defined first");
|
||||
return;
|
||||
}
|
||||
let window = &mut self.windows[self.current_window as usize];
|
||||
window.pen_column = 0;
|
||||
window.pen_row = 0;
|
||||
}
|
||||
/// Process Backspace (BS)
|
||||
pub fn process_bs(&mut self) {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_process_bs: Window has to be defined first");
|
||||
return;
|
||||
}
|
||||
//it looks strange, but in some videos (rarely) we have a backspace command
|
||||
//we just print one character over another
|
||||
let window = &mut self.windows[self.current_window as usize];
|
||||
let pd = match dtvcc_window_pd::new(window.attribs.print_direction) {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
warn!("{}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
match pd {
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT => {
|
||||
if window.pen_column > 0 {
|
||||
window.pen_column -= 1;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_RIGHT_LEFT => {
|
||||
if window.pen_column + 1 < window.col_count {
|
||||
window.pen_column += 1;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM => {
|
||||
if window.pen_row > 0 {
|
||||
window.pen_row -= 1;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_BOTTOM_TOP => {
|
||||
if window.pen_row + 1 < window.row_count {
|
||||
window.pen_row += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
/// Process P16
|
||||
/// Used for Code space extension for 16 bit charsets
|
||||
pub fn process_p16(&mut self, block: &[c_uchar]) {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_process_p16: Window has to be defined first");
|
||||
return;
|
||||
}
|
||||
let sym = dtvcc_symbol::new_16(block[0], block[1]);
|
||||
debug!("dtvcc_process_p16: [{:4X}]", sym.sym);
|
||||
self.process_character(sym);
|
||||
}
|
||||
/// Process End of Text (ETX)
|
||||
pub fn process_etx(&mut self) {}
|
||||
/// Handle DLY Delay
|
||||
/// DLY Delay
|
||||
pub fn handle_delay(&mut self, tenths_of_sec: u8) {
|
||||
debug!(
|
||||
"dtvcc_handle_DLY_Delay: dely for {} tenths of second",
|
||||
tenths_of_sec
|
||||
);
|
||||
todo!()
|
||||
}
|
||||
/// Handle DLC Delay Cancel
|
||||
|
||||
/// DLC Delay Cancel
|
||||
pub fn handle_delay_cancel(&mut self) {
|
||||
debug!("dtvcc_handle_DLC_DelayCancel");
|
||||
todo!();
|
||||
}
|
||||
/// Handle RST Reset
|
||||
|
||||
/// RST Reset
|
||||
///
|
||||
/// Clear text from all windows and set all windows to undefined and hidden
|
||||
pub fn handle_reset(&mut self) {
|
||||
for id in 0..CCX_DTVCC_MAX_WINDOWS as usize {
|
||||
let window = &mut self.windows[id];
|
||||
@@ -865,73 +875,6 @@ impl dtvcc_service_decoder {
|
||||
unsafe { (*self.tv).clear() };
|
||||
}
|
||||
|
||||
/// Process the character and add it to the current window
|
||||
pub fn process_character(&mut self, sym: dtvcc_symbol) {
|
||||
debug!("{}", self.current_window);
|
||||
let window = &mut self.windows[self.current_window as usize];
|
||||
let window_state = if is_true(window.is_defined) {
|
||||
"OK"
|
||||
} else {
|
||||
"undefined"
|
||||
};
|
||||
let (mut row, mut column) = (-1, -1);
|
||||
if self.current_window != -1 {
|
||||
row = window.pen_row;
|
||||
column = window.pen_column
|
||||
}
|
||||
debug!(
|
||||
"dtvcc_process_character: [{:04X}] - Window {} [{}], Pen: {}:{}",
|
||||
sym.sym, self.current_window, window_state, row, column
|
||||
);
|
||||
|
||||
if self.current_window == -1 || is_false(window.is_defined) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.is_empty = 0;
|
||||
// Add symbol to window
|
||||
unsafe {
|
||||
window.rows[window.pen_row as usize]
|
||||
.add(window.pen_column as usize)
|
||||
.write(sym);
|
||||
}
|
||||
// "Painting" char by pen - attribs
|
||||
window.pen_attribs[window.pen_row as usize][window.pen_column as usize] =
|
||||
window.pen_attribs_pattern;
|
||||
// "Painting" char by pen - colors
|
||||
window.pen_colors[window.pen_row as usize][window.pen_column as usize] =
|
||||
window.pen_color_pattern;
|
||||
|
||||
let pd = match dtvcc_window_pd::new(window.attribs.print_direction) {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
warn!("{}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
match pd {
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT => {
|
||||
if window.pen_column + 1 < window.col_count {
|
||||
window.pen_column += 1;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_RIGHT_LEFT => {
|
||||
if window.pen_column > 0 {
|
||||
window.pen_column -= 1;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM => {
|
||||
if window.pen_row + 1 < window.row_count {
|
||||
window.pen_row += 1;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_BOTTOM_TOP => {
|
||||
if window.pen_row > 0 {
|
||||
window.pen_row -= 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
/// Print the contents of tv screen to the output file
|
||||
pub fn screen_print(&mut self, encoder: &mut encoder_ctx, timing: &mut ccx_common_timing_ctx) {
|
||||
debug!("dtvcc_screen_print rust");
|
||||
@@ -956,9 +899,10 @@ impl dtvcc_service_decoder {
|
||||
tv.clear();
|
||||
}
|
||||
}
|
||||
/// Copy the contents of window to the tv screen
|
||||
|
||||
/// Copy the contents of window to the TV screen
|
||||
pub fn copy_to_screen(&self, window: &dtvcc_window) {
|
||||
if is_true(self.is_window_overlapping(window)) {
|
||||
if self.is_window_overlapping(window) {
|
||||
debug!("dtvcc_window_copy_to_screen : window needs to be skipped");
|
||||
return;
|
||||
} else {
|
||||
@@ -1048,14 +992,15 @@ impl dtvcc_service_decoder {
|
||||
tv.update_time_hide(window.time_ms_hide);
|
||||
}
|
||||
}
|
||||
/// Check if the given window is overlapping other windows
|
||||
pub fn is_window_overlapping(&self, window: &dtvcc_window) -> u8 {
|
||||
|
||||
/// Returns `true` if the given window is overlapping other windows
|
||||
pub fn is_window_overlapping(&self, window: &dtvcc_window) -> bool {
|
||||
let mut flag = 0;
|
||||
let (a_x1, a_x2, a_y1, a_y2) = match window.get_dimensions() {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
warn!("{}", e);
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
for id in 0..CCX_DTVCC_MAX_WINDOWS as usize {
|
||||
@@ -1064,7 +1009,7 @@ impl dtvcc_service_decoder {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
warn!("{}", e);
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if a_x1 == b_x1 && a_x2 == b_x2 && a_y1 == b_y1 && a_y2 == b_y2 {
|
||||
@@ -1082,9 +1027,10 @@ impl dtvcc_service_decoder {
|
||||
}
|
||||
}
|
||||
}
|
||||
flag
|
||||
flag == 1
|
||||
}
|
||||
/// True if decoder has any visible window
|
||||
|
||||
/// Returns `true` if decoder has any visible window
|
||||
pub fn has_visible_windows(&self) -> bool {
|
||||
for id in 0..CCX_DTVCC_MAX_WINDOWS {
|
||||
if is_true(self.windows[id as usize].visible) {
|
||||
@@ -1093,4 +1039,144 @@ impl dtvcc_service_decoder {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// -------------------------- G0, G1 and extended Commands-------------------------
|
||||
|
||||
/// G0 - Code Set - ASCII printable characters
|
||||
pub fn handle_G0(&mut self, block: &[c_uchar]) -> i32 {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_handle_G0: Window has to be defined first");
|
||||
return block.len() as i32;
|
||||
}
|
||||
|
||||
let character = block[0];
|
||||
debug!("G0: [{:2X}] ({})", character, character as char);
|
||||
let sym = if character == 0x7F {
|
||||
dtvcc_symbol::new(CCX_DTVCC_MUSICAL_NOTE_CHAR)
|
||||
} else {
|
||||
dtvcc_symbol::new(character as u16)
|
||||
};
|
||||
self.process_character(sym);
|
||||
1
|
||||
}
|
||||
|
||||
/// G1 - Code Set - ISO 8859-1 LATIN-1 Character Set
|
||||
pub fn handle_G1(&mut self, block: &[c_uchar]) -> i32 {
|
||||
if self.current_window == -1 {
|
||||
warn!("dtvcc_handle_G1: Window has to be defined first");
|
||||
return block.len() as i32;
|
||||
}
|
||||
|
||||
let character = block[0];
|
||||
debug!("G1: [{:2X}] ({})", character, character as char);
|
||||
let sym = dtvcc_symbol::new(character as u16);
|
||||
self.process_character(sym);
|
||||
1
|
||||
}
|
||||
|
||||
/// Extended codes (EXT1 + code), from the extended sets
|
||||
///
|
||||
/// G2 (20-7F) => Mostly unmapped, except for a few characters.
|
||||
///
|
||||
/// G3 (A0-FF) => A0 is the CC symbol, everything else reserved for future expansion in EIA708
|
||||
///
|
||||
/// C2 (00-1F) => Reserved for future extended misc. control and captions command codes
|
||||
///
|
||||
/// C3 (80-9F) => Reserved for future extended misc. control and captions command codes
|
||||
///
|
||||
/// WARN: This code is completely untested due to lack of samples. Just following specs!
|
||||
/// Returns number of used bytes, usually 1 (since EXT1 is not counted).
|
||||
pub fn handle_extended_char(&mut self, block: &[c_uchar]) -> u8 {
|
||||
let code = block[0];
|
||||
debug!(
|
||||
"dtvcc_handle_extended_char, first data code: [{}], length: [{}]",
|
||||
code as char,
|
||||
block.len()
|
||||
);
|
||||
|
||||
match code {
|
||||
0..=0x1F => commands::handle_C2(code),
|
||||
0x20..=0x7F => {
|
||||
let val = unsafe { dtvcc_get_internal_from_G2(code) };
|
||||
let sym = dtvcc_symbol::new(val as u16);
|
||||
self.process_character(sym);
|
||||
1
|
||||
}
|
||||
0x80..=0x9F => commands::handle_C3(code, block[1]),
|
||||
_ => {
|
||||
let val = unsafe { dtvcc_get_internal_from_G3(code) };
|
||||
let sym = dtvcc_symbol::new(val as u16);
|
||||
self.process_character(sym);
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Process the character and add it to the current window
|
||||
pub fn process_character(&mut self, sym: dtvcc_symbol) {
|
||||
debug!("{}", self.current_window);
|
||||
let window = &mut self.windows[self.current_window as usize];
|
||||
let window_state = if is_true(window.is_defined) {
|
||||
"OK"
|
||||
} else {
|
||||
"undefined"
|
||||
};
|
||||
let (mut row, mut column) = (-1, -1);
|
||||
if self.current_window != -1 {
|
||||
row = window.pen_row;
|
||||
column = window.pen_column
|
||||
}
|
||||
debug!(
|
||||
"dtvcc_process_character: [{:04X}] - Window {} [{}], Pen: {}:{}",
|
||||
sym.sym, self.current_window, window_state, row, column
|
||||
);
|
||||
|
||||
if self.current_window == -1 || is_false(window.is_defined) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.is_empty = 0;
|
||||
// Add symbol to window
|
||||
unsafe {
|
||||
window.rows[window.pen_row as usize]
|
||||
.add(window.pen_column as usize)
|
||||
.write(sym);
|
||||
}
|
||||
// "Painting" char by pen - attribs
|
||||
window.pen_attribs[window.pen_row as usize][window.pen_column as usize] =
|
||||
window.pen_attribs_pattern;
|
||||
// "Painting" char by pen - colors
|
||||
window.pen_colors[window.pen_row as usize][window.pen_column as usize] =
|
||||
window.pen_color_pattern;
|
||||
|
||||
let pd = match dtvcc_window_pd::new(window.attribs.print_direction) {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
warn!("{}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
match pd {
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT => {
|
||||
if window.pen_column + 1 < window.col_count {
|
||||
window.pen_column += 1;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_RIGHT_LEFT => {
|
||||
if window.pen_column > 0 {
|
||||
window.pen_column -= 1;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM => {
|
||||
if window.pen_row + 1 < window.row_count {
|
||||
window.pen_row += 1;
|
||||
}
|
||||
}
|
||||
dtvcc_window_pd::DTVCC_WINDOW_PD_BOTTOM_TOP => {
|
||||
if window.pen_row > 0 {
|
||||
window.pen_row -= 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
//! 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 {
|
||||
@@ -16,6 +19,7 @@ impl ccx_common_timing_ctx {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// 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 {
|
||||
@@ -24,6 +28,7 @@ impl ccx_common_timing_ctx {
|
||||
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 {
|
||||
@@ -34,6 +39,7 @@ impl ccx_common_timing_ctx {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
//! 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};
|
||||
@@ -13,7 +18,7 @@ use crate::{
|
||||
use log::{debug, warn};
|
||||
|
||||
impl dtvcc_tv_screen {
|
||||
/// Clear text from 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());
|
||||
@@ -21,6 +26,8 @@ impl dtvcc_tv_screen {
|
||||
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);
|
||||
@@ -29,6 +36,8 @@ impl dtvcc_tv_screen {
|
||||
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);
|
||||
@@ -37,6 +46,11 @@ impl dtvcc_tv_screen {
|
||||
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... [{:?}][{}]",
|
||||
@@ -87,6 +101,8 @@ impl dtvcc_tv_screen {
|
||||
|
||||
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;
|
||||
@@ -104,6 +120,10 @@ impl dtvcc_tv_screen {
|
||||
}
|
||||
(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),
|
||||
@@ -118,6 +138,10 @@ impl dtvcc_tv_screen {
|
||||
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,
|
||||
@@ -215,6 +239,8 @@ impl dtvcc_tv_screen {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write captions in SRT format
|
||||
pub fn write_srt(&self, writer: &mut Writer) -> Result<(), String> {
|
||||
if self.is_screen_empty(writer) {
|
||||
return Ok(());
|
||||
@@ -242,6 +268,8 @@ impl dtvcc_tv_screen {
|
||||
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(());
|
||||
@@ -278,6 +306,8 @@ impl dtvcc_tv_screen {
|
||||
}
|
||||
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());
|
||||
@@ -310,25 +340,28 @@ impl dtvcc_tv_screen {
|
||||
writer.write_to_file(buf.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn write_sami_header(&self, writer: &mut Writer) -> Result<(), String> {
|
||||
let mut buf = String::new();
|
||||
buf.push_str("<sami>\r\n");
|
||||
buf.push_str("<head>\r\n");
|
||||
buf.push_str("<style type=\"text/css\">\r\n");
|
||||
buf.push_str("<!--\r\n");
|
||||
buf.push_str(
|
||||
"p {margin-left: 16pt; margin-right: 16pt; margin-bottom: 16pt; margin-top: 16pt;\r\n",
|
||||
);
|
||||
buf.push_str("text-align: center; font-size: 18pt; font-family: arial; font-weight: bold; color: #f0f0f0;}\r\n");
|
||||
buf.push_str(".unknowncc {Name:Unknown; lang:en-US; SAMIType:CC;}\r\n");
|
||||
buf.push_str("-->\r\n");
|
||||
buf.push_str("</style>\r\n");
|
||||
buf.push_str("</head>\r\n\r\n");
|
||||
buf.push_str("<body>\r\n");
|
||||
|
||||
writer.write_to_file(buf.as_bytes())?;
|
||||
/// 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);
|
||||
@@ -345,6 +378,10 @@ impl dtvcc_tv_screen {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
@@ -355,6 +392,8 @@ impl dtvcc_tv_screen {
|
||||
}
|
||||
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() {
|
||||
@@ -363,6 +402,10 @@ impl dtvcc_tv_screen {
|
||||
}
|
||||
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,
|
||||
@@ -396,6 +439,10 @@ impl dtvcc_tv_screen {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
//! 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,
|
||||
@@ -13,6 +25,7 @@ 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);
|
||||
@@ -29,6 +42,7 @@ impl dtvcc_window {
|
||||
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;
|
||||
@@ -45,16 +59,21 @@ impl dtvcc_window {
|
||||
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 {
|
||||
@@ -127,6 +146,7 @@ impl dtvcc_window {
|
||||
}
|
||||
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();
|
||||
@@ -137,6 +157,7 @@ impl dtvcc_window {
|
||||
}
|
||||
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 {
|
||||
@@ -178,6 +199,9 @@ impl dtvcc_window {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// 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 {
|
||||
@@ -194,6 +218,7 @@ impl dtvcc_window {
|
||||
}
|
||||
|
||||
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),
|
||||
@@ -206,6 +231,7 @@ 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),
|
||||
@@ -227,25 +253,20 @@ struct WindowStyle {
|
||||
justify: dtvcc_window_justify,
|
||||
print_direction: dtvcc_window_pd,
|
||||
scroll_direction: dtvcc_window_sd,
|
||||
/// Either 0 or 1
|
||||
word_wrap: u8,
|
||||
/// always snap
|
||||
display_effect: dtvcc_window_sde,
|
||||
/// N/A always 0
|
||||
effect_direction: u8,
|
||||
/// N/A always 0
|
||||
effect_speed: u8,
|
||||
/// Either N/A or black, still always 0
|
||||
fill_color: u8,
|
||||
fill_opacity: dtvcc_window_fo,
|
||||
/// always border_none
|
||||
border_type: dtvcc_window_border,
|
||||
/// N/A always 0
|
||||
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;
|
||||
@@ -253,98 +274,69 @@ impl WindowStyle {
|
||||
let display_effect = dtvcc_window_sde::DTVCC_WINDOW_SDE_SNAP;
|
||||
let border_type = dtvcc_window_border::DTVCC_WINDOW_BORDER_NONE;
|
||||
|
||||
match preset {
|
||||
WindowPreset::NtscPopup => Self {
|
||||
justify: dtvcc_window_justify::DTVCC_WINDOW_JUSTIFY_LEFT,
|
||||
print_direction: dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT,
|
||||
scroll_direction: dtvcc_window_sd::DTVCC_WINDOW_SD_BOTTOM_TOP,
|
||||
word_wrap: 0,
|
||||
display_effect,
|
||||
effect_direction,
|
||||
effect_speed,
|
||||
fill_color,
|
||||
fill_opacity: dtvcc_window_fo::DTVCC_WINDOW_FO_SOLID,
|
||||
border_type,
|
||||
border_color,
|
||||
},
|
||||
WindowPreset::Popup => Self {
|
||||
justify: dtvcc_window_justify::DTVCC_WINDOW_JUSTIFY_LEFT,
|
||||
print_direction: dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT,
|
||||
scroll_direction: dtvcc_window_sd::DTVCC_WINDOW_SD_BOTTOM_TOP,
|
||||
word_wrap: 0,
|
||||
display_effect,
|
||||
effect_direction,
|
||||
effect_speed,
|
||||
fill_color,
|
||||
fill_opacity: dtvcc_window_fo::DTVCC_WINDOW_FO_TRANSPARENT,
|
||||
border_type,
|
||||
border_color,
|
||||
},
|
||||
WindowPreset::NtscCenteredPopup => Self {
|
||||
justify: dtvcc_window_justify::DTVCC_WINDOW_JUSTIFY_CENTER,
|
||||
print_direction: dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT,
|
||||
scroll_direction: dtvcc_window_sd::DTVCC_WINDOW_SD_BOTTOM_TOP,
|
||||
word_wrap: 0,
|
||||
display_effect,
|
||||
effect_direction,
|
||||
effect_speed,
|
||||
fill_color,
|
||||
fill_opacity: dtvcc_window_fo::DTVCC_WINDOW_FO_SOLID,
|
||||
border_type,
|
||||
border_color,
|
||||
},
|
||||
WindowPreset::NtscRollup => Self {
|
||||
justify: dtvcc_window_justify::DTVCC_WINDOW_JUSTIFY_LEFT,
|
||||
print_direction: dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT,
|
||||
scroll_direction: dtvcc_window_sd::DTVCC_WINDOW_SD_BOTTOM_TOP,
|
||||
word_wrap: 1,
|
||||
display_effect,
|
||||
effect_direction,
|
||||
effect_speed,
|
||||
fill_color,
|
||||
fill_opacity: dtvcc_window_fo::DTVCC_WINDOW_FO_SOLID,
|
||||
border_type,
|
||||
border_color,
|
||||
},
|
||||
WindowPreset::Rollup => Self {
|
||||
justify: dtvcc_window_justify::DTVCC_WINDOW_JUSTIFY_LEFT,
|
||||
print_direction: dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT,
|
||||
scroll_direction: dtvcc_window_sd::DTVCC_WINDOW_SD_BOTTOM_TOP,
|
||||
word_wrap: 1,
|
||||
display_effect,
|
||||
effect_direction,
|
||||
effect_speed,
|
||||
fill_color,
|
||||
fill_opacity: dtvcc_window_fo::DTVCC_WINDOW_FO_TRANSPARENT,
|
||||
border_type,
|
||||
border_color,
|
||||
},
|
||||
WindowPreset::NtscCenteredRollup => Self {
|
||||
justify: dtvcc_window_justify::DTVCC_WINDOW_JUSTIFY_CENTER,
|
||||
print_direction: dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT,
|
||||
scroll_direction: dtvcc_window_sd::DTVCC_WINDOW_SD_BOTTOM_TOP,
|
||||
word_wrap: 1,
|
||||
display_effect,
|
||||
effect_direction,
|
||||
effect_speed,
|
||||
fill_color,
|
||||
fill_opacity: dtvcc_window_fo::DTVCC_WINDOW_FO_SOLID,
|
||||
border_type,
|
||||
border_color,
|
||||
},
|
||||
WindowPreset::TickerTape => Self {
|
||||
justify: dtvcc_window_justify::DTVCC_WINDOW_JUSTIFY_LEFT,
|
||||
print_direction: dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM,
|
||||
scroll_direction: dtvcc_window_sd::DTVCC_WINDOW_SD_RIGHT_LEFT,
|
||||
word_wrap: 0,
|
||||
display_effect,
|
||||
effect_direction,
|
||||
effect_speed,
|
||||
fill_color,
|
||||
fill_opacity: dtvcc_window_fo::DTVCC_WINDOW_FO_SOLID,
|
||||
border_type,
|
||||
border_color,
|
||||
},
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -369,6 +361,7 @@ pub enum WindowPreset {
|
||||
}
|
||||
|
||||
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),
|
||||
@@ -383,6 +376,7 @@ impl WindowPreset {
|
||||
}
|
||||
}
|
||||
|
||||
/// Predefined pen style ids
|
||||
#[derive(PartialEq)]
|
||||
pub enum PenPreset {
|
||||
/// #1 Default NTSC Style
|
||||
@@ -402,6 +396,7 @@ pub enum PenPreset {
|
||||
}
|
||||
|
||||
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),
|
||||
@@ -416,6 +411,7 @@ impl PenPreset {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pen style for a specific pen preset
|
||||
pub struct PenStyle {
|
||||
/// always standard pen size
|
||||
pen_size: dtvcc_pen_size,
|
||||
@@ -432,7 +428,9 @@ pub struct PenStyle {
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -496,6 +494,12 @@ impl PenStyle {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
@@ -508,6 +512,8 @@ struct PenColor {
|
||||
/// Color of the outlined edges of text
|
||||
edge_color: u8,
|
||||
}
|
||||
|
||||
/// Opacity of the window/pen colors
|
||||
enum Opacity {
|
||||
Solid = 0,
|
||||
_Flash = 1,
|
||||
@@ -516,6 +522,7 @@ enum Opacity {
|
||||
}
|
||||
|
||||
impl Default for dtvcc_pen_color {
|
||||
/// Returns the default pen color
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fg_color: 0x3F,
|
||||
@@ -528,6 +535,7 @@ impl Default for dtvcc_pen_color {
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
@@ -61,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);
|
||||
@@ -72,14 +72,42 @@ 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;
|
||||
@@ -97,16 +125,19 @@ 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 { (*ctx.timing).get_fts(ctx.current_field as u8) };
|
||||
ctx.current_field = 3;
|
||||
|
||||
// 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 is_true(ctx.extraction_end.set) && current_time > ctx.extraction_end.time_in_ms {
|
||||
timeok = false;
|
||||
ctx.processed_enough = 1;
|
||||
|
||||
Reference in New Issue
Block a user