项目文件夹

文件
wehub-resource-sync ba4be087d5
Create PR to main with cherry-pick from release / cherry-pick (push) Failing after 0s
CICD NeMo / pre-flight (push) Failing after 0s
CICD NeMo / configure (push) Has been skipped
Build, validate, and release Neural Modules / pre-flight (push) Failing after 1s
CICD NeMo / code-linting (push) Has been skipped
Build, validate, and release Neural Modules / release (push) Has been skipped
Build, validate, and release Neural Modules / release-summary (push) Has been cancelled
CICD NeMo / cicd-test-container-build (push) Has been cancelled
CICD NeMo / cicd-import-tests (push) Has been cancelled
CICD NeMo / L0_Setup_Test_Data_And_Models (push) Has been cancelled
CICD NeMo / cicd-main-unit-tests (push) Has been cancelled
CICD NeMo / cicd-main-speech (push) Has been cancelled
CICD NeMo / Nemo_CICD_Test (push) Has been cancelled
CICD NeMo / Coverage (e2e) (push) Has been cancelled
CICD NeMo / Coverage (unit-test) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
CICD NeMo / cicd-wait-in-queue (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:28:58 +08:00

310 行
12 KiB
Python

# Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import os
import re
from collections import defaultdict
from os.path import expanduser
from typing import Any, Callable, Dict, Iterator, List, Optional, Union
from nemo.utils import logging
from nemo.utils.data_utils import DataStoreObject, get_datastore_object, is_datastore_path
from nemo.utils.nemo_logging import LogMode
class ManifestBase:
"""Deprecated manifest base class."""
def __init__(self, *args, **kwargs):
raise ValueError(
"This class is deprecated, look at https://github.com/NVIDIA/NeMo/pull/284 for correct behaviour."
)
class ManifestEN:
"""Deprecated English manifest class."""
def __init__(self, *args, **kwargs):
raise ValueError(
"This class is deprecated, look at https://github.com/NVIDIA/NeMo/pull/284 for correct behaviour."
)
def item_iter(
manifests_files: Union[str, List[str]], parse_func: Callable[[str, Optional[str]], Dict[str, Any]] = None
) -> Iterator[Dict[str, Any]]:
"""Iterate through json lines of provided manifests.
NeMo ASR pipelines often assume certain manifest files structure. In
particular, each manifest file should consist of line-per-sample files with
each line being correct json dict. Each such json dict should have a field
for audio file string, a field for duration float and a field for text
string. Offset also could be additional field and is set to None by
default.
Args:
manifests_files: Either single string file or list of such -
manifests to yield items from.
parse_func: A callable function which accepts as input a single line
of a manifest and optionally the manifest file itself,
and parses it, returning a dictionary mapping from str -> Any.
Yields:
Parsed key to value item dicts.
Raises:
ValueError: If met invalid json line structure.
"""
if isinstance(manifests_files, str):
manifests_files = [manifests_files]
if parse_func is None:
parse_func = __parse_item
errors = defaultdict(list)
k = -1
logging.debug('Manifest files: %s', str(manifests_files))
for manifest_file in manifests_files:
logging.debug('Using manifest file: %s', str(manifest_file))
cached_manifest_file = DataStoreObject(manifest_file).get()
logging.debug('Cached at: %s', str(cached_manifest_file))
with open(expanduser(cached_manifest_file), 'r') as f:
for line in f:
line = line.strip()
if not line:
continue
k += 1
try:
item = parse_func(line, manifest_file)
except json.JSONDecodeError:
errors[str(manifest_file)].append(line)
continue
item['id'] = k
yield item
if len(errors) > 0:
for filename, lines in errors.items():
logging.error("=============================================")
logging.error(f"Failed to parse {len(lines)} lines from manifest file: {filename}")
for line in lines:
logging.error(f"-- Failed to parse line: `{line}`")
raise RuntimeError("Failed to parse some lines from manifest files. See logs for more details.")
def __parse_item(line: str, manifest_file: str) -> Dict[str, Any]:
item = json.loads(line)
# Audio file
if 'audio_filename' in item:
item['audio_file'] = item.pop('audio_filename')
elif 'audio_filepath' in item:
item['audio_file'] = item.pop('audio_filepath')
elif 'context' in item:
item['audio_file'] = item['context']
# Video File
if 'video_filename' in item:
item['video_file'] = item.pop('video_filename')
elif 'video_filepath' in item:
item['video_file'] = item.pop('video_filepath')
if 'video_file' not in item and 'audio_file' not in item:
raise ValueError(
f"Manifest file {manifest_file} has invalid json line structure: "
f"{line} without proper audio/video file key."
)
# If the audio/video path is a relative path and does not exist,
# try to attach the parent directory of manifest to the audio path.
# Revert to the original path if the new path still doesn't exist.
# Assume that the audio path is like "wavs/xxxxxx.wav".
if 'audio_file' in item:
item['audio_file'] = get_full_path(audio_file=item['audio_file'], manifest_file=manifest_file)
if 'video_file' in item:
item['video_file'] = get_full_path(audio_file=item['video_file'], manifest_file=manifest_file)
# Duration.
if 'context_duration' in item and 'duration' not in item:
item['duration'] = item['context_duration']
elif 'duration' not in item:
raise ValueError(
f"Manifest file {manifest_file} has invalid json line structure: {line} without proper duration key."
)
# Text.
if 'text' in item:
pass
elif 'text_filepath' in item:
with open(item.pop('text_filepath'), 'r') as f:
item['text'] = f.read().replace('\n', '')
elif 'normalized_text' in item:
item['text'] = item['normalized_text']
else:
item['text'] = ""
# Optional RTTM file
if 'rttm_file' in item:
pass
elif 'rttm_filename' in item:
item['rttm_file'] = item.pop('rttm_filename')
elif 'rttm_filepath' in item:
item['rttm_file'] = item.pop('rttm_filepath')
else:
item['rttm_file'] = None
if item['rttm_file'] is not None:
item['rttm_file'] = get_full_path(audio_file=item['rttm_file'], manifest_file=manifest_file)
# Optional audio feature file
if 'feature_file' in item:
pass
elif 'feature_filename' in item:
item['feature_file'] = item.pop('feature_filename')
elif 'feature_filepath' in item:
item['feature_file'] = item.pop('feature_filepath')
else:
item['feature_file'] = None
if item['feature_file'] is not None:
item['feature_file'] = get_full_path(audio_file=item['feature_file'], manifest_file=manifest_file)
item = dict(
audio_file=item.get('audio_file', None),
video_file=item.get('video_file', None),
duration=item['duration'],
text=item['text'],
rttm_file=item['rttm_file'],
feature_file=item['feature_file'],
offset=item.get('offset', None),
speaker=item.get('speaker', None),
orig_sr=item.get('orig_sample_rate', None),
token_labels=item.get('token_labels', None),
lang=item.get('lang', None),
context=item.get('context', None),
context_type=item.get('context_type', None),
context_duration=item.get('context_duration', None),
answer=item.get('answer', None),
answer_type=item.get('answer_type', None),
answer_duration=item.get('answer_duration', None),
question=item.get('question', None),
question_type=item.get('question_type', None),
task=item.get('task', None),
)
return item
def is_tarred_dataset(audio_file: str, manifest_file: Optional[str] = None) -> bool:
"""Check if an audio file belongs to a tarred dataset based on path heuristics."""
if "/" in audio_file or manifest_file is None:
# audio files in a tarred dataset don't have `/` in their paths
return False
if os.path.basename(manifest_file) == "tarred_audio_manifest.json":
# the manifest file is a tarred manifest
return True
if "/sharded_manifests/" in manifest_file and re.match(r'^manifest_(\d+)\.json$', os.path.basename(manifest_file)):
# the manifest file is a sharded manifest
return True
return False
def get_full_path(
audio_file: Union[str, List[str]],
manifest_file: Optional[str] = None,
data_dir: Optional[str] = None,
audio_file_len_limit: int = 255,
force_cache: bool = True,
) -> Union[str, List[str]]:
"""Get full path to audio_file.
If the audio_file is a relative path and does not exist,
try to attach the parent directory of manifest to the audio path.
Revert to the original path if the new path still doesn't exist.
Assume that the audio path is like "wavs/xxxxxx.wav".
Args:
audio_file: path to an audio file, either absolute or assumed relative
to the manifest directory or data directory.
Alternatively, a list of paths may be provided.
manifest_file: path to a manifest file
data_dir: path to a directory containing data, use only if a manifest file is not provided
audio_file_len_limit: limit for length of audio_file when using relative paths
force_cache: cache and provide local cached path of the audio if audio_file is from a data object store
Returns:
Full path to audio_file or a list of paths.
"""
if isinstance(audio_file, list):
# If input is a list, return a list of full paths
return [
get_full_path(
audio_file=a_file,
manifest_file=manifest_file,
data_dir=data_dir,
audio_file_len_limit=audio_file_len_limit,
force_cache=force_cache,
)
for a_file in audio_file
]
elif isinstance(audio_file, str):
# If input is a string, get the corresponding full path
if is_datastore_path(audio_file):
return audio_file
if is_tarred_dataset(audio_file=audio_file, manifest_file=manifest_file):
logging.warning(
f"Manifest file `{manifest_file}` seems to be part of a tarred dataset, skip checking"
f" for relative paths. If this is not intended, please avoid having `/sharded_manifests/`"
f" and `tarred_audio_manifest.json` in manifest_filepath.",
mode=LogMode.ONCE,
)
return audio_file
if (
(len(audio_file) < audio_file_len_limit)
and not os.path.isabs(audio_file)
# and not os.path.isfile(audio_file) # Commented out because it slows down dataloading
):
# If audio_file is not available and the path is not absolute, the full path is assumed
# to be relative to the manifest file parent directory or data directory.
if manifest_file is None and data_dir is None:
raise ValueError('Use either manifest_file or data_dir to specify the data directory.')
elif manifest_file is not None and data_dir is not None:
raise ValueError(
f'Parameters manifest_file and data_dir cannot be used simultaneously. '
f'Currently manifest_file is {manifest_file} and data_dir is {data_dir}.'
)
# resolve the data directory
if data_dir is None:
data_dir = os.path.dirname(manifest_file)
# assume audio_file path is relative to data_dir
audio_file_path = os.path.join(data_dir, audio_file)
if is_datastore_path(audio_file_path):
# If audio was originally on an object store, use locally-cached path.
# If the file could not be found in the local cache, it will be downloaded.
audio_file_path = get_datastore_object(audio_file_path) if force_cache else audio_file_path
return audio_file_path
if os.path.isfile(audio_file_path):
audio_file = os.path.abspath(audio_file_path)
else:
audio_file = expanduser(audio_file)
else:
audio_file = expanduser(audio_file)
return audio_file
else:
raise ValueError(f'Unexpected audio_file type {type(audio_file)}, audio_file {audio_file}.')