Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Bug Wrong MP3 Duration with TagLib

Discussion in 'Scripting' started by danosarco, Sep 15, 2023.

  1. danosarco

    danosarco

    Joined:
    Dec 18, 2017
    Posts:
    5
    Hi there, In my script I'm writing a database reading information from mp3 files with TagLib Library.
    I'm getting wrong duration for some files and I don't know how to solve the problem.

    This is the code:

    Code (CSharp):
    1.  audioFilesInfo.Clear();
    2.                 string[] mp3Files = Directory.GetFiles(selectedFolderPath, "*.mp3", SearchOption.AllDirectories);
    3.  
    4.              
    5.  
    6.                 foreach (string filePath in mp3Files)
    7.                 {
    8.                     TagLib.File tagFile = TagLib.File.Create(filePath);
    9.  
    10.                     AudioFileInfo fileInfo = new AudioFileInfo();
    11.                     fileInfo.artist = tagFile.Tag.FirstPerformer;
    12.                     fileInfo.title = tagFile.Tag.Title;
    13.                     fileInfo.album = tagFile.Tag.Album;
    14.                     fileInfo.genre = tagFile.Tag.FirstGenre;
    15.                     fileInfo.year = (uint)tagFile.Tag.Year;
    16.                     fileInfo.duration = tagFile.Properties.Duration.ToString();
    17.                     fileInfo.filePath = filePath;
    18.                     fileInfo.played = false;
    19.  
    20.                     if (!AllFieldsAreNull(fileInfo))
    21.                     {
    22.                         audioFilesInfo.Add(fileInfo);
    23.                     }
    24.                 }
    For this file I'm getting 2:15 duration when it should be 3.25:
    Code (CSharp):
    1.  
    2. {
    3.     "artist": "Juan D'Arienzo con Hector Mauré",
    4.     "title": "Las Doce ",
    5.     "album": "Coleccion Juan D'Arienzo 1940-1949",
    6.     "genre": "Tango",
    7.     "year": 1944,
    8.     "duration": "00:02:15.7780000",
    9.     "filePath": "C:\\Users\\arote\\Desktop\\Mio\\Tango-Folder\\JUAN D'ARIENZO- Hector Maure\\Las Doce.mp3",
    10.     "played": false
    11.   },
    12.  
    File mp3 attached to check.
    If someone know how to solve please give me some help, I'm stuck if I'm not getting the correct duration because I'm playing this mp3 files through the player I built but If songs aren't complete there is a big problem.

    Even tried to calculate manually the duration of the file but it turn out wrong again.
    Turn out "2.26" instead of "2.15" but not correct anyways.

    Thanks in advance
    Danilo
     

    Attached Files:

  2. danosarco

    danosarco

    Joined:
    Dec 18, 2017
    Posts:
    5
    I've just noticed that bitrate too is not correct, may be the problem itself.

    Code (CSharp):
    1. (CBR), Bitrate: 144 kbps, Las Doce  - Juan D'Arienzo con Hector Mauré
    2. UnityEngine.Debug:Log (object)
    3. GeneratorTanda:<SelectFolderAndGenerateDatabase>b__69_0 (string[]) (at Assets/GeneratorTanda.cs:269)
    4. SimpleFileBrowser.FileBrowser:OnOperationSuccessful (string[]) (at Assets/UnitySimpleFileBrowser-master/Plugins/SimpleFileBrowser/Scripts/FileBrowser.cs:1663)
    5. SimpleFileBrowser.FileBrowser:OnSubmitButtonClicked () (at Assets/UnitySimpleFileBrowser-master/Plugins/SimpleFileBrowser/Scripts/FileBrowser.cs:1637)
    6. UnityEngine.EventSystems.EventSystem:Update () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)
    7.  
    8.  
    This is my debug and it says "Bitrate: 144 kbps" but from the file properties I see 95 kbps
     
  3. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,468
    Surely the problem is a "TagLib" problem so have you tried debugging that library? I'm not sure what Unity devs can provide to you specifically here TBH.
     
    Bunny83 likes this.
  4. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,495
    I don't know that library, however I do know mp3 quite well and there may be an issue with that file in particular. The main problem is that mp3 supports variable bitrates which can change per data chunk. So in such cases it's not possible to determine a single bitrate or extrapolate the size from the length or the length from the size.


    ps:
    I just had a look and yes, that mp3 indeed has a variable bitrate. As you can read here, this has been an issue for a long time and won't be fixed since the only way to determine the actual length would be to completely decode the whole song. Some players may do that or just scan through the chunks to calculate / estimate the length.

    TagLib seems to be designed to only read meta data from a file. If the length of the song is not really stored, you can't just read it out.
     
    Last edited: Sep 15, 2023
    danosarco and MelvMay like this.
  5. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,468
    I stand corrected. A Unity dev can help you! Thanks @Bunny83
     
  6. danosarco

    danosarco

    Joined:
    Dec 18, 2017
    Posts:
    5
    Thanks for your answer.
    Se there are no possible solution to this problem?

    Is there any other way to read file properties properly?
    I don't want to write manually a database with correct duration and information because I want it to work with every mp3 file possible.

    Thanks again
     
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,495
    Well, the issue is that this is not really a file property that you can simply "read". This SO answer sums it up pretty well.

    Well, there are, or could be. I once used a LGPL library which was a 100% managed code port of a java mp3 decoder library. Since it's 100% managed code it even worked in Unity Webplayer builds back then and should even work in WebGL builds. I posted about that years ago. Though I'm not sure if that decoder can actually handle variable bitrates. You probably have to try it.

    I just looked through my old HDDs and I've found this crude class that actually does the loading of the mp3 into an AudioClip.
    This code requires the LGPL library that you can find here. Of course you have to compile it with Visual Studio and add it to Unity. Keep in mind that it's an LGPL library, so you would need to ship the license with your application.
    Code (CSharp):
    1. /*
    2. * Written by
    3. *  _______  __   __  __    _  __    _  __   __   _____   _______
    4. * |  _    ||  | |  ||  |  | ||  |  | ||  | |  | |  _  | |       |
    5. * | |_|   ||  | |  ||   |_| ||   |_| ||  |_|  | | |_| | |___    |
    6. * |      | |  | |  ||       ||       ||       ||   _   | ___|   |
    7. * |  _    ||  |_|  ||  _    ||  _    ||_     _||  | |  ||___    |
    8. * | |_|   ||       || | |   || | |   |  |   |  |  |_|  | ___|   |
    9. * |_______||_______||_|  |__||_|  |__|  |___|  |_______||_______|
    10. *
    11. * 2014.04.09@20:12
    12. *
    13. * This is an interface wrapper for loading MP3 files in Unity
    14. * This script uses Mp3Sharp.dll which can be found here:
    15. * http://www.robburke.net/mle/mp3sharp/
    16. *
    17. */
    18. using UnityEngine;
    19. using System.Collections;
    20. using System.Collections.Generic;
    21.  
    22. public class MP3Loader : MonoBehaviour
    23. {
    24.     public enum LoadState {None, Downloading, Converting, Done, Error};
    25.     private float m_UpdateInterval = 0.05f;
    26.     private float m_Percent = 0;
    27.     private bool m_Loaded = false;
    28.     private AudioClip m_Clip = null;
    29.     private LoadState m_State = LoadState.None;
    30.     private long m_ComressedSize = 0;
    31.     private long m_Size = 0;
    32.     private string m_Error = "";
    33.  
    34.     public LoadState State
    35.     {
    36.         get {return m_State;}
    37.     }
    38.  
    39.     public float Percent
    40.     {
    41.         get { return m_Percent;}
    42.     }
    43.     public long Size { get { return m_Size; } }
    44.     public long CompressedSize { get { return m_ComressedSize; } }
    45.     public string Error { get { return m_Error; } }
    46.  
    47.     public float UpdateInterval
    48.     {
    49.         get { return m_UpdateInterval; }
    50.         set { m_UpdateInterval = value; }
    51.     }
    52.  
    53.     public AudioClip Clip
    54.     {
    55.         get {return m_Clip;}
    56.     }
    57.  
    58.     public bool IsLoaded
    59.     {
    60.         get {return m_Loaded;}
    61.     }
    62.  
    63.     private IEnumerator _LoadFromStream(System.IO.Stream aStream)
    64.     {
    65.         m_ComressedSize = aStream.Length;
    66.         m_State = LoadState.Converting;
    67.         Mp3Sharp.Mp3Stream mp3 = new Mp3Sharp.Mp3Stream(aStream);
    68.         byte[] buf = new byte[4096];
    69.         float[] floatBuf = new float[buf.Length/2];
    70.         List<float> result = new List<float>();
    71.         int bytesRead = buf.Length;
    72.         float time = Time.realtimeSinceStartup +m_UpdateInterval;
    73.         while (bytesRead == buf.Length)
    74.         {
    75.             bytesRead = mp3.Read(buf, 0, buf.Length);
    76.             int floatCount = bytesRead/2;
    77.             if (bytesRead != buf.Length)
    78.                 floatBuf = new float[bytesRead/2];
    79.             for (int i = 0; i < floatCount; i++)
    80.             {
    81.                 floatBuf[i] = (short)(buf[i*2] | (buf[i*2+1]<<8));
    82.                 floatBuf[i] /= 32767.0f;
    83.             }
    84.          
    85.             result.AddRange(floatBuf);
    86.             if (Time.realtimeSinceStartup > time)
    87.             {
    88.                 yield return null;
    89.                 time = Time.realtimeSinceStartup +m_UpdateInterval;
    90.             }
    91.             m_Percent = (float)mp3.Position / mp3.Length;
    92.             m_Size = result.Count;
    93.         }
    94.         if(result.Count <= 0)
    95.         {
    96.             m_Error = "Converting error";
    97.             m_State = LoadState.Error;
    98.             yield break;
    99.         }
    100.         int samples = Mathf.FloorToInt(result.Count/2);
    101.         var clip = AudioClip.Create("clip", samples, 2, 44100, false);
    102.         Debug.Log("Length:" + result.Count);
    103.         float[] array = result.ToArray();
    104.         result.Clear();
    105.         result = null;
    106.         clip.SetData(array,0);
    107.         m_Clip = clip;
    108.         m_Percent = 1;
    109.         m_Loaded = true;
    110.         m_State = LoadState.Done;
    111.     }
    112.  
    113.     private IEnumerator _LoadFromURL(string aURL)
    114.     {
    115.         m_State = LoadState.Downloading;
    116.         WWW request = new WWW(aURL);
    117.         while(!request.isDone)
    118.         {
    119.             yield return null;
    120.             m_Percent = request.progress;
    121.         }
    122.         if (string.IsNullOrEmpty(request.error))
    123.         {
    124.             var stream = new System.IO.MemoryStream(request.bytes);
    125.             request.Dispose();
    126.             request = null;
    127.             yield return StartCoroutine(_LoadFromStream(stream));
    128.         }
    129.         else
    130.         {
    131.             m_Error = "Can't load MP3 from " + aURL+"\n" + request.error;
    132.             m_State = LoadState.Error;
    133.         }
    134.     }
    135.  
    136.     public static MP3Loader LoadFromStream(System.IO.Stream aStream)
    137.     {
    138.         var go = new GameObject("MP3Loader");
    139.         var loader = go.AddComponent<MP3Loader>();
    140.         loader.StartCoroutine(loader._LoadFromStream(aStream));
    141.         return loader;
    142.     }
    143.  
    144.     public static MP3Loader LoadFromURL(string aURL)
    145.     {
    146.         var go = new GameObject("MP3Loader");
    147.         var loader = go.AddComponent<MP3Loader>();
    148.         loader.StartCoroutine(loader._LoadFromURL(aURL));
    149.         return loader;
    150.     }
    151. }
    152.  
    It assumes a sample rate of 44100. Maybe that could be different? I seriously don't know. This was almost 10 years ago :p. So you have to experiment if it actually works. Note that this of course creates an actual audio clip of the mp3 in an uncompressed PCM format. So this requires quite a bit of memory. However if you're only interested in determining the length, you could just ditch the audio clip generation and just count the samples without actually creating the huge samples list. Divide the sample count by the samples rate and you have the length.

    Though again: I can't guarantee that this can load any potential mp3 file. If you have some sample files, go through them and see if it works or not. I had a sample script that I used in a webplayer build that loaded several mp3 from public urls. However most of them do no longer exist, so I don't see the point of posting it here. It just used that MP3Loader class several times.