parent
fa25324463
commit
404da4ae22
@ -1,37 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
|
||||||
using NzbDrone.Test.Common;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.ParserTests
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class MusicParserFixture : CoreTest
|
|
||||||
{
|
|
||||||
//[TestCase("___▲▲▲___")]
|
|
||||||
//[TestCase("Add N to (X)")]
|
|
||||||
//[TestCase("Animal Collective")]
|
|
||||||
//[TestCase("D12")]
|
|
||||||
//[TestCase("David Sylvian[Discography]")]
|
|
||||||
//[TestCase("Eagle-Eye Cherry")]
|
|
||||||
//[TestCase("Erlend Øye")]
|
|
||||||
//[TestCase("Adult.")] // Not sure if valid, not openable in Windows OS
|
|
||||||
//[TestCase("Maroon 5")]
|
|
||||||
//[TestCase("Moimir Papalescu & The Nihilists")]
|
|
||||||
//[TestCase("N.W.A")]
|
|
||||||
//[TestCase("oOoOO")]
|
|
||||||
//[TestCase("Panic! at the Disco")]
|
|
||||||
//[TestCase("The 5 6 7 8's")]
|
|
||||||
//[TestCase("tUnE-yArDs")]
|
|
||||||
//[TestCase("U2")]
|
|
||||||
//[TestCase("Белые Братья")]
|
|
||||||
//[TestCase("Zog Bogbean - From The Marcy Playground")]
|
|
||||||
|
|
||||||
// TODO: Rewrite this test to something that makes sense.
|
|
||||||
public void should_parse_author_names(string title)
|
|
||||||
{
|
|
||||||
Parser.Parser.ParseTitle(title).AuthorTitle.Should().Be(title);
|
|
||||||
ExceptionVerification.IgnoreWarns();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.Azw
|
||||||
|
{
|
||||||
|
public class AzwFile
|
||||||
|
{
|
||||||
|
public byte[] RawData { get; }
|
||||||
|
public ushort SectionCount { get; private set; }
|
||||||
|
public SectionInfo[] Info { get; private set; }
|
||||||
|
public string Ident { get; private set; }
|
||||||
|
|
||||||
|
protected AzwFile(string path)
|
||||||
|
{
|
||||||
|
RawData = File.ReadAllBytes(path);
|
||||||
|
GetSectionInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void GetSectionInfo()
|
||||||
|
{
|
||||||
|
Ident = Encoding.ASCII.GetString(RawData, 0x3c, 8);
|
||||||
|
SectionCount = Math.Min(Util.GetUInt16(RawData, 76), (ushort)1);
|
||||||
|
|
||||||
|
if (Ident != "BOOKMOBI" || SectionCount == 0)
|
||||||
|
{
|
||||||
|
throw new AzwTagException("Invalid mobi header");
|
||||||
|
}
|
||||||
|
|
||||||
|
Info = new SectionInfo[SectionCount];
|
||||||
|
Info[0].Start_addr = Util.GetUInt32(RawData, 78);
|
||||||
|
Info[0].End_addr = Util.GetUInt32(RawData, 78 + 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected byte[] GetSectionData(uint i)
|
||||||
|
{
|
||||||
|
return Util.SubArray(RawData, Info[i].Start_addr, Info[i].Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.Azw
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class AzwTagException : Exception
|
||||||
|
{
|
||||||
|
public AzwTagException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AzwTagException(System.Runtime.Serialization.SerializationInfo info,
|
||||||
|
System.Runtime.Serialization.StreamingContext context)
|
||||||
|
: base(info, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Instrumentation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.Azw
|
||||||
|
{
|
||||||
|
public class ExtMeta
|
||||||
|
{
|
||||||
|
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(ExtMeta));
|
||||||
|
|
||||||
|
public Dictionary<uint, ulong> IdValue { get; } = new Dictionary<uint, ulong>();
|
||||||
|
public Dictionary<uint, List<string>> IdString { get; } = new Dictionary<uint, List<string>>();
|
||||||
|
public Dictionary<uint, string> IdHex { get; } = new Dictionary<uint, string>();
|
||||||
|
|
||||||
|
public ExtMeta(byte[] ext, Encoding encoding)
|
||||||
|
{
|
||||||
|
var num_items = Util.GetUInt32(ext, 8);
|
||||||
|
uint pos = 12;
|
||||||
|
for (var i = 0; i < num_items; i++)
|
||||||
|
{
|
||||||
|
var id = Util.GetUInt32(ext, pos);
|
||||||
|
var size = Util.GetUInt32(ext, pos + 4);
|
||||||
|
if (IdMapping.Id_map_strings.ContainsKey(id))
|
||||||
|
{
|
||||||
|
var a = encoding.GetString(Util.SubArray(ext, pos + 8, size - 8));
|
||||||
|
|
||||||
|
if (IdString.ContainsKey(id))
|
||||||
|
{
|
||||||
|
IdString[id].Add(a);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IdString.Add(id, new List<string> { a });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (IdMapping.Id_map_values.ContainsKey(id))
|
||||||
|
{
|
||||||
|
ulong a = 0;
|
||||||
|
switch (size)
|
||||||
|
{
|
||||||
|
case 9:
|
||||||
|
a = Util.GetUInt8(ext, pos + 8);
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
a = Util.GetUInt16(ext, pos + 8);
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
a = Util.GetUInt32(ext, pos + 8);
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
a = Util.GetUInt64(ext, pos + 8);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Logger.Warn("unexpected size:" + size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IdValue.ContainsKey(id))
|
||||||
|
{
|
||||||
|
Logger.Debug("Meta id duplicate:{0}\nPervious:{1} \nLatter:{2}", IdMapping.Id_map_values[id], IdValue[id], a);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IdValue.Add(id, a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (IdMapping.Id_map_hex.ContainsKey(id))
|
||||||
|
{
|
||||||
|
var a = Util.ToHexString(ext, pos + 8, size - 8);
|
||||||
|
|
||||||
|
if (IdHex.ContainsKey(id))
|
||||||
|
{
|
||||||
|
Logger.Debug("Meta id duplicate:{0}\nPervious:{1} \nLatter:{2}", IdMapping.Id_map_hex[id], IdHex[id], a);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IdHex.Add(id, a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Unknown id
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string StringOrNull(uint key)
|
||||||
|
{
|
||||||
|
return IdString.TryGetValue(key, out var value) ? value.FirstOrDefault() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> StringList(uint key)
|
||||||
|
{
|
||||||
|
return IdString.TryGetValue(key, out var value) ? value : new List<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,135 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.Azw
|
|
||||||
{
|
|
||||||
public class ExtMeta
|
|
||||||
{
|
|
||||||
public Dictionary<uint, ulong> IdValue;
|
|
||||||
public Dictionary<uint, string> IdString;
|
|
||||||
public Dictionary<uint, string> IdHex;
|
|
||||||
|
|
||||||
public ExtMeta(byte[] ext, Encoding encoding)
|
|
||||||
{
|
|
||||||
IdValue = new Dictionary<uint, ulong>();
|
|
||||||
IdString = new Dictionary<uint, string>();
|
|
||||||
IdHex = new Dictionary<uint, string>();
|
|
||||||
|
|
||||||
var num_items = Util.GetUInt32(ext, 8);
|
|
||||||
uint pos = 12;
|
|
||||||
for (var i = 0; i < num_items; i++)
|
|
||||||
{
|
|
||||||
var id = Util.GetUInt32(ext, pos);
|
|
||||||
var size = Util.GetUInt32(ext, pos + 4);
|
|
||||||
if (IdMapping.Id_map_strings.ContainsKey(id))
|
|
||||||
{
|
|
||||||
var a = encoding.GetString(Util.SubArray(ext, pos + 8, size - 8));
|
|
||||||
|
|
||||||
if (IdString.ContainsKey(id))
|
|
||||||
{
|
|
||||||
if (id == 100 || id == 517)
|
|
||||||
{
|
|
||||||
IdString[id] += "&" + a;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine(string.Format("Meta id duplicate:{0}\nPervious:{1} \nLatter:{2}", IdMapping.Id_map_strings[id], IdString[id], a));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
IdString.Add(id, a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (IdMapping.Id_map_values.ContainsKey(id))
|
|
||||||
{
|
|
||||||
ulong a = 0;
|
|
||||||
switch (size)
|
|
||||||
{
|
|
||||||
case 9: a = Util.GetUInt8(ext, pos + 8); break;
|
|
||||||
case 10: a = Util.GetUInt16(ext, pos + 8); break;
|
|
||||||
case 12: a = Util.GetUInt32(ext, pos + 8); break;
|
|
||||||
case 16: a = Util.GetUInt64(ext, pos + 8); break;
|
|
||||||
default: Console.WriteLine("unexpected size:" + size); break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IdValue.ContainsKey(id))
|
|
||||||
{
|
|
||||||
Console.WriteLine(string.Format("Meta id duplicate:{0}\nPervious:{1} \nLatter:{2}", IdMapping.Id_map_values[id], IdValue[id], a));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
IdValue.Add(id, a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (IdMapping.Id_map_hex.ContainsKey(id))
|
|
||||||
{
|
|
||||||
var a = Util.ToHexString(ext, pos + 8, size - 8);
|
|
||||||
|
|
||||||
if (IdHex.ContainsKey(id))
|
|
||||||
{
|
|
||||||
Console.WriteLine(string.Format("Meta id duplicate:{0}\nPervious:{1} \nLatter:{2}", IdMapping.Id_map_hex[id], IdHex[id], a));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
IdHex.Add(id, a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Unknown id
|
|
||||||
}
|
|
||||||
|
|
||||||
pos += size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string StringOrNull(uint key)
|
|
||||||
{
|
|
||||||
return IdString.TryGetValue(key, out var value) ? value : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MobiHeader : Section
|
|
||||||
{
|
|
||||||
private readonly uint _length;
|
|
||||||
private readonly uint _codepage;
|
|
||||||
private readonly uint _exth_flag;
|
|
||||||
|
|
||||||
public MobiHeader(byte[] header)
|
|
||||||
: base("Mobi Header", header)
|
|
||||||
{
|
|
||||||
var mobi = Encoding.ASCII.GetString(header, 16, 4);
|
|
||||||
if (mobi != "MOBI")
|
|
||||||
{
|
|
||||||
throw new AzwTagException("Invalid mobi header");
|
|
||||||
}
|
|
||||||
|
|
||||||
Version = Util.GetUInt32(header, 36);
|
|
||||||
MobiType = Util.GetUInt32(header, 24);
|
|
||||||
|
|
||||||
_codepage = Util.GetUInt32(header, 28);
|
|
||||||
|
|
||||||
var encoding = _codepage == 65001 ? Encoding.UTF8 : CodePagesEncodingProvider.Instance.GetEncoding((int)_codepage);
|
|
||||||
Title = encoding.GetString(header, (int)Util.GetUInt32(header, 0x54), (int)Util.GetUInt32(header, 0x58));
|
|
||||||
|
|
||||||
_exth_flag = Util.GetUInt32(header, 0x80);
|
|
||||||
_length = Util.GetUInt32(header, 20);
|
|
||||||
if ((_exth_flag & 0x40) > 0)
|
|
||||||
{
|
|
||||||
var exth = Util.SubArray(header, _length + 16, Util.GetUInt32(header, _length + 20));
|
|
||||||
ExtMeta = new ExtMeta(exth, encoding);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new AzwTagException("No EXTH header. Readarr cannot process this file.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Title { get; private set; }
|
|
||||||
public uint Version { get; private set; }
|
|
||||||
public uint MobiType { get; private set; }
|
|
||||||
public ExtMeta ExtMeta { get; private set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +1,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.Azw
|
namespace NzbDrone.Core.MediaFiles.Azw
|
||||||
{
|
{
|
||||||
public struct SectionInfo
|
public static class IdMapping
|
||||||
{
|
|
||||||
public ulong Start_addr;
|
|
||||||
public ulong End_addr;
|
|
||||||
|
|
||||||
public ulong Length => End_addr - Start_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AzwFile
|
|
||||||
{
|
|
||||||
public byte[] Raw_data;
|
|
||||||
public ushort Section_count;
|
|
||||||
public SectionInfo[] Section_info;
|
|
||||||
public string Ident;
|
|
||||||
|
|
||||||
protected AzwFile(string path)
|
|
||||||
{
|
|
||||||
Raw_data = File.ReadAllBytes(path);
|
|
||||||
GetSectionInfo();
|
|
||||||
|
|
||||||
if (Ident != "BOOKMOBI" || Section_count == 0)
|
|
||||||
{
|
|
||||||
throw new AzwTagException("Invalid mobi header");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void GetSectionInfo()
|
|
||||||
{
|
|
||||||
Ident = Encoding.ASCII.GetString(Raw_data, 0x3c, 8);
|
|
||||||
Section_count = Util.GetUInt16(Raw_data, 76);
|
|
||||||
Section_info = new SectionInfo[Section_count];
|
|
||||||
|
|
||||||
Section_info[0].Start_addr = Util.GetUInt32(Raw_data, 78);
|
|
||||||
for (uint i = 1; i < Section_count; i++)
|
|
||||||
{
|
|
||||||
Section_info[i].Start_addr = Util.GetUInt32(Raw_data, 78 + (i * 8));
|
|
||||||
Section_info[i - 1].End_addr = Section_info[i].Start_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Section_info[Section_count - 1].End_addr = (ulong)Raw_data.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected byte[] GetSectionData(uint i)
|
|
||||||
{
|
|
||||||
return Util.SubArray(Raw_data, Section_info[i].Start_addr, Section_info[i].Length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class IdMapping
|
|
||||||
{
|
{
|
||||||
public static Dictionary<uint, string> Id_map_strings = new Dictionary<uint, string>
|
public static Dictionary<uint, string> Id_map_strings = new Dictionary<uint, string>
|
||||||
{
|
{
|
@ -0,0 +1,41 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.Azw
|
||||||
|
{
|
||||||
|
public class MobiHeader
|
||||||
|
{
|
||||||
|
public MobiHeader(byte[] header)
|
||||||
|
{
|
||||||
|
var mobi = Encoding.ASCII.GetString(header, 16, 4);
|
||||||
|
if (mobi != "MOBI")
|
||||||
|
{
|
||||||
|
throw new AzwTagException("Invalid mobi header");
|
||||||
|
}
|
||||||
|
|
||||||
|
Version = Util.GetUInt32(header, 36);
|
||||||
|
MobiType = Util.GetUInt32(header, 24);
|
||||||
|
|
||||||
|
var codepage = Util.GetUInt32(header, 28);
|
||||||
|
|
||||||
|
var encoding = codepage == 65001 ? Encoding.UTF8 : CodePagesEncodingProvider.Instance.GetEncoding((int)codepage);
|
||||||
|
Title = encoding.GetString(header, (int)Util.GetUInt32(header, 0x54), (int)Util.GetUInt32(header, 0x58));
|
||||||
|
|
||||||
|
var exthFlag = Util.GetUInt32(header, 0x80);
|
||||||
|
var length = Util.GetUInt32(header, 20);
|
||||||
|
if ((exthFlag & 0x40) > 0)
|
||||||
|
{
|
||||||
|
var exth = Util.SubArray(header, length + 16, Util.GetUInt32(header, length + 20));
|
||||||
|
ExtMeta = new ExtMeta(exth, encoding);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new AzwTagException("No EXTH header. Readarr cannot process this file.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Title { get; }
|
||||||
|
public uint Version { get; }
|
||||||
|
public uint MobiType { get; }
|
||||||
|
public ExtMeta ExtMeta { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,47 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.Azw
|
|
||||||
{
|
|
||||||
public class Section
|
|
||||||
{
|
|
||||||
public string Type;
|
|
||||||
public byte[] Raw;
|
|
||||||
public string Comment = "";
|
|
||||||
|
|
||||||
public Section(byte[] raw)
|
|
||||||
{
|
|
||||||
Raw = raw;
|
|
||||||
if (raw.Length < 4)
|
|
||||||
{
|
|
||||||
Type = "Empty Section";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type = Encoding.ASCII.GetString(raw, 0, 4);
|
|
||||||
|
|
||||||
switch (Type)
|
|
||||||
{
|
|
||||||
case "??\r\n": Type = "End Of File"; break;
|
|
||||||
case "?6?\t": Type = "Place Holder"; break;
|
|
||||||
case "\0\0\0\0": Type = "Empty Section0"; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Section(Section s)
|
|
||||||
{
|
|
||||||
Type = s.Type;
|
|
||||||
Raw = s.Raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Section(string type, byte[] raw)
|
|
||||||
{
|
|
||||||
Type = type;
|
|
||||||
Raw = raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual int GetSize()
|
|
||||||
{
|
|
||||||
return Raw.Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,10 @@
|
|||||||
|
namespace NzbDrone.Core.MediaFiles.Azw
|
||||||
|
{
|
||||||
|
public struct SectionInfo
|
||||||
|
{
|
||||||
|
public ulong Start_addr;
|
||||||
|
public ulong End_addr;
|
||||||
|
|
||||||
|
public ulong Length => End_addr - Start_addr;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue