Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to parse audio without lazy loaded parsers? #2280

Closed
1 task done
julianpoemp opened this issue Nov 6, 2024 · 4 comments
Closed
1 task done

How to parse audio without lazy loaded parsers? #2280

julianpoemp opened this issue Nov 6, 2024 · 4 comments
Assignees
Labels
question Question, clarification, discussion

Comments

@julianpoemp
Copy link

Has the question been asked before?

  • I have searched the existing issues

Question

Hi,

I would like to use music-metadata in one of my libraries. Because there are issues with the lazy loaded parsers, I want to use parseBlob for web (browser) without lazy loading. Is there any way to archive this?

Cheers,
Julian

@julianpoemp julianpoemp added the question Question, clarification, discussion label Nov 6, 2024
@Borewit
Copy link
Owner

Borewit commented Nov 10, 2024

Hi @julianpoemp, the dynamic imports is not something what can be changed during runtime. I am sure there configurations in which the dynamic imports do not work in the browser, but here are certainly ways to make this working in the browser. I did actually implement this in favor for of browser usage, to improve initial application loading time.

An example of a browser usage (based on Angular): https://audio-tag-analyzer.netlify.app/

@Borewit Borewit self-assigned this Nov 10, 2024
@julianpoemp
Copy link
Author

julianpoemp commented Nov 10, 2024

Hi @Borewit,

thank you for your answer.

I'm using music-metadata in one of my Angular apps, too. Building the Angular app with the dynamic imports provided by music-metadata, there are no issues. The problem is as soon as you want to create a library with ESM, CJS, UMD, and IFEE support, you can't build it because of various issues originated by the dynamic imports.

If you are interested in details of the problem I can create an example repository for reproduction.

In TS projects the compiler recognizes if a function uses dynamic imports or not. So it should be possible add a version of the parse function that accepts a parser class as argument and uses it instead of a lazy loaded parser.

Providing this would make music-metadata more open to a bigger audience.

In a local repository I tested building music-metadata with vite with static parsers and changed the code as follows:

/**
 * Parse Web API File
 * Requires Blob to be able to stream using a ReadableStreamBYOBReader, only available since Node.js ≥ 20
 * @param blob - Blob to parse
 * @param options - Parsing options
 * @returns Metadata
 */
export async function parseBlob(
  blob: Blob,
  parserClass: any,
  options: IOptions = {}
): Promise<IAudioMetadata> {
  const fileInfo: IFileInfo = { mimeType: blob.type, size: blob.size };
  if (blob instanceof File) {
    fileInfo.path = (blob as File).name;
  }
  return parseWebStream(
    blob.stream() as AnyWebByteStream,
    parserClass,
    fileInfo,
    options
  );
}

/**
 * Parse audio from Web Stream.Readable
 * @param webStream - WebStream to read the audio track from
 * @param options - Parsing options
 * @param fileInfo - File information object or MIME-type string
 * @returns Metadata
 */
export function parseWebStream(
  webStream: AnyWebByteStream,
  parserClass: any,
  fileInfo?: IFileInfo | string,
  options: IOptions = {}
): Promise<IAudioMetadata> {
  return parse(
    fromWebStream(webStream, {
      fileInfo:
        typeof fileInfo === 'string' ? { mimeType: fileInfo } : fileInfo,
    }),
    parserClass,
    options
  );
}

async function parse(
  tokenizer: ITokenizer,
  parserClass: any,
  opts?: IOptions
): Promise<IAudioMetadata> {
  // Parser found, execute parser
  const metadata = new MetadataCollector(opts);
  const parser = new parserClass(metadata, tokenizer, opts);
  await parser.parse();
  return metadata.toCommonMetadata();
}

That allows to use it like that:

if(extension === ".mp3") {
  const info = await parseBlob(blob, MpegParser, fileInfo);
}

@Borewit
Copy link
Owner

Borewit commented Nov 10, 2024

While dynamic imports do add complexity for module bundlers, I believe the actual issue belongs to the module blunder or how it is configured. Static imports in some modules don’t prevent lazy loading in others, so the root cause seems more related to the bundler's handling of these cases, not this module. Given that ESM is the official JavaScript module standard, moving towards it seems like the clear solution.

If this is a runtime issue, a potential solution could be to introduce a ParserFactory. By default, it would use dynamic imports, but could be overridden with a static parser factory that directly loads the parser, avoiding dynamic imports.

If the issue is related to TypeScript's compile-time handling of dynamic imports, solving it might be more difficult. Sure, migrating to static imports might work, but I’m hesitant to take that route as it would limit flexibility and modern practices.

@julianpoemp
Copy link
Author

If this is a runtime issue, a potential solution could be to introduce a ParserFactory. By default, it would use dynamic imports, but could be overridden with a static parser factory that directly loads the parser, avoiding dynamic imports.

It's not a runtime issue, it's a compile time issue because of vite handling dynamic imports for umd. In my vite config I have to add inlineDynamicImports: true to the rollupOptions. Now it works fine.

Thank you very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Question, clarification, discussion
Projects
None yet
Development

No branches or pull requests

2 participants