add extra formats
This commit is contained in:
@@ -18,6 +18,10 @@ from .transcript_merger import TranscriptMerger
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Transcript output formats supported by whisper/whisperx (besides the json base).
|
||||||
|
# json is always produced because it is the base for the enhanced transcript.
|
||||||
|
VALID_TRANSCRIPT_FORMATS = {'json', 'srt', 'vtt', 'txt', 'tsv'}
|
||||||
|
|
||||||
|
|
||||||
class WorkflowConfig:
|
class WorkflowConfig:
|
||||||
"""Configuration for the processing workflow."""
|
"""Configuration for the processing workflow."""
|
||||||
@@ -36,6 +40,12 @@ class WorkflowConfig:
|
|||||||
self.diarize = kwargs.get('diarize', False)
|
self.diarize = kwargs.get('diarize', False)
|
||||||
self.language = kwargs.get('language')
|
self.language = kwargs.get('language')
|
||||||
|
|
||||||
|
# Additional transcript output formats (json is always written as the
|
||||||
|
# base for the enhanced transcript).
|
||||||
|
self.transcript_formats = self._parse_transcript_formats(
|
||||||
|
kwargs.get('transcript_formats')
|
||||||
|
)
|
||||||
|
|
||||||
# Frame extraction
|
# Frame extraction
|
||||||
self.scene_detection = kwargs.get('scene_detection', False)
|
self.scene_detection = kwargs.get('scene_detection', False)
|
||||||
self.scene_threshold = kwargs.get('scene_threshold', 15.0)
|
self.scene_threshold = kwargs.get('scene_threshold', 15.0)
|
||||||
@@ -69,12 +79,44 @@ class WorkflowConfig:
|
|||||||
self.embed_images = kwargs.get('embed_images', False)
|
self.embed_images = kwargs.get('embed_images', False)
|
||||||
self.embed_quality = kwargs.get('embed_quality', 80)
|
self.embed_quality = kwargs.get('embed_quality', 80)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_transcript_formats(raw) -> list:
|
||||||
|
"""Parse the --transcript-formats value into a deduplicated list.
|
||||||
|
|
||||||
|
Accepts a comma-separated string (e.g. "srt,txt") or 'all'. json is
|
||||||
|
always included since it is the base for the enhanced transcript.
|
||||||
|
"""
|
||||||
|
formats = ['json']
|
||||||
|
if not raw:
|
||||||
|
return formats
|
||||||
|
|
||||||
|
if isinstance(raw, str):
|
||||||
|
requested = [f.strip().lower() for f in raw.split(',') if f.strip()]
|
||||||
|
else:
|
||||||
|
requested = [str(f).strip().lower() for f in raw if str(f).strip()]
|
||||||
|
|
||||||
|
if 'all' in requested:
|
||||||
|
requested = [f for f in VALID_TRANSCRIPT_FORMATS if f != 'json']
|
||||||
|
|
||||||
|
for fmt in requested:
|
||||||
|
if fmt not in VALID_TRANSCRIPT_FORMATS:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid transcript format '{fmt}'. "
|
||||||
|
f"Choose from: {', '.join(sorted(VALID_TRANSCRIPT_FORMATS))}, all"
|
||||||
|
)
|
||||||
|
if fmt not in formats:
|
||||||
|
formats.append(fmt)
|
||||||
|
|
||||||
|
return formats
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
"""Convert config to dictionary for manifest."""
|
"""Convert config to dictionary for manifest."""
|
||||||
return {
|
return {
|
||||||
"whisper": {
|
"whisper": {
|
||||||
"enabled": self.run_whisper,
|
"enabled": self.run_whisper,
|
||||||
"model": self.whisper_model
|
"model": self.whisper_model,
|
||||||
|
"diarize": self.diarize,
|
||||||
|
"transcript_formats": self.transcript_formats
|
||||||
},
|
},
|
||||||
"frame_extraction": {
|
"frame_extraction": {
|
||||||
"method": "scene_detection" if self.scene_detection else "interval",
|
"method": "scene_detection" if self.scene_detection else "interval",
|
||||||
@@ -217,12 +259,21 @@ class ProcessingWorkflow:
|
|||||||
logger.info(f"Running Whisper transcription (model: {self.config.whisper_model})...")
|
logger.info(f"Running Whisper transcription (model: {self.config.whisper_model})...")
|
||||||
logger.info("This may take a few minutes depending on video length...")
|
logger.info("This may take a few minutes depending on video length...")
|
||||||
|
|
||||||
|
# Determine output format. json is always produced (base for the
|
||||||
|
# enhanced transcript). If extra formats are requested we ask whisper
|
||||||
|
# for "all" in a single transcription pass, then prune the formats the
|
||||||
|
# user didn't request (whisper's CLI only accepts one --output_format).
|
||||||
|
extra_formats = [f for f in self.config.transcript_formats if f != 'json']
|
||||||
|
output_format = "all" if extra_formats else "json"
|
||||||
|
if extra_formats:
|
||||||
|
logger.info(f"Additional transcript formats requested: {', '.join(extra_formats)}")
|
||||||
|
|
||||||
# Build command
|
# Build command
|
||||||
cmd = [
|
cmd = [
|
||||||
transcribe_cmd,
|
transcribe_cmd,
|
||||||
str(self.config.video_path),
|
str(self.config.video_path),
|
||||||
"--model", self.config.whisper_model,
|
"--model", self.config.whisper_model,
|
||||||
"--output_format", "json",
|
"--output_format", output_format,
|
||||||
"--output_dir", str(self.output_mgr.output_dir),
|
"--output_dir", str(self.output_mgr.output_dir),
|
||||||
]
|
]
|
||||||
if use_diarize:
|
if use_diarize:
|
||||||
@@ -244,6 +295,10 @@ class ProcessingWorkflow:
|
|||||||
|
|
||||||
transcript_path = self.output_mgr.get_path(f"{self.config.video_path.stem}.json")
|
transcript_path = self.output_mgr.get_path(f"{self.config.video_path.stem}.json")
|
||||||
|
|
||||||
|
# Prune any formats produced by "all" that the user didn't request.
|
||||||
|
if extra_formats:
|
||||||
|
self._prune_transcript_formats()
|
||||||
|
|
||||||
if transcript_path.exists():
|
if transcript_path.exists():
|
||||||
logger.info(f"✓ Whisper transcription completed: {transcript_path.name}")
|
logger.info(f"✓ Whisper transcription completed: {transcript_path.name}")
|
||||||
|
|
||||||
@@ -275,6 +330,29 @@ class ProcessingWorkflow:
|
|||||||
logger.error(f"Whisper failed: {e.stderr}")
|
logger.error(f"Whisper failed: {e.stderr}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def _prune_transcript_formats(self):
|
||||||
|
"""Remove transcript files produced by "all" that weren't requested.
|
||||||
|
|
||||||
|
whisper/whisperx only accept a single --output_format, so we run with
|
||||||
|
"all" and delete the formats the user didn't ask for. json is always
|
||||||
|
kept since it is the base for the enhanced transcript.
|
||||||
|
"""
|
||||||
|
keep = set(self.config.transcript_formats)
|
||||||
|
stem = self.config.video_path.stem
|
||||||
|
for fmt in VALID_TRANSCRIPT_FORMATS:
|
||||||
|
if fmt in keep:
|
||||||
|
continue
|
||||||
|
stray = self.output_mgr.get_path(f"{stem}.{fmt}")
|
||||||
|
if stray.exists():
|
||||||
|
try:
|
||||||
|
stray.unlink()
|
||||||
|
logger.debug(f"Removed unrequested transcript format: {stray.name}")
|
||||||
|
except OSError as e:
|
||||||
|
logger.warning(f"Could not remove {stray.name}: {e}")
|
||||||
|
|
||||||
|
written = [f for f in self.config.transcript_formats]
|
||||||
|
logger.info(f"✓ Transcript formats written: {', '.join(written)}")
|
||||||
|
|
||||||
def _extract_frames(self):
|
def _extract_frames(self):
|
||||||
"""Extract frames from video."""
|
"""Extract frames from video."""
|
||||||
logger.info("Step 1: Extracting frames from video...")
|
logger.info("Step 1: Extracting frames from video...")
|
||||||
|
|||||||
@@ -82,6 +82,15 @@ Examples:
|
|||||||
help='Language of the audio (e.g. en, es, fr). Default: auto-detect',
|
help='Language of the audio (e.g. en, es, fr). Default: auto-detect',
|
||||||
default=None
|
default=None
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--transcript-formats',
|
||||||
|
help='Comma-separated extra transcript/diarization formats to write '
|
||||||
|
'alongside JSON (choices: srt, vtt, txt, tsv, or all). JSON is '
|
||||||
|
'always written as the base for the enhanced transcript. '
|
||||||
|
'Only produced when transcription actually runs (use '
|
||||||
|
'--skip-cache-whisper to regenerate). Example: --transcript-formats srt,txt',
|
||||||
|
default=None
|
||||||
|
)
|
||||||
|
|
||||||
# Output options
|
# Output options
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
|||||||
Reference in New Issue
Block a user