1) Support for user-defined external commandline codecs, tested on TAK

2) Better support for zip archives, which previously worked only with flac/wav files
3) More reliable tag handling, using taglib-sharp. Pictures are now preserved.
4) ALAC decoder bug fixed
This commit is contained in:
chudov
2009-02-19 04:09:59 +00:00
parent 3d94188f92
commit f37d698f6a
32 changed files with 3965 additions and 2641 deletions

View File

@@ -15,7 +15,7 @@ using System.Collections.Specialized;
namespace CUETools.Processor
{
public static class AudioReadWrite {
public static IAudioSource GetAudioSource(string path, Stream IO, string extension)
public static IAudioSource GetAudioSource(string path, Stream IO, string extension, CUEConfig config)
{
switch (extension)
{
@@ -34,25 +34,27 @@ namespace CUETools.Processor
return new TTAReader(path, IO);
#endif
default:
if (extension == "." + config.udc1Extension && config.udc1Decoder != "")
return new UserDefinedReader(path, IO, config.udc1Decoder, config.udc1Params, config.udc1APEv2);
throw new Exception("Unsupported audio type: " + path);
}
}
public static IAudioSource GetAudioSource(string path, Stream IO)
public static IAudioSource GetAudioSource(string path, Stream IO, CUEConfig config)
{
string extension = Path.GetExtension(path).ToLower();
string filename = Path.GetFileNameWithoutExtension(path);
string secondExtension = Path.GetExtension(filename).ToLower();
if (secondExtension != ".lossy" && secondExtension != ".lwcdf")
return GetAudioSource(path, IO, extension);
return GetAudioSource(path, IO, extension, config);
string lossyPath = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(filename) + ".lossy" + extension);
string lwcdfPath = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(filename) + ".lwcdf" + extension);
IAudioSource lossySource = GetAudioSource(lossyPath, null, extension);
IAudioSource lossySource = GetAudioSource(lossyPath, null, extension, config);
IAudioSource lwcdfSource = null;
try
{
lwcdfSource = GetAudioSource(lwcdfPath, null, extension);
lwcdfSource = GetAudioSource(lwcdfPath, null, extension, config);
}
catch
{
@@ -91,6 +93,11 @@ namespace CUETools.Processor
break;
#endif
default:
if (extension == "." + config.udc1Extension && config.udc1Encoder != "")
{
dest = new UserDefinedWriter(path, bitsPerSample, channelCount, sampleRate, null, config.udc1Encoder, config.udc1EncParams);
break;
}
throw new Exception("Unsupported audio type: " + path);
}
dest.FinalSampleCount = finalSampleCount;

View File

@@ -99,8 +99,14 @@
<Compile Include="Processor.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Settings.cs" />
<Compile Include="Tagging.cs" />
<Compile Include="UserDefined.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\taglib-sharp\taglib-sharp.csproj">
<Project>{4CC18776-125E-4318-9D24-D60110AD9697}</Project>
<Name>taglib-sharp</Name>
</ProjectReference>
<ProjectReference Include="..\CUETools.Codecs.ALAC\CUETools.Codecs.ALAC.csproj">
<Project>{F2EC7193-D5E5-4252-9803-5CEB407E910F}</Project>
<Name>CUETools.Codecs.ALAC</Name>

View File

@@ -53,7 +53,8 @@ namespace CUETools.Processor
WavPack,
APE,
TTA,
NoAudio
NoAudio,
UDC1
}
public enum AccurateRipMode
@@ -74,7 +75,7 @@ namespace CUETools.Processor
}
public static class General {
public static string FormatExtension(OutputAudioFormat value)
public static string FormatExtension(OutputAudioFormat value, CUEConfig config)
{
switch (value)
{
@@ -84,6 +85,7 @@ namespace CUETools.Processor
case OutputAudioFormat.TTA: return ".tta";
case OutputAudioFormat.WAV: return ".wav";
case OutputAudioFormat.NoAudio: return ".dummy";
case OutputAudioFormat.UDC1: return "." + config.udc1Extension;
}
return ".wav";
}
@@ -270,6 +272,8 @@ namespace CUETools.Processor
public bool lossyWAVHybrid;
public bool decodeHDCDtoLW16;
public bool decodeHDCDto24bit;
public string udc1Extension, udc1Decoder, udc1Params, udc1Encoder, udc1EncParams;
public bool udc1APEv2;
public CUEConfig()
{
@@ -316,6 +320,9 @@ namespace CUETools.Processor
lossyWAVHybrid = true;
decodeHDCDtoLW16 = false;
decodeHDCDto24bit = true;
udc1Extension = udc1Decoder = udc1Params = udc1Encoder = udc1EncParams = "";
udc1APEv2 = false;
}
public void Save (SettingsWriter sw)
@@ -363,6 +370,15 @@ namespace CUETools.Processor
sw.Save("LossyWAVHybrid", lossyWAVHybrid);
sw.Save("DecodeHDCDToLossyWAV16", decodeHDCDtoLW16);
sw.Save("DecodeHDCDTo24bit", decodeHDCDto24bit);
if (udc1Extension != "")
{
sw.Save("UDC1Extension", udc1Extension);
sw.Save("UDC1Decoder", udc1Decoder);
sw.Save("UDC1Params", udc1Params);
sw.Save("UDC1Encoder", udc1Encoder);
sw.Save("UDC1EncParams", udc1EncParams);
sw.Save("UDC1APEv2", udc1APEv2);
}
}
public void Load(SettingsReader sr)
@@ -410,6 +426,13 @@ namespace CUETools.Processor
lossyWAVHybrid = sr.LoadBoolean("LossyWAVHybrid") ?? true;
decodeHDCDtoLW16 = sr.LoadBoolean("DecodeHDCDToLossyWAV16") ?? false;
decodeHDCDto24bit = sr.LoadBoolean("DecodeHDCDTo24bit") ?? true;
udc1Extension = sr.Load("UDC1Extension") ?? "";
udc1Decoder = sr.Load("UDC1Decoder") ?? "";
udc1Params = sr.Load("UDC1Params") ?? "";
udc1Encoder = sr.Load("UDC1Encoder") ?? "";
udc1EncParams = sr.Load("UDC1EncParams") ?? "";
udc1APEv2 = sr.LoadBoolean("UDC1APEv2") ?? false;
}
public string CleanseString (string s)
@@ -484,7 +507,7 @@ namespace CUETools.Processor
private string _mbReleaseId;
private string _eacLog;
private string _cuePath;
private NameValueCollection _albumTags;
private TagLib.File _fileInfo;
private const int _arOffsetRange = 5 * 588 - 1;
private HDCDDotNet.HDCDDotNet hdcdDecoder;
private bool _outputLossyWAV = false;
@@ -516,7 +539,6 @@ namespace CUETools.Processor
_toc = new CDImageLayout();
_sources = new List<SourceInfo>();
_sourcePaths = new List<string>();
_albumTags = new NameValueCollection();
_stop = false;
_pause = false;
_cuePath = null;
@@ -701,7 +723,7 @@ namespace CUETools.Processor
bool seenFirstFileIndex = false;
List<IndexInfo> indexes = new List<IndexInfo>();
IndexInfo indexInfo;
NameValueCollection _trackTags = null;
TagLib.File _trackFileInfo = null;
TextReader sr;
if (Directory.Exists(pathIn))
@@ -712,6 +734,8 @@ namespace CUETools.Processor
string[] audioExts = new string[] { "*.wav", "*.flac", "*.wv", "*.ape", "*.m4a", "*.tta" };
for (i = 0; i < audioExts.Length && cueSheet == null; i++)
cueSheet = CUESheet.CreateDummyCUESheet(pathIn, audioExts[i]);
if (_config.udc1Extension != null && cueSheet == null)
cueSheet = CUESheet.CreateDummyCUESheet(pathIn, "*." + _config.udc1Extension);
if (cueSheet == null)
throw new Exception("Input directory doesn't contain supported audio files.");
sr = new StringReader(cueSheet);
@@ -839,7 +863,7 @@ namespace CUETools.Processor
sr = new StreamReader (pathIn, CUESheet.Encoding);
string logPath = Path.ChangeExtension(pathIn, ".log");
if (File.Exists(logPath))
if (System.IO.File.Exists(logPath))
{
StreamReader logReader = new StreamReader(logPath, CUESheet.Encoding);
_eacLog = logReader.ReadToEnd();
@@ -848,7 +872,7 @@ namespace CUETools.Processor
else if (CUEToolsSelection != null)
{
CUEToolsSelectionEventArgs e = new CUEToolsSelectionEventArgs();
e.choices = Directory.GetFiles(cueDir, "*.log");
e.choices = Directory.GetFiles(cueDir == "" ? "." : cueDir, "*.log");
if (e.choices.Length > 0)
{
CUEToolsSelection(this, e);
@@ -862,18 +886,15 @@ namespace CUETools.Processor
}
} else
{
IAudioSource audioSource;
NameValueCollection tags;
string cuesheetTag = null;
audioSource = AudioReadWrite.GetAudioSource(pathIn,null);
tags = audioSource.Tags;
TagLib.File fileInfo;
GetSampleLength(pathIn, out fileInfo);
NameValueCollection tags = Tagging.Analyze(fileInfo);
cuesheetTag = tags.Get("CUESHEET");
_accurateRipId = tags.Get("ACCURATERIPID");
_eacLog = tags.Get("LOG");
if (_eacLog == null) _eacLog = tags.Get("LOGFILE");
if (_eacLog == null) _eacLog = tags.Get("EACLOG");
audioSource.Close();
if (cuesheetTag == null)
throw new Exception("Input file does not contain a .cue sheet.");
sr = new StringReader (cuesheetTag);
@@ -909,8 +930,8 @@ namespace CUETools.Processor
}
_sourcePaths.Add(pathAudio);
absoluteFileStartTime += fileTimeLengthFrames;
NameValueCollection tags;
fileTimeLengthSamples = GetSampleLength(pathAudio, out tags);
TagLib.File fileInfo;
fileTimeLengthSamples = GetSampleLength(pathAudio, out fileInfo);
if ((fileTimeLengthSamples % 588) == 492 && _config.truncate4608ExtraSamples)
{
_truncated4608 = true;
@@ -918,9 +939,9 @@ namespace CUETools.Processor
}
fileTimeLengthFrames = (int)((fileTimeLengthSamples + 587) / 588);
if (_hasEmbeddedCUESheet)
_albumTags = tags;
_fileInfo = fileInfo;
else
_trackTags = tags;
_trackFileInfo = fileInfo;
seenFirstFileIndex = false;
}
}
@@ -947,8 +968,9 @@ namespace CUETools.Processor
seenFirstFileIndex = true;
if (isAudioTrack)
{
if (_tracks.Count > 0 && _trackTags != null && _trackTags.Count != 0)
_tracks[_tracks.Count - 1]._trackTags = _trackTags;
if (_tracks.Count > 0 && _trackFileInfo != null)
_tracks[_tracks.Count - 1]._fileInfo = _trackFileInfo;
_trackFileInfo = null;
sourceInfo.Path = pathAudio;
sourceInfo.Offset = 0;
sourceInfo.Length = (uint)fileTimeLengthSamples;
@@ -1116,30 +1138,46 @@ namespace CUETools.Processor
}
if (!_hasEmbeddedCUESheet && _hasSingleFilename)
{
_albumTags = _tracks[0]._trackTags;
_tracks[0]._trackTags = new NameValueCollection();
_fileInfo = _tracks[0]._fileInfo;
_tracks[0]._fileInfo = null;
}
if (_config.fillUpCUE)
{
if ((_config.overwriteCUEData || General.FindCUELine(_attributes, "PERFORMER") == null) && GetCommonTag("ALBUM ARTIST") != null)
General.SetCUELine(_attributes, "PERFORMER", GetCommonTag("ALBUM ARTIST"), true);
if ((_config.overwriteCUEData || General.FindCUELine(_attributes, "PERFORMER") == null) && GetCommonTag("ARTIST") != null)
General.SetCUELine(_attributes, "PERFORMER", GetCommonTag("ARTIST"), true);
if ((_config.overwriteCUEData || General.FindCUELine(_attributes, "TITLE") == null) && GetCommonTag("ALBUM") != null)
General.SetCUELine(_attributes, "TITLE", GetCommonTag("ALBUM"), true);
if ((_config.overwriteCUEData || General.FindCUELine(_attributes, "REM", "DATE") == null) && GetCommonTag("DATE") != null)
General.SetCUELine(_attributes, "REM", "DATE", GetCommonTag("DATE"), false);
if ((_config.overwriteCUEData || General.FindCUELine(_attributes, "REM", "DATE") == null) && GetCommonTag("YEAR") != null)
General.SetCUELine(_attributes, "REM", "DATE", GetCommonTag("YEAR"), false);
if ((_config.overwriteCUEData || General.FindCUELine(_attributes, "REM", "GENRE") == null) && GetCommonTag("GENRE") != null)
General.SetCUELine(_attributes, "REM", "GENRE", GetCommonTag("GENRE"), true);
if (_config.overwriteCUEData || General.FindCUELine(_attributes, "PERFORMER") == null)
{
string value = GetCommonTag(delegate(TagLib.File file) { return file.Tag.JoinedAlbumArtists; });
if (value == null)
value = GetCommonTag(delegate(TagLib.File file) { return file.Tag.JoinedPerformers; });
if (value != null)
General.SetCUELine(_attributes, "PERFORMER", value, true);
}
if (_config.overwriteCUEData || General.FindCUELine(_attributes, "TITLE") == null)
{
string value = GetCommonTag(delegate(TagLib.File file) { return file.Tag.Album; });
if (value != null)
General.SetCUELine(_attributes, "TITLE", value, true);
}
if (_config.overwriteCUEData || General.FindCUELine(_attributes, "REM", "DATE") == null)
{
string value = GetCommonTag(delegate(TagLib.File file) { return file.Tag.Year != 0 ? file.Tag.Year.ToString() : null; });
if (value != null)
General.SetCUELine(_attributes, "REM", "DATE", value, false);
}
if (_config.overwriteCUEData || General.FindCUELine(_attributes, "REM", "GENRE") == null)
{
string value = GetCommonTag(delegate(TagLib.File file) { return file.Tag.JoinedGenres; });
if (value != null)
General.SetCUELine(_attributes, "REM", "GENRE", value, true);
}
for (i = 0; i < TrackCount; i++)
{
TrackInfo track = _tracks[i];
string artist = _hasTrackFilenames ? track._trackTags.Get("ARTIST") :
_hasEmbeddedCUESheet ? _albumTags.Get(String.Format("cue_track{0:00}_ARTIST", i + 1)) : null;
string title = _hasTrackFilenames ? track._trackTags.Get("TITLE") :
_hasEmbeddedCUESheet ? _albumTags.Get(String.Format("cue_track{0:00}_TITLE", i + 1)) : null;
string artist = _hasTrackFilenames ? track._fileInfo.Tag.JoinedPerformers :
_hasEmbeddedCUESheet ? Tagging.TagListToSingleValue(Tagging.GetMiscTag(_fileInfo, String.Format("cue_track{0:00}_ARTIST", i + 1))) :
null;
string title = _hasTrackFilenames ? track._fileInfo.Tag.Title :
_hasEmbeddedCUESheet ? Tagging.TagListToSingleValue(Tagging.GetMiscTag(_fileInfo, String.Format("cue_track{0:00}_TITLE", i + 1))) :
null;
if ((_config.overwriteCUEData || track.Artist == "") && artist != null)
track.Artist = artist;
if ((_config.overwriteCUEData || track.Title == "") && title != null)
@@ -1149,10 +1187,11 @@ namespace CUETools.Processor
CUELine cddbDiscIdLine = General.FindCUELine(_attributes, "REM", "DISCID");
_cddbDiscIdTag = cddbDiscIdLine != null && cddbDiscIdLine.Params.Count == 3 ? cddbDiscIdLine.Params[2] : null;
if (_cddbDiscIdTag == null) _cddbDiscIdTag = GetCommonTag("DISCID");
if (_cddbDiscIdTag == null)
_cddbDiscIdTag = GetCommonMiscTag("DISCID");
if (_accurateRipId == null)
_accurateRipId = GetCommonTag("ACCURATERIPID");
_accurateRipId = GetCommonMiscTag("ACCURATERIPID");
if (_accurateRipId == null)
{
@@ -1291,7 +1330,7 @@ namespace CUETools.Processor
}
}
private Stream OpenArchive(string fileName, bool showProgress)
internal Stream OpenArchive(string fileName, bool showProgress)
{
#if !MONO
if (Path.GetExtension(_archivePath).ToLower() == ".rar")
@@ -1305,18 +1344,10 @@ namespace CUETools.Processor
#endif
if (Path.GetExtension(_archivePath).ToLower() == ".zip")
{
ZipInputStream zipStream = new ZipInputStream(File.OpenRead(_archivePath));
ZipEntry theEntry = null;
while ((theEntry = zipStream.GetNextEntry()) != null)
if (theEntry.Name == fileName)
break;
if (theEntry == null)
throw new Exception("Archive entry not found.");
if (theEntry.IsCrypted)
{
unrar_PasswordRequired(this, new PasswordRequiredEventArgs());
zipStream.Password = _archivePassword;
}
SeekableZipStream zipStream = new SeekableZipStream(_archivePath, fileName);
zipStream.PasswordRequired += new PasswordRequiredHandler(unrar_PasswordRequired);
if (showProgress)
zipStream.ExtractionProgress += new ExtractionProgressHandler(unrar_ExtractionProgress);
return zipStream;
}
throw new Exception("Unknown archive type.");
@@ -1403,10 +1434,12 @@ namespace CUETools.Processor
}
#endif
public string GetCommonTag(string tagName)
public delegate string GetStringTagProvider(TagLib.File file);
public string GetCommonTag(GetStringTagProvider provider)
{
if (_hasEmbeddedCUESheet || _hasSingleFilename)
return _albumTags.Get(tagName);
return General.EmptyStringToNull(provider(_fileInfo));
if (_hasTrackFilenames)
{
string tagValue = null;
@@ -1414,7 +1447,7 @@ namespace CUETools.Processor
for (int i = 0; i < TrackCount; i++)
{
TrackInfo track = _tracks[i];
string newValue = track._trackTags.Get (tagName);
string newValue = General.EmptyStringToNull(provider(track._fileInfo));
if (tagValue == null)
tagValue = newValue;
else
@@ -1425,6 +1458,11 @@ namespace CUETools.Processor
return null;
}
public string GetCommonMiscTag(string tagName)
{
return GetCommonTag(delegate(TagLib.File file) { return Tagging.TagListToSingleValue(Tagging.GetMiscTag(file, tagName)); });
}
private static string LocateFile(string dir, string file, List<string> contents) {
List<string> dirList, fileList;
string altDir;
@@ -1446,7 +1484,7 @@ namespace CUETools.Processor
for (int iDir = 0; iDir < dirList.Count; iDir++) {
for (int iFile = 0; iFile < fileList.Count; iFile++) {
string path = Path.Combine(dirList[iDir], fileList[iFile]);
if ( (contents == null && File.Exists(path))
if ((contents == null && System.IO.File.Exists(path))
|| (contents != null && contents.Contains(path)))
return path;
path = dirList[iDir] + '/' + fileList[iFile];
@@ -1464,7 +1502,7 @@ namespace CUETools.Processor
_outputFormat = format;
_cuePath = outputPath;
string extension = General.FormatExtension(format);
string extension = General.FormatExtension(format, _config);
List<string> find, replace;
string filename;
int iTrack;
@@ -1551,16 +1589,17 @@ namespace CUETools.Processor
}
}
private int GetSampleLength(string path, out NameValueCollection tags)
private int GetSampleLength(string path, out TagLib.File fileInfo)
{
IAudioSource audioSource;
ShowProgress("Analyzing input file...", 0.0, 0.0, path, null);
if (_isArchive)
audioSource = AudioReadWrite.GetAudioSource(path, OpenArchive(path, true));
else
audioSource = AudioReadWrite.GetAudioSource(path, null);
TagLib.UserDefined.AdditionalFileTypes.Config = _config;
TagLib.File.IFileAbstraction file = _isArchive
? (TagLib.File.IFileAbstraction) new ArchiveFileAbstraction(this, path)
: (TagLib.File.IFileAbstraction) new TagLib.File.LocalFileAbstraction(path);
fileInfo = TagLib.File.Create(file);
IAudioSource audioSource = AudioReadWrite.GetAudioSource(path, _isArchive ? OpenArchive(path, true) : null, _config);
if ((audioSource.BitsPerSample != 16) ||
(audioSource.ChannelCount != 2) ||
(audioSource.SampleRate != 44100) ||
@@ -1569,8 +1608,6 @@ namespace CUETools.Processor
audioSource.Close();
throw new Exception("Audio format is invalid.");
}
tags = audioSource.Tags;
audioSource.Close();
return (int)audioSource.Length;
}
@@ -2021,13 +2058,12 @@ namespace CUETools.Processor
{
string logContents = LOGContents();
string cueContents = CUESheetContents(style);
bool needNewCRCs = _accurateRipMode != AccurateRipMode.None &&
(_accurateRipMode == AccurateRipMode.VerifyAndConvert || _isCD) &&
_config.writeArTagsOnConvert &&
_arVerify.AccResult == HttpStatusCode.OK;
uint tracksMatch = 0;
int bestOffset = 0;
if (needNewCRCs)
if (_accurateRipMode != AccurateRipMode.None &&
_config.writeArTagsOnConvert &&
_arVerify.AccResult == HttpStatusCode.OK)
FindBestOffset(1, true, out tracksMatch, out bestOffset);
if (logContents != null)
@@ -2035,51 +2071,70 @@ namespace CUETools.Processor
else
if (_eacLog != null && _config.extractLog)
WriteText(Path.ChangeExtension(_cuePath, ".log"), _eacLog);
if (style != CUEStyle.SingleFileWithCUE)
if (style == CUEStyle.SingleFileWithCUE || style == CUEStyle.SingleFile)
{
WriteText(_cuePath, cueContents);
#if !MONO
if (needNewCRCs && style != CUEStyle.SingleFile)
if (style == CUEStyle.SingleFileWithCUE && _config.createCUEFileWhenEmbedded)
WriteText(Path.ChangeExtension(_cuePath, ".cue"), cueContents);
if (style == CUEStyle.SingleFile)
WriteText(_cuePath, cueContents);
if (_outputFormat != OutputAudioFormat.NoAudio)
{
for (int iTrack = 0; iTrack < TrackCount; iTrack++)
NameValueCollection tags = GenerateAlbumTags(bestOffset, style == CUEStyle.SingleFileWithCUE);
TagLib.UserDefined.AdditionalFileTypes.Config = _config;
TagLib.File fileInfo = TagLib.File.Create(new TagLib.File.LocalFileAbstraction(destPaths[0]));
if (Tagging.UpdateTags(fileInfo, tags, _config))
{
IAudioSource audioSource = AudioReadWrite.GetAudioSource(destPaths[iTrack + (htoaToFile ? 1 : 0)], null);
CleanupTags(audioSource.Tags, "ACCURATERIP");
GenerateAccurateRipTags(audioSource.Tags, 0, bestOffset, iTrack);
audioSource.UpdateTags(false);
audioSource.Close();
audioSource = null;
fileInfo.Tag.DiscCount = (_tracks[0]._fileInfo ?? _fileInfo).Tag.DiscCount; // TODO: GetCommonTag?
fileInfo.Tag.Disc = (_tracks[0]._fileInfo ?? _fileInfo).Tag.Disc;
//fileInfo.Tag.Title = null;
//fileInfo.Tag.Performers = (_tracks[iTrack]._fileInfo ?? _fileInfo).Tag.Performers;
//if (fileInfo.Tag.Performers.Length == 0) fileInfo.Tag.Performers = new string[] { _tracks[iTrack].Artist != "" ? _tracks[iTrack].Artist : Artist };
fileInfo.Tag.AlbumArtists = (_tracks[0]._fileInfo ?? _fileInfo).Tag.AlbumArtists;
if (fileInfo.Tag.AlbumArtists.Length == 0) fileInfo.Tag.AlbumArtists = new string[] { Artist };
fileInfo.Tag.Album = (_tracks[0]._fileInfo ?? _fileInfo).Tag.Album ?? Title;
uint year = (_tracks[0]._fileInfo ?? _fileInfo).Tag.Year;
fileInfo.Tag.Year = year != 0 ? year : ("" != Year && uint.TryParse(Year, out year)) ? year : 0;
fileInfo.Tag.Genres = (_tracks[0]._fileInfo ?? _fileInfo).Tag.Genres;
if (fileInfo.Tag.Genres.Length == 0) fileInfo.Tag.Genres = new string[] { Genre };
fileInfo.Tag.Pictures = (_tracks[0]._fileInfo ?? _fileInfo).Tag.Pictures;
fileInfo.Save();
}
}
#endif
}
else
{
if (_config.createCUEFileWhenEmbedded)
WriteText(Path.ChangeExtension(_cuePath, ".cue"), cueContents);
#if !MONO
if (needNewCRCs || _isCD)
{
IAudioSource audioSource = AudioReadWrite.GetAudioSource(destPaths[0], null);
if (_isCD)
WriteText(_cuePath, cueContents);
if (_config.createM3U)
WriteText(Path.ChangeExtension(_cuePath, ".m3u"), M3UContents(style));
if (_outputFormat != OutputAudioFormat.NoAudio)
for (int iTrack = 0; iTrack < TrackCount; iTrack++)
{
if (_accurateRipMode != AccurateRipMode.VerifyThenConvert)
audioSource.Tags.Add("CUESHEET", cueContents);
audioSource.Tags.Add("LOG", logContents);
string path = destPaths[iTrack + (htoaToFile ? 1 : 0)];
NameValueCollection tags = GenerateTrackTags(iTrack, bestOffset);
TagLib.UserDefined.AdditionalFileTypes.Config = _config;
TagLib.File fileInfo = TagLib.File.Create(new TagLib.File.LocalFileAbstraction(path));
if (Tagging.UpdateTags(fileInfo, tags, _config))
{
fileInfo.Tag.TrackCount = (uint) TrackCount;
fileInfo.Tag.Track = (uint) iTrack + 1;
fileInfo.Tag.DiscCount = (_tracks[iTrack]._fileInfo ?? _fileInfo).Tag.DiscCount;
fileInfo.Tag.Disc = (_tracks[iTrack]._fileInfo ?? _fileInfo).Tag.Disc;
fileInfo.Tag.Title = _tracks[iTrack]._fileInfo != null ? _tracks[iTrack]._fileInfo.Tag.Title : _tracks[iTrack].Title;
fileInfo.Tag.Performers = (_tracks[iTrack]._fileInfo ?? _fileInfo).Tag.Performers;
if (fileInfo.Tag.Performers.Length == 0) fileInfo.Tag.Performers = new string[] { _tracks[iTrack].Artist != "" ? _tracks[iTrack].Artist : Artist };
fileInfo.Tag.AlbumArtists = (_tracks[iTrack]._fileInfo ?? _fileInfo).Tag.AlbumArtists;
if (fileInfo.Tag.AlbumArtists.Length == 0) fileInfo.Tag.AlbumArtists = new string[] { Artist };
fileInfo.Tag.Album = (_tracks[iTrack]._fileInfo ?? _fileInfo).Tag.Album ?? Title;
uint year = (_tracks[iTrack]._fileInfo ?? _fileInfo).Tag.Year;
fileInfo.Tag.Year = year != 0 ? year : ("" != Year && uint.TryParse(Year, out year)) ? year : 0;
fileInfo.Tag.Genres = (_tracks[iTrack]._fileInfo ?? _fileInfo).Tag.Genres;
if (fileInfo.Tag.Genres.Length == 0) fileInfo.Tag.Genres = new string[] { Genre };
fileInfo.Tag.Pictures = (_tracks[iTrack]._fileInfo ?? _fileInfo).Tag.Pictures;
fileInfo.Save();
}
}
if (needNewCRCs)
{
CleanupTags(audioSource.Tags, "ACCURATERIP");
GenerateAccurateRipTags(audioSource.Tags, 0, bestOffset, -1);
}
audioSource.UpdateTags(false);
audioSource.Close();
audioSource = null;
}
#endif
}
if (style != CUEStyle.SingleFileWithCUE && style != CUEStyle.SingleFile && _config.createM3U)
WriteText(Path.ChangeExtension(_cuePath, ".m3u"), M3UContents(style));
}
}
@@ -2095,34 +2150,25 @@ namespace CUETools.Processor
if (_hasEmbeddedCUESheet)
{
IAudioSource audioSource = AudioReadWrite.GetAudioSource(_sourcePaths[0], null);
NameValueCollection tags = audioSource.Tags;
CleanupTags(tags, "ACCURATERIP");
GenerateAccurateRipTags (tags, 0, bestOffset, -1);
#if !MONO
if (audioSource is FLACReader)
audioSource.UpdateTags (true);
#endif
audioSource.Close();
audioSource = null;
if (_fileInfo is TagLib.Flac.File)
{
NameValueCollection tags = Tagging.Analyze(_fileInfo);
CleanupTags(tags, "ACCURATERIP");
GenerateAccurateRipTags(tags, 0, bestOffset, -1);
if (Tagging.UpdateTags(_fileInfo, tags, _config))
_fileInfo.Save();
}
} else if (_hasTrackFilenames)
{
for (int iTrack = 0; iTrack < TrackCount; iTrack++)
{
string src = _sourcePaths[iTrack + (_hasHTOAFilename ? 1 : 0)];
IAudioSource audioSource = AudioReadWrite.GetAudioSource(src, null);
#if !MONO
if (audioSource is FLACReader)
if (_tracks[iTrack]._fileInfo is TagLib.Flac.File)
{
NameValueCollection tags = audioSource.Tags;
NameValueCollection tags = Tagging.Analyze(_tracks[iTrack]._fileInfo);
CleanupTags(tags, "ACCURATERIP");
GenerateAccurateRipTags (tags, 0, bestOffset, iTrack);
audioSource.UpdateTags(true);
GenerateAccurateRipTags(tags, 0, bestOffset, iTrack);
if (Tagging.UpdateTags(_tracks[iTrack]._fileInfo, tags, _config))
_tracks[iTrack]._fileInfo.Save();
}
#endif
audioSource.Close();
audioSource = null;
}
}
}
@@ -2145,76 +2191,74 @@ namespace CUETools.Processor
}
}
private void SetTrackTags(IAudioDest audioDest, int iTrack, int bestOffset)
private NameValueCollection GenerateTrackTags(int iTrack, int bestOffset)
{
NameValueCollection destTags = new NameValueCollection();
if (_hasEmbeddedCUESheet)
{
string trackPrefix = String.Format ("cue_track{0:00}_", iTrack + 1);
string[] keys = _albumTags.AllKeys;
for (int i = 0; i < keys.Length; i++)
string trackPrefix = String.Format("cue_track{0:00}_", iTrack + 1);
NameValueCollection albumTags = Tagging.Analyze(_fileInfo);
foreach (string key in albumTags.AllKeys)
{
if (keys[i].ToLower().StartsWith(trackPrefix)
|| !keys[i].ToLower().StartsWith("cue_track"))
if (key.ToLower().StartsWith(trackPrefix)
|| !key.ToLower().StartsWith("cue_track"))
{
string name = keys[i].ToLower().StartsWith(trackPrefix) ?
keys[i].Substring(trackPrefix.Length) : keys[i];
string[] values = _albumTags.GetValues(keys[i]);
string name = key.ToLower().StartsWith(trackPrefix) ?
key.Substring(trackPrefix.Length) : key;
string[] values = albumTags.GetValues(key);
for (int j = 0; j < values.Length; j++)
destTags.Add(name, values[j]);
}
}
}
else if (_hasTrackFilenames)
destTags.Add(_tracks[iTrack]._trackTags);
destTags.Add(Tagging.Analyze(_tracks[iTrack]._fileInfo));
else if (_hasSingleFilename)
{
// TODO?
}
destTags.Remove("CUESHEET");
// these will be set explicitely
destTags.Remove("ARTIST");
destTags.Remove("TITLE");
destTags.Remove("ALBUM");
destTags.Remove("ALBUMARTIST");
destTags.Remove("DATE");
destTags.Remove("GENRE");
destTags.Remove("TRACKNUMBER");
destTags.Remove("TRACKTOTAL");
destTags.Remove("TOTALTRACKS");
destTags.Remove("DISCNUMBER");
destTags.Remove("DISCTOTAL");
destTags.Remove("TOTALDISCS");
destTags.Remove("LOG");
destTags.Remove("LOGFILE");
destTags.Remove("EACLOG");
// these are not valid
destTags.Remove("CUESHEET");
CleanupTags(destTags, "ACCURATERIP");
CleanupTags(destTags, "REPLAYGAIN");
if (destTags.Get("TITLE") == null && "" != _tracks[iTrack].Title)
destTags.Add("TITLE", _tracks[iTrack].Title);
if (destTags.Get("ARTIST") == null && "" != _tracks[iTrack].Artist)
destTags.Add("ARTIST", _tracks[iTrack].Artist);
if (destTags.Get("ARTIST") == null && "" != Artist)
destTags.Add("ARTIST", Artist);
if (destTags.Get("ALBUM ARTIST") == null && "" != Artist)
destTags.Add("ALBUM ARTIST", Artist);
if (destTags.Get("ALBUM") == null && "" != Title)
destTags.Add("ALBUM", Title);
if (destTags.Get("DATE") == null && "" != Year)
destTags.Add("DATE", Year);
if (destTags.Get("GENRE") == null && "" != Genre)
destTags.Add("GENRE", Genre);
destTags.Add("TRACKNUMBER", (iTrack + 1).ToString("00"));
destTags.Add("TOTALTRACKS", TrackCount.ToString("00"));
if (_config.writeArTagsOnConvert)
{
if (!_isCD && _accurateRipMode == AccurateRipMode.VerifyThenConvert && _arVerify.AccResult == HttpStatusCode.OK)
if (_accurateRipMode != AccurateRipMode.None && _arVerify.AccResult == HttpStatusCode.OK)
GenerateAccurateRipTags(destTags, _writeOffset, bestOffset, iTrack);
else
destTags.Add("ACCURATERIPID", _accurateRipId);
}
audioDest.SetTags(destTags);
return destTags;
}
private void SetAlbumTags(IAudioDest audioDest, int bestOffset, bool fWithCUE)
private NameValueCollection GenerateAlbumTags(int bestOffset, bool fWithCUE)
{
NameValueCollection destTags = new NameValueCollection();
if (_hasEmbeddedCUESheet || _hasSingleFilename)
{
destTags.Add(_albumTags);
destTags.Add(Tagging.Analyze(_fileInfo));
if (!fWithCUE)
CleanupTags(destTags, "CUE_TRACK");
}
@@ -2222,32 +2266,45 @@ namespace CUETools.Processor
{
for (int iTrack = 0; iTrack < TrackCount; iTrack++)
{
string[] keys = _tracks[iTrack]._trackTags.AllKeys;
for (int i = 0; i < keys.Length; i++)
NameValueCollection trackTags = Tagging.Analyze(_tracks[iTrack]._fileInfo);
foreach (string key in trackTags.AllKeys)
{
string singleValue = GetCommonTag (keys[i]);
string singleValue = GetCommonMiscTag(key);
if (singleValue != null)
{
if (destTags.Get(keys[i]) == null)
destTags.Add(keys[i], singleValue);
if (destTags.Get(key) == null)
destTags.Add(key, singleValue);
}
else if (fWithCUE && keys[i].ToUpper() != "TRACKNUMBER")
else if (fWithCUE && key.ToUpper() != "TRACKNUMBER")
{
string[] values = _tracks[iTrack]._trackTags.GetValues(keys[i]);
string[] values = trackTags.GetValues(key);
for (int j = 0; j < values.Length; j++)
destTags.Add(String.Format("cue_track{0:00}_{1}", iTrack + 1, keys[i]), values[j]);
destTags.Add(String.Format("cue_track{0:00}_{1}", iTrack + 1, key), values[j]);
}
}
}
}
destTags.Remove("CUESHEET");
// these will be set explicitely
destTags.Remove("ARTIST");
destTags.Remove("TITLE");
destTags.Remove("ALBUM");
destTags.Remove("ALBUMARTIST");
destTags.Remove("DATE");
destTags.Remove("GENRE");
destTags.Remove("TRACKNUMBER");
destTags.Remove("TRACKTOTAL");
destTags.Remove("TOTALTRACKS");
destTags.Remove("DISCNUMBER");
destTags.Remove("DISCTOTAL");
destTags.Remove("TOTALDISCS");
// these are not valid
CleanupTags(destTags, "ACCURATERIP");
CleanupTags(destTags, "REPLAYGAIN");
if (fWithCUE && (!_isCD || _accurateRipMode == AccurateRipMode.VerifyThenConvert))
destTags.Remove("CUESHEET");
if (fWithCUE)
destTags.Add("CUESHEET", CUESheetContents(CUEStyle.SingleFileWithCUE));
if (_config.embedLog)
@@ -2255,18 +2312,21 @@ namespace CUETools.Processor
destTags.Remove("LOG");
destTags.Remove("LOGFILE");
destTags.Remove("EACLOG");
if (_eacLog != null)
string logContents = LOGContents();
if (logContents != null)
destTags.Add("LOG", logContents);
else if (_eacLog != null)
destTags.Add("LOG", _eacLog);
}
if (_config.writeArTagsOnConvert)
{
if (fWithCUE && !_isCD && _accurateRipMode == AccurateRipMode.VerifyThenConvert && _arVerify.AccResult == HttpStatusCode.OK)
if (fWithCUE && _accurateRipMode != AccurateRipMode.None && _arVerify.AccResult == HttpStatusCode.OK)
GenerateAccurateRipTags(destTags, _writeOffset, bestOffset, -1);
else
destTags.Add("ACCURATERIPID", _accurateRipId);
}
audioDest.SetTags(destTags);
return destTags;
}
public void WriteAudioFilesPass(string dir, CUEStyle style, string[] destPaths, int[] destLengths, bool htoaToFile, bool noOutput)
@@ -2324,11 +2384,6 @@ namespace CUETools.Processor
_appliedWriteOffset = true;
}
uint tracksMatch;
int bestOffset = _writeOffset;
if (!noOutput && _accurateRipMode == AccurateRipMode.VerifyThenConvert && _config.writeArTagsOnConvert && _arVerify.AccResult == HttpStatusCode.OK)
FindBestOffset(1, true, out tracksMatch, out bestOffset);
if (_config.detectHDCD)
{
// currently broken verifyThenConvert on HDCD detection!!!! need to check for HDCD results higher
@@ -2336,13 +2391,10 @@ namespace CUETools.Processor
catch { }
}
if (style == CUEStyle.SingleFile || style == CUEStyle.SingleFileWithCUE)
{
iDest++;
audioDest = GetAudioDest(destPaths[iDest], destLengths[iDest], hdcdDecoder != null && hdcdDecoder.Decoding ? hdcdDecoder.BitsPerSample : 16, noOutput);
if (!noOutput)
SetAlbumTags(audioDest, bestOffset, style == CUEStyle.SingleFileWithCUE);
}
uint currentOffset = 0, previousOffset = 0;
@@ -2371,8 +2423,6 @@ namespace CUETools.Processor
if (audioDest != null)
audioDest.Close();
audioDest = GetAudioDest(destPaths[iDest], destLengths[iDest], hdcdDecoder != null && hdcdDecoder.Decoding ? hdcdDecoder.BitsPerSample : 16, noOutput);
if (!noOutput)
SetTrackTags(audioDest, iTrack, bestOffset);
}
for (iIndex = 0; iIndex <= _toc[_toc.FirstAudio + iTrack].LastIndex; iIndex++)
@@ -2394,8 +2444,6 @@ namespace CUETools.Processor
audioDest.Close();
iDest++;
audioDest = GetAudioDest(destPaths[iDest], destLengths[iDest], hdcdDecoder != null && hdcdDecoder.Decoding ? hdcdDecoder.BitsPerSample : 16, noOutput);
if (!noOutput)
SetTrackTags(audioDest, iTrack, bestOffset);
}
if ((style == CUEStyle.GapsAppended) && (iIndex == 0) && (iTrack == 0))
@@ -2740,9 +2788,9 @@ namespace CUETools.Processor
} else
#endif
if (_isArchive)
audioSource = AudioReadWrite.GetAudioSource(sourceInfo.Path, OpenArchive(sourceInfo.Path, false));
audioSource = AudioReadWrite.GetAudioSource(sourceInfo.Path, OpenArchive(sourceInfo.Path, false), _config);
else
audioSource = AudioReadWrite.GetAudioSource(sourceInfo.Path, null);
audioSource = AudioReadWrite.GetAudioSource(sourceInfo.Path, null, _config);
}
if (sourceInfo.Offset != 0)
@@ -2957,6 +3005,155 @@ namespace CUETools.Processor
}
}
public class SeekableZipStream : Stream
{
ZipFile zipFile;
ZipEntry zipEntry;
Stream zipStream;
long position;
byte[] temp;
public SeekableZipStream(string path, string fileName)
{
zipFile = new ZipFile(path);
zipEntry = zipFile.GetEntry(fileName);
if (zipEntry == null)
throw new Exception("Archive entry not found.");
zipStream = zipFile.GetInputStream(zipEntry);
temp = new byte[65536];
position = 0;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return true; }
}
public override bool CanWrite
{
get { return false; }
}
public override long Length
{
get
{
return zipEntry.Size;
}
}
public override long Position
{
get { return position; }
set { Seek(value, SeekOrigin.Begin); }
}
public override void Close()
{
zipStream.Close();
zipEntry = null;
zipFile.Close();
}
public override void Flush()
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
if (position == 0 && zipEntry.IsCrypted && ((ZipInputStream)zipStream).Password == null && PasswordRequired != null)
{
PasswordRequiredEventArgs e = new PasswordRequiredEventArgs();
PasswordRequired(this, e);
if (e.ContinueOperation && e.Password.Length > 0)
((ZipInputStream)zipStream).Password = e.Password;
}
// TODO: always save to a local temp circular buffer for optimization of the backwards seek.
int total = zipStream.Read(buffer, offset, count);
position += total;
if (ExtractionProgress != null)
{
ExtractionProgressEventArgs e = new ExtractionProgressEventArgs();
e.BytesExtracted = position;
e.FileName = zipEntry.Name;
e.FileSize = zipEntry.Size;
e.PercentComplete = 100.0 * position / zipEntry.Size;
ExtractionProgress(this, e);
}
return total;
}
public override long Seek(long offset, SeekOrigin origin)
{
long seek_to;
switch (origin)
{
case SeekOrigin.Begin:
seek_to = offset;
break;
case SeekOrigin.Current:
seek_to = Position + offset;
break;
case SeekOrigin.End:
seek_to = Length + offset;
break;
default:
throw new NotSupportedException();
}
if (seek_to < 0 || seek_to > Length)
throw new IOException("Invalid seek");
if (seek_to < position)
{
zipStream.Close();
zipStream = zipFile.GetInputStream(zipEntry);
position = 0;
}
while (seek_to > position)
if (Read(temp, 0, (int) Math.Min(seek_to - position, (long) temp.Length)) <= 0)
throw new IOException("Invalid seek");
return position;
}
public override void Write(byte[] array, int offset, int count)
{
throw new NotSupportedException();
}
public event PasswordRequiredHandler PasswordRequired;
public event ExtractionProgressHandler ExtractionProgress;
}
public class ArchiveFileAbstraction : TagLib.File.IFileAbstraction
{
private string name;
private CUESheet _cueSheet;
public ArchiveFileAbstraction(CUESheet cueSheet, string file)
{
name = file;
_cueSheet = cueSheet;
}
public string Name
{
get { return name; }
}
public System.IO.Stream ReadStream
{
get { return _cueSheet.OpenArchive(Name, true); }
}
public System.IO.Stream WriteStream
{
get { return null; }
}
public void CloseStream(System.IO.Stream stream)
{
stream.Close();
}
}
public class CUELine {
private List<String> _params;
private List<bool> _quoted;
@@ -3034,11 +3231,11 @@ namespace CUETools.Processor
public class TrackInfo {
private List<CUELine> _attributes;
public NameValueCollection _trackTags;
public TagLib.File _fileInfo;
public TrackInfo() {
_attributes = new List<CUELine>();
_trackTags = new NameValueCollection();
_fileInfo = null;
}
public List<CUELine> Attributes {

View File

@@ -0,0 +1,178 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
namespace CUETools.Processor
{
public class Tagging
{
public static bool UpdateTags(TagLib.File fileInfo, NameValueCollection tags, CUEConfig config)
{
if (fileInfo is TagLib.Flac.File)
{
TagLib.Ogg.XiphComment xiph = (TagLib.Ogg.XiphComment)fileInfo.GetTag(TagLib.TagTypes.Xiph);
foreach (string tag in tags.AllKeys)
xiph.SetField(tag, tags.GetValues(tag));
return true;
}
if (fileInfo is TagLib.Riff.File)
return false;
if (fileInfo is TagLib.UserDefined.File && !(fileInfo as TagLib.UserDefined.File).SupportsAPEv2)
return false;
TagLib.Ape.Tag ape = (TagLib.Ape.Tag)fileInfo.GetTag(TagLib.TagTypes.Ape, true);
foreach (string tag in tags.AllKeys)
ape.SetValue(XiphTagNameToApe(tag), tags.GetValues(tag));
return true;
}
public static void UpdateTags(string path, NameValueCollection tags, CUEConfig config)
{
TagLib.UserDefined.AdditionalFileTypes.Config = config;
TagLib.File fileInfo = TagLib.File.Create(new TagLib.File.LocalFileAbstraction(path));
if (UpdateTags(fileInfo, tags, config))
fileInfo.Save();
//IAudioSource audioSource = AudioReadWrite.GetAudioSource(path, null, config);
//audioSource.Tags = tags;
//audioSource.UpdateTags(false);
//audioSource.Close();
//audioSource = null;
}
public static string[] GetMiscTag(TagLib.File file, string name)
{
//TagLib.Mpeg4.AppleTag apple = (TagLib.Mpeg4.AppleTag)file.GetTag(TagLib.TagTypes.Apple);
//TagLib.Id3v2.Tag id3v2 = (TagLib.Id3v2.Tag)file.GetTag(TagLib.TagTypes.Id3v2);
TagLib.Ogg.XiphComment xiph = (TagLib.Ogg.XiphComment)file.GetTag(TagLib.TagTypes.Xiph);
TagLib.Ape.Tag ape = (TagLib.Ape.Tag)file.GetTag(TagLib.TagTypes.Ape);
//if (apple != null)
//{
// string[] text = apple.GetText(name);
// if (text.Length != 0)
// return text;
//}
//if (id3v2 != null)
// foreach (TagLib.Id3v2.Frame f in id3v2.GetFrames())
// if (f is TagLib.Id3v2.TextInformationFrame && ((TagLib.Id3v2.TextInformationFrame)f).Text != null)
// return ((TagLib.Id3v2.TextInformationFrame)f).Text;
if (xiph != null)
{
string[] l = xiph.GetField(name);
if (l != null && l.Length != 0)
return l;
}
if (ape != null)
{
TagLib.Ape.Item item = ape.GetItem(name);
if (item != null)
return item.ToStringArray();
}
return null;
}
public static string TagListToSingleValue(string[] list)
{
return list == null ? null :
list.Length == 0 ? null :
list.Length == 1 ? list[0] :
null; // TODO: merge them?
}
public static string ApeTagNameToXiph(string tag)
{
if (tag.ToUpper() == "YEAR")
return "DATE";
if (tag.ToUpper() == "TRACK")
return "TRACKNUMBER";
if (tag.ToUpper() == "DISC")
return "DISCNUMBER";
return tag;
}
public static string XiphTagNameToApe(string tag)
{
if (tag.ToUpper() == "DATE")
return "Year";
if (tag.ToUpper() == "TRACKNUMBER")
return "Track";
if (tag.ToUpper() == "DISCNUMBER")
return "Disc";
return tag;
}
public static NameValueCollection Analyze(string path)
{
return Analyze(new TagLib.File.LocalFileAbstraction(path));
}
public static NameValueCollection Analyze(TagLib.File.IFileAbstraction file)
{
return Analyze(TagLib.File.Create(file));
}
public static NameValueCollection Analyze(TagLib.File fileInfo)
{
NameValueCollection tags = new NameValueCollection();
TagLib.Ogg.XiphComment xiph = (TagLib.Ogg.XiphComment)fileInfo.GetTag(TagLib.TagTypes.Xiph);
TagLib.Ape.Tag ape = (TagLib.Ape.Tag)fileInfo.GetTag(TagLib.TagTypes.Ape);
if (xiph != null)
{
foreach (string tag in xiph)
foreach (string value in xiph.GetField(tag))
tags.Add(tag, value);
}
else if (ape != null)
{
foreach (string tag in ape)
foreach (string value in ape.GetItem(tag).ToStringArray())
tags.Add(ApeTagNameToXiph(tag), value);
}
else
{
//if (audioSource is CUETools.Codecs.ALAC.ALACReader)
//tags = (audioSource as CUETools.Codecs.ALAC.ALACReader).Tags;
}
// TODO: enumerate dash atoms somehow?
//TagLib.Mpeg4.AppleTag apple = (TagLib.Mpeg4.AppleTag)fileInfo.GetTag(TagLib.TagTypes.Apple);
//if (apple != null)
//{
// tags = new NameValueCollection();
// foreach (TagLib.Mpeg4.Box tag in apple)
// if (tag.BoxType == "----")
// foreach (string value in apple.GetDashBox(tag.)
// tags.Add(tag, value);
//}
return tags;
}
//public void SetTextField(TagLib.File file,
// TagLib.ByteVector apple_name, TagLib.ByteVector id3v2_name,
// string xiph_name, string ape_name, string[] values)
//{
// TagLib.Mpeg4.AppleTag apple = (TagLib.Mpeg4.AppleTag)file.GetTag(TagLib.TagTypes.Apple, true);
// TagLib.Id3v2.Tag id3v2 = (TagLib.Id3v2.Tag)file.GetTag(TagLib.TagTypes.Id3v2, true);
// TagLib.Ogg.XiphComment xiph = (TagLib.Ogg.XiphComment)file.GetTag(TagLib.TagTypes.Xiph, true);
// TagLib.Ape.Tag ape = (TagLib.Ape.Tag)file.GetTag(TagLib.TagTypes.Ape, (file is TagLib.Mpc.File));
// if (apple != null)
// apple.SetText(apple_name, values);
// if (id3v2 != null)
// id3v2.SetTextFrame(id3v2_name, new TagLib.StringList(values));
// if (xiph != null)
// xiph.AddFields(xiph_name, values);
// if (ape != null)
// ape.AddValues(ape_name, values, true);
//}
}
}

View File

@@ -0,0 +1,319 @@
//
// File.cs: Provides tagging and properties support for WavPack files.
//
// Author:
// Brian Nickel (brian.nickel@gmail.com)
//
// Original Source:
// wvfile.cpp from libtunepimp
//
// Copyright (C) 2006-2007 Brian Nickel
// Copyright (C) 2006 by Lukáš Lalinský (Original Implementation)
// Copyright (C) 2004 by Allan Sandfeld Jensen (Original Implementation)
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License version
// 2.1 as published by the Free Software Foundation.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
//
using System;
using TagLib;
namespace TagLib.UserDefined {
/// <summary>
/// This class extends <see cref="TagLib.NonContainer.File" /> to
/// provide tagging and properties support for user defined format files.
/// </summary>
/// <remarks>
/// A <see cref="TagLib.Ape.Tag" /> will be added automatically to
/// any file that doesn't contain one. This change does not effect
/// the file and can be reversed using the following method:
/// <code>file.RemoveTags (file.TagTypes &amp; ~file.TagTypesOnDisk);</code>
/// </remarks>
[SupportedMimeType("taglib/misc", "misc")]
public class File : TagLib.NonContainer.File
{
#region Private Fields
private bool _supportsAPEv2 = true;
#endregion
#region Constructors
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="File" /> for a specified path in the local file
/// system and specified read style.
/// </summary>
/// <param name="path">
/// A <see cref="string" /> object containing the path of the
/// file to use in the new instance.
/// </param>
/// <param name="propertiesStyle">
/// A <see cref="ReadStyle" /> value specifying at what level
/// of accuracy to read the media properties, or <see
/// cref="ReadStyle.None" /> to ignore the properties.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="path" /> is <see langword="null" />.
/// </exception>
public File (string path, ReadStyle propertiesStyle, bool supportsAPEv2)
: base (path, propertiesStyle)
{
_supportsAPEv2 = supportsAPEv2;
}
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="File" /> for a specified path in the local file
/// system with an average read style.
/// </summary>
/// <param name="path">
/// A <see cref="string" /> object containing the path of the
/// file to use in the new instance.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="path" /> is <see langword="null" />.
/// </exception>
public File (string path, bool supportsAPEv2) : base (path)
{
_supportsAPEv2 = supportsAPEv2;
}
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="File" /> for a specified file abstraction and
/// specified read style.
/// </summary>
/// <param name="abstraction">
/// A <see cref="IFileAbstraction" /> object to use when
/// reading from and writing to the file.
/// </param>
/// <param name="propertiesStyle">
/// A <see cref="ReadStyle" /> value specifying at what level
/// of accuracy to read the media properties, or <see
/// cref="ReadStyle.None" /> to ignore the properties.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="abstraction" /> is <see langword="null"
/// />.
/// </exception>
public File (File.IFileAbstraction abstraction,
ReadStyle propertiesStyle, bool supportsAPEv2)
: base (abstraction, propertiesStyle)
{
_supportsAPEv2 = supportsAPEv2;
}
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="File" /> for a specified file abstraction with an
/// average read style.
/// </summary>
/// <param name="abstraction">
/// A <see cref="IFileAbstraction" /> object to use when
/// reading from and writing to the file.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="abstraction" /> is <see langword="null"
/// />.
/// </exception>
public File (File.IFileAbstraction abstraction, bool supportsAPEv2)
: base (abstraction)
{
_supportsAPEv2 = supportsAPEv2;
}
#endregion
#region Public Methods
public bool SupportsAPEv2
{
get
{
return _supportsAPEv2;
}
}
/// <summary>
/// Gets a tag of a specified type from the current instance,
/// optionally creating a new tag if possible.
/// </summary>
/// <param name="type">
/// A <see cref="TagLib.TagTypes" /> value indicating the
/// type of tag to read.
/// </param>
/// <param name="create">
/// A <see cref="bool" /> value specifying whether or not to
/// try and create the tag if one is not found.
/// </param>
/// <returns>
/// A <see cref="Tag" /> object containing the tag that was
/// found in or added to the current instance. If no
/// matching tag was found and none was created, <see
/// langword="null" /> is returned.
/// </returns>
/// <remarks>
/// If a <see cref="TagLib.Id3v2.Tag" /> is added to the
/// current instance, it will be placed at the start of the
/// file. On the other hand, <see cref="TagLib.Id3v1.Tag" />
/// <see cref="TagLib.Ape.Tag" /> will be added to the end of
/// the file. All other tag types will be ignored.
/// </remarks>
public override TagLib.Tag GetTag (TagTypes type, bool create)
{
Tag t = (Tag as TagLib.NonContainer.Tag).GetTag (type);
if (t != null || !create)
return t;
switch (type)
{
case TagTypes.Id3v1:
return EndTag.AddTag (type, Tag);
case TagTypes.Id3v2:
return StartTag.AddTag (type, Tag);
case TagTypes.Ape:
return EndTag.AddTag (type, Tag);
default:
return null;
}
}
#endregion
#region Protected Methods
/// <summary>
/// Reads format specific information at the start of the
/// file.
/// </summary>
/// <param name="start">
/// A <see cref="long" /> value containing the seek position
/// at which the tags end and the media data begins.
/// </param>
/// <param name="propertiesStyle">
/// A <see cref="ReadStyle" /> value specifying at what level
/// of accuracy to read the media properties, or <see
/// cref="ReadStyle.None" /> to ignore the properties.
/// </param>
protected override void ReadStart (long start,
ReadStyle propertiesStyle)
{
}
/// <summary>
/// Reads format specific information at the end of the
/// file.
/// </summary>
/// <param name="end">
/// A <see cref="long" /> value containing the seek position
/// at which the media data ends and the tags begin.
/// </param>
/// <param name="propertiesStyle">
/// A <see cref="ReadStyle" /> value specifying at what level
/// of accuracy to read the media properties, or <see
/// cref="ReadStyle.None" /> to ignore the properties.
/// </param>
protected override void ReadEnd (long end,
ReadStyle propertiesStyle)
{
// Make sure we have an APE tag.
if (_supportsAPEv2)
GetTag (TagTypes.Ape, true);
}
/// <summary>
/// Reads the audio properties from the file represented by
/// the current instance.
/// </summary>
/// <param name="start">
/// A <see cref="long" /> value containing the seek position
/// at which the tags end and the media data begins.
/// </param>
/// <param name="end">
/// A <see cref="long" /> value containing the seek position
/// at which the media data ends and the tags begin.
/// </param>
/// <param name="propertiesStyle">
/// A <see cref="ReadStyle" /> value specifying at what level
/// of accuracy to read the media properties, or <see
/// cref="ReadStyle.None" /> to ignore the properties.
/// </param>
/// <returns>
/// A <see cref="TagLib.Properties" /> object describing the
/// media properties of the file represented by the current
/// instance.
/// </returns>
protected override Properties ReadProperties (long start,
long end,
ReadStyle propertiesStyle)
{
return new Properties ();
}
#endregion
}
public static class AdditionalFileTypes
{
private static bool inited = false;
private static CUETools.Processor.CUEConfig _config;
public static CUETools.Processor.CUEConfig Config
{
set
{
Init();
_config = value;
}
}
private static TagLib.File UserDefinedResolver(TagLib.File.IFileAbstraction abstraction, string mimetype, TagLib.ReadStyle style)
{
if (mimetype == "taglib/flac" || mimetype == "taglib/wv" || mimetype == "taglib/ape" || mimetype == "taglib/wav")
return null;
if (mimetype == "taglib/tta")
return new File(abstraction, style, true);
if (mimetype == "taglib/" + _config.udc1Extension)
return new File(abstraction, style, _config.udc1APEv2);
return null;
}
static AdditionalFileTypes ()
{
Init();
}
internal static void Init()
{
if (inited)
return;
TagLib.File.AddFileTypeResolver(new TagLib.File.FileTypeResolver(UserDefinedResolver));
//FileTypes.Register(typeof(TagLib.NonContainer.File));
inited = true;
}
}
}