or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdimport-autotag.mdindex.mdlibrary-management.mdplugin-system.mdquery-system.mduser-interface.mdutilities-templates.md

import-autotag.mddocs/

0

# Import and Autotag System

1

2

Automated music import with metadata matching, user interaction for ambiguous matches, and integration with multiple metadata sources for comprehensive tagging. The import system is the primary way music gets into a beets library with correct metadata.

3

4

## Capabilities

5

6

### Import Session Management

7

8

Central class that controls the import process, handles user interaction, and manages import decisions.

9

10

```python { .api }

11

class ImportSession:

12

def __init__(self, lib: Library, loghandler: logging.Handler = None, config: dict = None):

13

"""

14

Initialize an import session.

15

16

Parameters:

17

- lib: Library instance to import into

18

- loghandler: Optional logging handler for import messages

19

- config: Optional configuration overrides

20

"""

21

22

def run(self) -> None:

23

"""

24

Execute the import process for all configured paths.

25

Processes files, matches metadata, and handles user interaction.

26

"""

27

28

def choose_match(self, task: ImportTask) -> action:

29

"""

30

Handle match selection for an import task.

31

32

Parameters:

33

- task: ImportTask with potential matches

34

35

Returns:

36

Action constant (APPLY, ASIS, SKIP, etc.)

37

"""

38

39

def choose_item(self, task: ImportTask, item: Item) -> action:

40

"""

41

Handle individual item decisions during import.

42

43

Parameters:

44

- task: ImportTask containing the item

45

- item: Specific Item being processed

46

47

Returns:

48

Action constant for this item

49

"""

50

51

def resolve_duplicate(self, task: ImportTask, found_duplicates: List[Item]) -> action:

52

"""

53

Handle duplicate resolution during import.

54

55

Parameters:

56

- task: ImportTask that found duplicates

57

- found_duplicates: List of existing items that match

58

59

Returns:

60

Action to take for the duplicates

61

"""

62

```

63

64

### Import Task Types

65

66

Different task types for handling various import scenarios.

67

68

```python { .api }

69

class ImportTask:

70

"""Base class for import tasks representing album imports."""

71

72

def __init__(self, toppath: str, paths: List[str], items: List[Item]):

73

"""

74

Initialize import task.

75

76

Parameters:

77

- toppath: Top-level directory being imported

78

- paths: List of file paths in the album

79

- items: List of Item objects created from files

80

"""

81

82

def lookup_candidates(self) -> List[AlbumInfo]:

83

"""

84

Look up metadata candidates for this album.

85

86

Returns:

87

List of AlbumInfo objects with potential matches

88

"""

89

90

def match_candidates(self, candidates: List[AlbumInfo]) -> List[Tuple[AlbumInfo, Distance]]:

91

"""

92

Score metadata candidates against the actual items.

93

94

Parameters:

95

- candidates: List of AlbumInfo objects to score

96

97

Returns:

98

List of (AlbumInfo, Distance) tuples sorted by match quality

99

"""

100

101

class SingletonImportTask(ImportTask):

102

"""Import task for individual tracks (not part of an album)."""

103

104

def lookup_candidates(self) -> List[TrackInfo]:

105

"""

106

Look up metadata candidates for this single track.

107

108

Returns:

109

List of TrackInfo objects with potential matches

110

"""

111

112

class ArchiveImportTask(ImportTask):

113

"""Import task for compressed archives (ZIP, RAR, etc.)."""

114

115

def extract(self, dest: str) -> str:

116

"""

117

Extract archive contents to destination directory.

118

119

Parameters:

120

- dest: Destination directory for extraction

121

122

Returns:

123

Path to extracted contents

124

"""

125

```

126

127

### Import Actions

128

129

Constants defining possible actions during import process.

130

131

```python { .api }

132

# Import action constants

133

SKIP = 'skip' # Skip importing this item/album

134

ASIS = 'asis' # Import as-is without metadata changes

135

TRACKS = 'tracks' # Import as individual tracks, not album

136

APPLY = 'apply' # Apply the selected metadata match

137

ALBUMS = 'albums' # Group items into albums during import

138

RETAG = 'retag' # Re-tag existing items with new metadata

139

```

140

141

### Core Autotag Functions

142

143

High-level functions for automated metadata tagging.

144

145

```python { .api }

146

def tag_album(items: List[Item], search_artist: str = None, search_album: str = None) -> Tuple[List[AlbumInfo], List[TrackInfo]]:

147

"""

148

Automatically tag an album by searching metadata sources.

149

150

Parameters:

151

- items: List of Item objects representing album tracks

152

- search_artist: Optional artist hint for search

153

- search_album: Optional album title hint for search

154

155

Returns:

156

Tuple of (album_matches, track_matches) with scoring information

157

"""

158

159

def tag_item(item: Item, search_artist: str = None, search_title: str = None) -> Tuple[List[TrackInfo], Distance]:

160

"""

161

Automatically tag a single item by searching metadata sources.

162

163

Parameters:

164

- item: Item object to tag

165

- search_artist: Optional artist hint for search

166

- search_title: Optional title hint for search

167

168

Returns:

169

Tuple of (track_matches, best_distance) with match information

170

"""

171

172

def apply_metadata(info: Union[AlbumInfo, TrackInfo], items: List[Item]) -> None:

173

"""

174

Apply metadata from AlbumInfo or TrackInfo to items.

175

176

Parameters:

177

- info: AlbumInfo or TrackInfo object with metadata

178

- items: List of Item objects to update

179

"""

180

```

181

182

### Metadata Information Classes

183

184

Classes representing metadata retrieved from various sources.

185

186

```python { .api }

187

class AlbumInfo:

188

"""Album metadata information from external sources."""

189

190

# Core album fields

191

album: str # Album title

192

artist: str # Album artist

193

albumtype: str # Album type (album, single, EP, etc.)

194

albumtypes: List[str] # List of album types

195

va: bool # Various artists album

196

year: int # Release year

197

month: int # Release month

198

day: int # Release day

199

country: str # Release country

200

label: str # Record label

201

catalognum: str # Catalog number

202

asin: str # Amazon ASIN

203

albumdisambig: str # Disambiguation string

204

releasegroupid: str # Release group ID

205

albumid: str # Album ID from source

206

albumstatus: str # Release status

207

media: str # Media format

208

albumdisambig: str # Album disambiguation

209

210

# Track information

211

tracks: List[TrackInfo] # List of track metadata

212

213

def __init__(self, **kwargs):

214

"""Initialize with metadata fields as keyword arguments."""

215

216

class TrackInfo:

217

"""Track metadata information from external sources."""

218

219

# Core track fields

220

title: str # Track title

221

artist: str # Track artist

222

length: float # Duration in seconds

223

track: int # Track number

224

disc: int # Disc number

225

medium: int # Medium number

226

medium_index: int # Index within medium

227

medium_total: int # Total tracks on medium

228

artist_sort: str # Artist sort name

229

disctitle: str # Disc title

230

trackid: str # Track ID from source

231

artistid: str # Artist ID from source

232

233

def __init__(self, **kwargs):

234

"""Initialize with metadata fields as keyword arguments."""

235

236

class Distance:

237

"""Represents similarity measurement between items and metadata."""

238

239

def __init__(self):

240

self.album_distance: float = 0.0 # Album-level distance

241

self.tracks: List[float] = [] # Per-track distances

242

self.unmatched_tracks: int = 0 # Number of unmatched tracks

243

self.extra_tracks: int = 0 # Number of extra tracks

244

245

@property

246

def distance(self) -> float:

247

"""Overall distance score (lower is better match)."""

248

249

@property

250

def max_distance(self) -> float:

251

"""Maximum possible distance for normalization."""

252

```

253

254

### Match Classes

255

256

Classes representing matching results with scoring information.

257

258

```python { .api }

259

class AlbumMatch:

260

"""Represents a potential album match with distance scoring."""

261

262

def __init__(self, info: AlbumInfo, distance: Distance, items: List[Item]):

263

"""

264

Initialize album match.

265

266

Parameters:

267

- info: AlbumInfo object with metadata

268

- distance: Distance object with match scoring

269

- items: List of Item objects being matched

270

"""

271

272

@property

273

def goodness(self) -> float:

274

"""Match quality score (higher is better)."""

275

276

class TrackMatch:

277

"""Represents a potential track match with distance scoring."""

278

279

def __init__(self, info: TrackInfo, distance: float, item: Item):

280

"""

281

Initialize track match.

282

283

Parameters:

284

- info: TrackInfo object with metadata

285

- distance: Distance score for this match

286

- item: Item object being matched

287

"""

288

289

# Match quality levels

290

class Recommendation:

291

"""Enumeration of match recommendation levels."""

292

STRONG = 'strong' # High confidence match

293

MEDIUM = 'medium' # Moderate confidence match

294

LOW = 'low' # Low confidence match

295

NONE = 'none' # No good matches found

296

```

297

298

## Import Workflow Examples

299

300

### Basic Import Session

301

302

```python

303

from beets.library import Library

304

from beets.importer import ImportSession

305

306

# Create library and import session

307

lib = Library('/path/to/library.db', '/music')

308

session = ImportSession(lib)

309

310

# Configure paths to import

311

session.paths = ['/path/to/new/music']

312

313

# Run import process (interactive)

314

session.run()

315

```

316

317

### Programmatic Import Control

318

319

```python

320

from beets.importer import ImportSession, action

321

from beets.library import Library

322

323

class AutoImportSession(ImportSession):

324

"""Custom import session with automatic decisions."""

325

326

def choose_match(self, task):

327

"""Always apply the best match if confidence is high."""

328

if task.rec == task.rec.STRONG:

329

return action.APPLY

330

else:

331

return action.SKIP

332

333

def choose_item(self, task, item):

334

"""Auto-apply strong track matches."""

335

if task.item_match.goodness > 0.8:

336

return action.APPLY

337

else:

338

return action.ASIS

339

340

# Use custom session

341

lib = Library('/path/to/library.db', '/music')

342

session = AutoImportSession(lib)

343

session.paths = ['/path/to/new/music']

344

session.run()

345

```

346

347

### Manual Tagging

348

349

```python

350

from beets.autotag import tag_album, tag_item, apply_metadata

351

from beets.library import Item

352

353

# Tag an album manually

354

items = [Item.from_path(p) for p in album_paths]

355

album_matches, track_matches = tag_album(items)

356

357

if album_matches:

358

best_match = album_matches[0] # Highest scoring match

359

apply_metadata(best_match.info, items)

360

361

# Add to library

362

for item in items:

363

lib.add(item)

364

365

# Tag individual track

366

item = Item.from_path('/path/to/track.mp3')

367

track_matches, distance = tag_item(item)

368

369

if track_matches and distance.distance < 0.1: # Good match

370

apply_metadata(track_matches[0], [item])

371

lib.add(item)

372

```

373

374

### Custom Metadata Sources

375

376

```python

377

from beets.plugins import MetadataSourcePlugin

378

from beets.autotag.hooks import AlbumInfo, TrackInfo

379

380

class CustomMetadataSource(MetadataSourcePlugin):

381

"""Example custom metadata source plugin."""

382

383

def get_albums(self, query: str) -> Iterator[AlbumInfo]:

384

"""Search for albums from custom source."""

385

# Implement custom album search

386

results = self.search_custom_api(query)

387

388

for result in results:

389

yield AlbumInfo(

390

album=result['title'],

391

artist=result['artist'],

392

year=result['year'],

393

tracks=[

394

TrackInfo(

395

title=track['title'],

396

artist=track['artist'],

397

track=track['number']

398

) for track in result['tracks']

399

]

400

)

401

402

def get_tracks(self, query: str) -> Iterator[TrackInfo]:

403

"""Search for individual tracks."""

404

# Implement custom track search

405

pass

406

```

407

408

## Configuration Options

409

410

### Import Configuration

411

412

```python

413

# Access import configuration

414

from beets import config

415

416

# Common import settings

417

write_tags = config['import']['write'].get(bool) # Write tags to files

418

copy_files = config['import']['copy'].get(bool) # Copy vs move files

419

resume = config['import']['resume'].get(bool) # Resume interrupted imports

420

incremental = config['import']['incremental'].get(bool) # Skip unchanged directories

421

quiet_fallback = config['import']['quiet_fallback'].get(bool) # Auto-accept on timeout

422

423

# Metadata source configuration

424

sources = config['import']['sources'].get(list) # Enabled metadata sources

425

search_ids = config['import']['search_ids'].get(list) # IDs to search for

426

```

427

428

### Path Configuration

429

430

```python

431

# Path format configuration

432

path_formats = config['paths'].get(dict)

433

434

# Example path formats

435

default_format = config['paths']['default'].get(str) # Default path template

436

album_format = config['paths']['albumtype:album'].get(str) # Album-specific format

437

comp_format = config['paths']['comp'].get(str) # Compilation format

438

```

439

440

## Error Handling

441

442

```python { .api }

443

class ImportAbortError(Exception):

444

"""Raised when import process is aborted by user."""

445

446

class ImportDuplicateAlbumError(Exception):

447

"""Raised when attempting to import duplicate album."""

448

```

449

450

Common import issues:

451

- Corrupt or unreadable audio files

452

- Network timeouts when fetching metadata

453

- Duplicate detection conflicts

454

- Insufficient disk space for copying files

455

- Permission errors when moving/copying files