Trouble playing mp3s after id3 image edit - c#

Due to hardware restrictions, the software we produce tries to ensure that any audio file it imports into it's library (ready to be copied onto the hardware) is an acceptable bit rate.
Recently we've started using FFmpeg to convert a number of different audio types to mp3 to allow them to be imported and used on our hardware. Whilst the conversion works fine and the mp3 files work on our hardware afterwards, we're having issues specifically when adding an album art to the ID3 tags of the mp3. The track will not play audio in our software. It also seems that Windows cannot pick up the values of the ID3 tags in explorer, but Windows Media Player will still play the track.
This problem only seems to occur when changing a newly converted mp3s' ID3 tags after using FFmpeg. Changing tags on mp3s from other sources or those that have already got ID3 tag album art is fine.
The code for using FFmpeg from our software is as follows:
private const string SAMPLE_RATE = "44100";
...
//create temp file for output
outFile = Path.GetTempFileName();
outFile = Path.ChangeExtension(outFile, "mp3");
if (!File.Exists(inFile))
return false;
string metadata = (inFile.EndsWith("mp3")) ? " " : " -map_meta_data 0:0 ";
//build process
string workingDirectory = Environment.CurrentDirectory;
ProcessStartInfo FFmpegProcessInfo = new ProcessStartInfo();
FFmpegProcessInfo.WorkingDirectory = workingDirectory;
FFmpegProcessInfo.FileName = "ffmpeg.exe";
FFmpegProcessInfo.Arguments = "-i \"" + inFile + "\"" + " -ar "+SAMPLE_RATE + metadata + "\"" + outFile + "\""; //default conversion to SAMPLE_RATE
FFmpegProcessInfo.CreateNoWindow = true; //hide from user
//let us grab the output
FFmpegProcessInfo.RedirectStandardError = true;
FFmpegProcessInfo.RedirectStandardOutput = true;
FFmpegProcessInfo.UseShellExecute = false;
Process p = Process.Start(FFmpegProcessInfo);
To change the ID3 tags we have started using TagLib-Sharp and the code for changing the ID3 tags is:
public void SetId3Tags(string path, Bitmap image, IDictionary<string, string> values)
{
FileInfo fileInfo = new FileInfo(path);
fileInfo.Attributes = FileAttributes.Normal;
try
{
TagLib.File tagFile = TagLib.File.Create(path);
if (values.ContainsKey("Title"))
tagFile.Tag.Title = values["Title"];
if (values.ContainsKey("Artist"))
tagFile.Tag.Performers = new string[1] { values["Artist"] };
if (values.ContainsKey("Comments"))
tagFile.Tag.Comment = values["Comments"];
if (image != null) {
string tmpImg = Path.GetTempFileName();
image.Save(tmpImg);
IPicture newArt = new Picture(tmpImg);
tagFile.Tag.Pictures = new IPicture[1] {newArt};
}
tagFile.Save();
}
catch (Exception e)
{
_logger.Log(e);
}
}
And the code used to play the track in the software (FilgraphManager in QuartzTypeLib):
public void Play()
{
if (!_isPaused)
{
_graphManager = new FilgraphManager();
_mp3control = (IMediaControl)_graphManager;
_mp3position = (IMediaPosition)_graphManager;
_tempFile = Path.GetTempFileName();
File.Copy(_fullPath, _tempFile, true);
_mp3control.RenderFile(_tempFile);
}
else
{
_isPaused = false;
}
_mp3control.Run();
}
And the error when executing _mp3control.RenderFile(_tempFile):
{System.Runtime.InteropServices.ExternalException} = {"Exception from HRESULT: 0x80040266"}
at QuartzTypeLib.FilgraphManagerClass.RenderFile(String strFilename)
My largest problem here is that I don't know whether the fault lies with (our implementation of) FFmpeg (large library that's used fine in many other places), TagLib-Sharp or the audio playing.
Edit 1: Following J. Andrew Laughlin's advice I've been looking at the differences of the ID3 tags in the hex of each file. This is what I've found:
The initial input is ID3v2.3. After re-encoding with FFmpeg, the ID3 data is v2.4. This initial re-encoded file plays fine in media players and our software. Using TagLib# in our software to add album art keeps ID3v2.4 but the tags are only available using TagLib# to read them and it only plays in media players such as Windows Media Player. Using another tool to change the ID3 tags (in this case AudioShell Tag Editor) and add the same album art changed the ID3 version to 2.3 and meant that the mp3 played on our softwares audio player as well as other media players - However changing the tags afterwards produces an exception when saving the image.
One other thing I tried was to rip out the ID3v2.4 block completely after the re-encoding, this plays (as you'd expect) in all media players. When using the TagLib# on this untagged file, the tags were correctly applied (v2.3) and it continued to play properly in our software as well as others.
Unless anyone can suggest an elegant solution (either force TagLib# to write a new ID3v2.3 block or stop FFmpeg from writing one at all) I think I may just programmatically remove the ID3v2.4 block from the file after encoding and then write a new one.

TagLib# can be used to "downgrade" an ID3 tag from 2.4 to 2.3. I personally prefer to convert my ID3 tags to 2.3 since it is more consistently adopted across music players.
It's been a while, but I believe you can use the following in your above code:
TagLib.Id3v2.Tag id3v2tag = tagFile.GetTag(TagLib.TagTypes.Id3v2, false);
if(id3v2tag != null)
id3v2tag.Version = 3;
tagFile.Save();
Alternatively, you can force all tags to render in 2.3 by using the following code when your application initializes:
TagLib.Id3v2.Tag.DefaultVersion = 3;
TagLib.Id3v2.Tag.ForceDefaultVersion = true;
TagLib# can also remove tags completely and re-add them, but it shouldn't have to come to that.
Good luck!

Related

iPad can't read text file

In my Unity3D project I have several text fields. I saved my text in some text files.
When I test my project on the computer everything works fine, and my code reads the text files. But if I upload to my iPad it won't work and the text fields stay empty.
In the image you can see where I have saved my text files.
To read my text files I use the following code:
public Text infoText;
void Update()
{
readTextFile("FileName", "StepNumber")
}
public void readTextFile(string fileName, string stepNumber)
{
StreamReader txt_Reader = new StreamReader("Assets/Resources/Text_Files/" + fileName + ".txt");
while(!txt_Reader.EndOfStream)
{
string txt_String = txt_Reader.ReadLine();
if(txt_String.Contains(stepNumber))
{
string[] separator = { "_" };
string[] strList = txt_String.Split(separator, System.StringSplitOptions.RemoveEmptyEntries);
infoText.text = string.Join("\n", strList.Skip(1));
}
}
}
What do I have to change that my iPad can read from the text files?
EDIT:
My text files looks like this:
Step 1:
* Some Text
* Some Text
Step 2:
* Some Text
* Some Text
* Some Text
Step 3:
* Some Text
Step 4:
* Some Text
So each * should be a new line in my text field. With my old c# code this was no problem, but with
var lines = textFiles.text.Split(new char[] { `*` });
foreach(var line in lines)
{
...
}
i do not know how I can do that, that my text field shows all two lines for step one.
First of all from the Best Practices for the Resources folder
**Don't use it!
Please read the reasons there.
In general for system paths do never use simple string concatenation + "/" +!
Rather use Path.Combine which automatically uses the correct path separators according to the executing platform
Path.Combine(Application.dataPath, "Resources", "Text_Files", fileName + ".txt");
However, you don't/can't simply use a StreamReader to access the Resources folders (See Resources API since it is packed into the build so you have to go through Resources.Load like
// Here you can use / since this is how Unity stores internal paths
// for load you omit the suffix
TextAsset textFile = Resources.Load<TextAsset>("Text_Files/" + filename);
string fileContent = textFile.text;
Or also have a look at Resources.LoadAsync to not block the main thread meanwhile.
BUT
Speaking about blocking the main thread: What you definitely do not want to do is using any of these within Update thus doing heavy FileIO/Loading every frame!
Store the content of that file once as it won't change afterwards anyway!
Depending on your needs you could also simply put your file in any other folder inside the Assets and simply use a TextAsset field directly and drag it into according slot via the Inspector
public TextAsset textFile;
Finally you can then go through the lines one by one using e.g.
var lines = textFile.text.Split(new char[]{'/n'});
foreach(var line in lines)
{
...
}
Note that also that Split is a quite heavy operation since it has to parse every single character in the string and create new substrings so even store these results somewhere in a field of you need them multiple times during runtime!
Typed on smartphone but I hope the idea gets clear
In your case, StreamReader txt_Reader = new StreamReader("Assets/Resources/Text_Files/" + fileName + ".txt"); points to a file on your computer. Assets/Resources/Text_Files/ only exists on your computer.
You need to access a folder that exists on your iPad. It's likely you also didn't save your data to a folder existing on your IPad.
For other devices you could use : Application.persistentDataPath + "/" + fileName
Source: https://docs.unity3d.com/ScriptReference/Application-dataPath.html

How can I do reading and writing TextAssets at runtime with Unity 3D?

I have my desired txt files which I want to use as TextAssets. I need these files to be usable at runtime by my other scripts. Now the issue is that I can not figure out a way to make these things work.
I know that I should be using the Assets/Resources or the Streaming Assets folder but for some reason things are not working properly. Is there a way to incorporate it all with StreamWriters and Filestreams? What about TextAssets assigned in Unity Editor, can those also be setup as Streaming?
Some examples of code that uses my assets:
public void TaskOnClick() //getting multi-values
{
string filename = "Assets/Resources/TempoText/multi-export.txt";
using (StreamWriter writeFile = new StreamWriter(filename, false))
{
foreach (string inputJson in File.ReadLines("Assets/Resources/TempoText/multi-import.txt"))
{
string temperature = GetTemperatureByRegex(inputJson);
Debug.Log(temperature);
writeFile.AutoFlush = true;
Console.SetOut(writeFile);
writeFile.WriteLine(temperature.ToString());
}
}
File.Copy("Assets/Resources/TempoText/multi-export.txt", "Assets/Resources/multi-export.txt", true);
}
//or
FileStream filestream = new FileStream("Assets/Resources/TempoText/multi-import.txt", FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
var writeFile = new StreamWriter(filestream);
{
var document = collection.Find(new BsonDocument()).Sort(sort).Limit(limit: limit).ForEachAsync(d => Console.WriteLine(d)); //displays last 10 entries
Debug.Log(document.ToString());
writeFile.AutoFlush = true;
Console.SetOut(writeFile);
writeFile.Write(document.ToString());
}
All help greatly appreciated, I've basically messed up big time since I only found out about this now when I built everything as is...
Edit: got the streamwriters to do everything nicely with Application.persistentDataPath! Now stuck with a problem that I already struggled with - how to assign a TextAsset to get the file from a fixed path...
public TextAsset textFile;
Wondering how to set this to get it's .txt from Application.persistentDataPath
Application.persistentDataPath is what was needed all along.
Something nobody ever mentioned wherever I looked around. Hope somebody will be able to find the correct way using this mess of a question and lackluster answer.

MP3 ID3 Read Cover Art: Can only read embedded

I've tried every Library i could find, and been searching StackOverflow and google for days without getting it working.
Libraries: UltraID3Lib, TagLib, IdSharp, ID3 Tag Library by Perry Butler, ID3Lib, C# ID3 Library, ID3.Net, ... ...
None of these seem to be able to get the Cover Art that winamp shows the file has. There is no cover art or jpg or anything of the sort in the folder containing the mp3 either.
Only embedded Cover Art is showing up in every library, but other are not.
When opening winamp mp3 "File Info", if the "origin" displays "embedded" it shows up in every library, but if it shows origin "folder" then only winamp seems to know about it. Mind you, there are no images in any folder at all, the mp3 itself does contain this image.
So far i only found 1 other compiled application that gets these images, but i have no way of finding out how they did it.
Anyone know what I'm doing wrong? (I am using TagLib atm: https://github.com/mono/taglib-sharp)
public Song(string filePath)
{
var id3 = TagLib.File.Create(filePath);
TrackNumber = (Winamp.PlaylistPosition + 1);
Artist = id3.Tag.FirstArtist;
Title = id3.Tag.Title;
Position = Winamp.GetTrackPosition();
Length = Winamp.GetTrackLength();
AlbumArt = LoadPicture(id3);
Status = Winamp.GetPlaybackStatus();
}
private Bitmap LoadPicture(TagLib.File file)
{
Image currentImage = null;
if (file.Tag.Pictures.Length > 0)
{
TagLib.IPicture pic = file.Tag.Pictures[0];
MemoryStream ms = new MemoryStream(pic.Data.Data);
if (ms.Length > 0)
currentImage = Image.FromStream(ms);
ms.Close();
}
return (Bitmap)currentImage;
}

How to extract just the audio from an MP4 file and convert it to FLAC file in C#?

My goal is to write C# that turns Microsoft LYNC meeting audio into text.
Here is my project so far. Initially I was trying to record from microphone, save it to WAV then convert WAV to FLAC and using GoogleSpeechAPI, convert FLAC to text. But I got stuck recording microphone audio to WAV format.
The problem is it needs to be in a very specific WAV format, i.e. int16 or int24 for the WAV to work with the WAV to FLAC convertion method. I kept recording 8 bits per sample instead of (16 or 24 bits) per sample.
So, starting over. Microsoft Lync directly records meetings and saves it as a video in MP4 format. If I can somehow write code to convert MP4 to FLAC, that would also solve my problem. Any code example?
I recently had a ASP.NET MVC 5 application where I needed to convert .mp4 to .webm and this worked successfully, so this is an idea to apply the same concept that worked with video files but in this instance they would be audio files.
First, you would download the FFMPEG executable and copy it to a folder inside your project/solution.
The command to convert your audio file to a FLAC would be something like this:
ffmpeg -i audio.xxx -c:a flac audio.flac
You can wrap this inside a C# method to execute FFMPEG like this:
public string PathToFfmpeg { get; set; }
public void ToFlacFormat(string pathToMp4, string pathToFlac)
{
var ffmpeg = new Process
{
StartInfo = {UseShellExecute = false, RedirectStandardError = true, FileName = PathToFfmpeg}
};
var arguments =
String.Format(
#"-i ""{0}"" -c:a flac ""{1}""",
pathToMp4, pathToFlac);
ffmpeg.StartInfo.Arguments = arguments;
try
{
if (!ffmpeg.Start())
{
Debug.WriteLine("Error starting");
return;
}
var reader = ffmpeg.StandardError;
string line;
while ((line = reader.ReadLine()) != null)
{
Debug.WriteLine(line);
}
}
catch (Exception exception)
{
Debug.WriteLine(exception.ToString());
return;
}
ffmpeg.Close();
}

Whant to check all kind of audio & video quality in asp.net C#

How can I check the quality and bit-rate of audio & video files in C# asp.net?
if it is a riff / wave file the file can be examined on a binary level to get this kind of information, but it depends on exactly what kind of file.
https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
Or you can use windows media player to get some information.
using WMPLib;
// this file is called Interop.WMPLib.dll
WindowsMediaPlayerClass wmp = new WindowsMediaPlayerClass();
IWMPMedia mediaInfo = wmp.newMedia("myfile.wmv");
// write duration
Console.WriteLine("Duration = " + mediaInfo.duration);
// write named attributes
for (int i=0; i<mediaInfo.attributeCount; i++) {
Console.WriteLine(mediaInfo.getAttributeName(i) + " = " + mediaInfo.getItemInfo(mediaInfo.getAttributeName(i)) );
}
How to get the Interop.WMPLib.dll

Categories

Resources