Exemple #1
0
 private void TagCheckTextIsSame(IList <FlacFormat> flacs, string key)
 {
     if (FlacFormat.IsFlacMultiTagAllSame(flacs, key) == false)
     {
         IssueModel.Add(key + " tags are inconsistent.", Severity.Warning, IssueTags.BadTag | IssueTags.StrictErr);
     }
 }
Exemple #2
0
            public void CheckFlacRipTags(IList <FlacFormat> flacs)
            {
                int prevTrackNum = -1;

                foreach (FlacFormat flac in flacs)
                {
                    var trackTag = flac.GetTagValue("TRACKNUMBER");

                    var             integerRegex    = new Regex("^([0-9]+)", RegexOptions.Compiled);
                    MatchCollection reMatches       = integerRegex.Matches(trackTag);
                    string          trackTagCapture = reMatches.Count == 1 ? reMatches[0].Groups[1].ToString() : trackTag;

                    if (int.TryParse(trackTagCapture, out int trackNum))
                    {
                        if (prevTrackNum >= 0 && trackNum != prevTrackNum + 1)
                        {
                            IssueModel.Add($"Gap in TRACKNUMBER tags near '{trackTag}'.", Severity.Error, IssueTags.BadTag);
                        }
                        prevTrackNum = trackNum;
                    }
                }

                TagCheckTextIsSame(flacs, "TRACKTOTAL");
                TagCheckTextIsSame(flacs, "DISCNUMBER");
                TagCheckTextIsSame(flacs, "DISCTOTAL");
                TagCheckTextIsSame(flacs, "DATE");
                TagCheckTextIsSame(flacs, "RELEASE DATE");

                bool?isSameAA = FlacFormat.IsFlacTagsAllSame(flacs, "ALBUMARTIST");

                if (isSameAA == false)
                {
                    IssueModel.Add("ALBUMARTIST tags are inconsistent.", Severity.Warning, IssueTags.BadTag | IssueTags.StrictErr);
                }
                else if (isSameAA == null)
                {
                    bool?isSameArtist = FlacFormat.IsFlacTagsAllSame(flacs, "ARTIST");
                    if (isSameArtist == false)
                    {
                        IssueModel.Add("Inconsistent ARTIST tag or missing ALBUMARTIST tag.", Severity.Warning, IssueTags.BadTag);
                    }
                    else if (isSameArtist == null)
                    {
                        IssueModel.Add("ARTIST is missing.", Severity.Warning, IssueTags.BadTag);
                    }
                }

                TagCheckTextIsSame(flacs, "ALBUM");
                TagCheckTextIsSame(flacs, "ORGANIZATION");
                TagCheckTextIsSame(flacs, "BARCODE");
                TagCheckTextIsSame(flacs, "CATALOGNUMBER");
                TagCheckTextIsSame(flacs, "ALBUMARTISTSORTORDER");
            }
Exemple #3
0
            public Model(Stream stream, byte[] hdr, string path)
            {
                base._data = Data = new FlacFormat(this, stream, path);

                Data.MetadataBlockStreamInfoSize = ConvertTo.FromBig24ToInt32(hdr, 5);
                if (Data.MetadataBlockStreamInfoSize < 34)
                {
                    IssueModel.Add($"Bad metablock size of {Data.MetadataBlockStreamInfoSize}", Severity.Fatal);
                    return;
                }

                var bb = new byte[Data.MetadataBlockStreamInfoSize];

                Data.ValidSize = 8;

                Data.fbs.Position = Data.ValidSize;
                var got = Data.fbs.Read(bb, 0, Data.MetadataBlockStreamInfoSize);

                if (got != Data.MetadataBlockStreamInfoSize)
                {
                    IssueModel.Add("File truncated", Severity.Fatal);
                    return;
                }

                Data.MinBlockSize = ConvertTo.FromBig16ToInt32(bb, 0);
                Data.MinBlockSize = ConvertTo.FromBig16ToInt32(bb, 2);
                Data.MinFrameSize = ConvertTo.FromBig24ToInt32(bb, 4);
                Data.MaxFrameSize = ConvertTo.FromBig24ToInt32(bb, 7);

                Data.MetaSampleRate = bb[10] << 12 | bb[11] << 4 | bb[12] >> 4;
                Data.ChannelCount   = ((bb[12] & 0x0E) >> 1) + 1;
                Data.BitsPerSample  = (((bb[12] & 1) << 4) | (bb[13] >> 4)) + 1;
                Data.TotalSamples   = ((bb[13] & 0x0F) << 32) | bb[14] << 24 | bb[15] << 16 | bb[16] << 8 | bb[17];

                Data.storedAudioDataMD5 = new byte[16];
                Array.Copy(bb, 18, Data.storedAudioDataMD5, 0, 16);

                Data.ValidSize += Data.MetadataBlockStreamInfoSize;

                for (;;)
                {
                    bb = new byte[12];
                    try
                    {
                        Data.fbs.Position = Data.ValidSize;
                    }
                    catch (EndOfStreamException)
                    {
                        IssueModel.Add("File truncated near meta data", Severity.Fatal);
                        return;
                    }

                    Data.fbs.Position = Data.ValidSize;
                    got = Data.fbs.Read(bb, 0, 4);
                    if (got != 4)
                    {
                        IssueModel.Add("File truncated near meta data", Severity.Fatal);
                        return;
                    }

                    if (bb[0] == 0xFF)
                    {
                        break;
                    }

                    int blockSize = ConvertTo.FromBig24ToInt32(bb, 1);
                    Data.ValidSize += 4;

                    switch ((FlacBlockType)(bb[0] & 0x7F))
                    {
                    case FlacBlockType.Padding:
                        Data.Blocks.AddPad((int)Data.ValidSize, blockSize);
                        break;

                    case FlacBlockType.Application:
                        got = Data.fbs.Read(bb, 0, 4);
                        if (got != 4)
                        {
                            IssueModel.Add("File truncated near tags", Severity.Fatal);
                            return;
                        }
                        int appId = ConvertTo.FromBig32ToInt32(bb, 0);
                        Data.Blocks.AddApp((int)Data.ValidSize, blockSize, appId);
                        break;

                    case FlacBlockType.SeekTable:
                        var st = new byte[blockSize];
                        got = Data.fbs.Read(st, 0, blockSize);
                        if (got != blockSize)
                        {
                            IssueModel.Add("File truncated near seek table", Severity.Fatal);
                            return;
                        }
                        Data.Blocks.AddSeekTable((int)Data.ValidSize, blockSize, st);
                        break;

                    case FlacBlockType.Tags:
                        bb = new byte[blockSize];
                        Data.fbs.Position = Data.ValidSize;
                        got = Data.fbs.Read(bb, 0, blockSize);
                        if (got != blockSize)
                        {
                            IssueModel.Add("File truncated near tags", Severity.Fatal);
                            return;
                        }
                        if (Data.Blocks.Tags != null)
                        {
                            IssueModel.Add("Contains multiple tag blocks", Severity.Error);
                        }
                        else
                        {
                            Data.Blocks.AddTags((int)Data.ValidSize, blockSize, bb);
                        }
                        break;

                    case FlacBlockType.CueSheet:
                        var sb = new byte[284];
                        got = Data.fbs.Read(sb, 0, 284);
                        if (got != 284)
                        {
                            IssueModel.Add("File truncated near cuesheet", Severity.Fatal);
                            return;
                        }
                        var isCD       = (sb[24] & 0x80) != 0;
                        int trackCount = sb[283];
                        Data.Blocks.AddCuesheet((int)Data.ValidSize, blockSize, isCD, trackCount);
                        break;

                    case FlacBlockType.Picture:
                        var pb = new byte[blockSize];
                        got = Data.fbs.Read(pb, 0, blockSize);
                        if (got != blockSize)
                        {
                            IssueModel.Add("File truncated near picture", Severity.Fatal);
                            return;
                        }
                        var picType = (PicType)ConvertTo.FromBig32ToInt32(pb, 0);
                        var mimeLen = ConvertTo.FromBig32ToInt32(pb, 4);
                        var mime    = Encoding.UTF8.GetString(pb, 8, mimeLen);
                        var descLen = ConvertTo.FromBig32ToInt32(pb, mimeLen + 8);
                        var desc    = Encoding.UTF8.GetString(pb, mimeLen + 12, descLen);
                        var width   = ConvertTo.FromBig32ToInt32(pb, mimeLen + descLen + 12);
                        var height  = ConvertTo.FromBig32ToInt32(pb, mimeLen + descLen + 16);
                        Data.Blocks.AddPic((int)Data.ValidSize, blockSize, picType, width, height);
                        break;

                    case FlacBlockType.Invalid:
                        IssueModel.Add("Encountered invalid block type", Severity.Fatal);
                        return;

                    default:
                        IssueModel.Add($"Encountered reserved block type '{bb[0]}'", Severity.Warning);
                        break;
                    }

                    Data.ValidSize += blockSize;
                }

                try
                {
                    Data.fbs.Position = Data.ValidSize;
                }
                catch (EndOfStreamException)
                {
                    IssueModel.Add("File truncated near frame header", Severity.Fatal);
                    return;
                }
                got = Data.fbs.Read(bb, 0, 4);
                if (got != 4)
                {
                    IssueModel.Add("File truncated", Severity.Fatal);
                    return;
                }

                // Detect frame header sync code
                if (bb[0] != 0xFF || (bb[1] & 0xFC) != 0xF8)
                {
                    IssueModel.Add("Audio data not found", Severity.Fatal);
                    return;
                }

                Data.mediaPosition = Data.ValidSize;

                Data.SampleOrFrameNumber = Data.fbs.ReadWobbly(out byte[] wtfBuf);
                if (Data.SampleOrFrameNumber < 0)
                {
                    IssueModel.Add("File truncated or badly formed sample/frame number.", Severity.Fatal);
                    return;
                }
                Array.Copy(wtfBuf, 0, bb, 4, wtfBuf.Length);
                int bPos = 4 + wtfBuf.Length;

                Data.RawBlockingStrategy = bb[1] & 1;

                Data.RawBlockSize = bb[2] >> 4;
                if (Data.RawBlockSize == 0)
                {
                    Data.BlockSize = 0;
                }
                else if (Data.RawBlockSize == 1)
                {
                    Data.BlockSize = 192;
                }
                else if (Data.RawBlockSize >= 2 && Data.RawBlockSize <= 5)
                {
                    Data.BlockSize = 576 * (1 << (Data.RawBlockSize - 2));
                }
                else if (Data.RawBlockSize == 6)
                {
                    got            = Data.fbs.Read(bb, bPos, 1);
                    Data.BlockSize = bb[bPos] + 1;
                    bPos          += 1;
                }
                else if (Data.RawBlockSize == 7)
                {
                    got            = Data.fbs.Read(bb, bPos, 2);
                    Data.BlockSize = (bb[bPos] << 8) + bb[bPos + 1] + 1;
                    bPos          += 2;
                }
                else
                {
                    Data.BlockSize = 256 * (1 << (Data.RawBlockSize - 8));
                }


                Data.RawSampleRate = bb[2] & 0xF;
                if (Data.RawSampleRate == 0xC)
                {
                    got = Data.fbs.Read(bb, bPos, 1);
                    Data.SampleRateText = bb[bPos] + "kHz";
                    bPos += 1;
                }
                else if (Data.RawSampleRate == 0xD || Data.RawSampleRate == 0xE)
                {
                    got = Data.fbs.Read(bb, bPos, 2);
                    Data.SampleRateText = (bb[bPos] << 8).ToString() + bb[bPos + 1] + (Data.RawSampleRate == 0xD? " Hz" : " kHz");
                    bPos += 2;
                }
                else if (Data.RawSampleRate == 0)
                {
                    Data.SampleRateText = Data.MetaSampleRate.ToString() + " Hz";
                }
                else
                {
                    Data.SampleRateText = SampleRateMap[Data.RawSampleRate];
                }

                Data.RawChannelAssignment = bb[3] >> 4;

                Data.RawSampleSize = (bb[3] & 0xE) >> 1;
                if (Data.RawSampleSize == 0)
                {
                    Data.SampleSizeText = Data.BitsPerSample.ToString() + " bits";
                }
                else
                {
                    Data.SampleSizeText = SampleSizeMap[Data.RawSampleSize];
                }

                Data.aHdr = new byte[bPos];
                Array.Copy(bb, Data.aHdr, bPos);

                Data.ValidSize   += bPos;
                Data.fbs.Position = Data.ValidSize;
                int octet = Data.fbs.ReadByte();

                if (octet < 0)
                {
                    IssueModel.Add("File truncated near CRC-8", Severity.Fatal);
                    return;
                }
                Data.StoredAudioHeaderCRC8 = (Byte)octet;

                try
                {
                    Data.fbs.Position = Data.mediaPosition;
                }
                catch (EndOfStreamException)
                {
                    IssueModel.Add("File truncated near audio data", Severity.Fatal);
                    return;
                }

                try
                {
                    Data.fbs.Position = Data.FileSize - 2;
                }
                catch (EndOfStreamException)
                {
                    IssueModel.Add("File truncated looking for end", Severity.Fatal);
                    return;
                }

                bb = new byte[2];
                if (Data.fbs.Read(bb, 0, 2) != 2)
                {
                    IssueModel.Add("Read failed on audio block CRC-16", Severity.Fatal);
                    return;
                }

                Data.StoredAudioBlockCRC16 = (UInt16)(bb[0] << 8 | bb[1]);
                Data.MediaCount            = Data.FileSize - Data.mediaPosition;

                GetDiagnostics();
            }
Exemple #4
0
            public void ValidateRip(IList <FlacFormat> flacs, bool checkTags)
            {
                Data.IsLosslessRip = true;

                Severity baddest = Severity.NoIssue;

                PerformValidations();
                if (baddest < Data.Issues.MaxSeverity)
                {
                    baddest = Data.Issues.MaxSeverity;
                }

                if (baddest >= Severity.Error)
                {
                    Data.RpIssue = IssueModel.Add($"{Data.Subname} to FLAC rip check failed.", baddest, IssueTags.Failure);
                }
                else if (baddest >= Severity.Warning)
                {
                    Data.RpIssue = IssueModel.Add($"{Data.Subname} to FLAC rip check successful with warnings.", baddest, IssueTags.Success);
                }
                else
                {
                    Data.RpIssue = IssueModel.Add($"{Data.Subname} to FLAC rip check successful!", Severity.Advisory, IssueTags.Success);
                }
                return;

                void PerformValidations()
                {
                    if (flacs.Count != TracksModel.GetCount() || flacs.Count == 0)
                    {
                        Data.TkIssue = IssueModel.Add($"Folder contains {flacs.Count} .flac files, {Data.Subname} log contains {TracksModel.GetCount()} tracks.");
                        baddest      = Severity.Error;
                        return;
                    }

                    int    errCount = 0, warnCount = 0;
                    string errs = string.Empty, warns = string.Empty;

                    for (int fx = 0; fx < flacs.Count; ++fx)
                    {
                        FlacFormat flac = flacs[fx];
                        if (baddest < flac.Issues.MaxSeverity)
                        {
                            baddest = flac.Issues.MaxSeverity;
                        }
                        var level = flac.Issues.MaxLevelWhereAny(IssueTags.BadTag);
                        if (level >= Severity.Error)
                        {
                            if (warnCount < 2)
                            {
                                errs = warnCount == 1 ? "s" + errs + ", " : errs + " ";
                                ++errCount;
                            }
                            else
                            {
                                errs += ", ";
                            }
                            errs = errs + flac.GetTagValue("TRACKNUMBER");
                        }
                        else if (level == Severity.Warning)
                        {
                            if (warnCount < 2)
                            {
                                warns = warnCount == 1 ? "s" + warns + ", " : warns + " ";
                                ++warnCount;
                            }
                            else
                            {
                                warns += ", ";
                            }
                            warns = warns + flac.GetTagValue("TRACKNUMBER");
                        }
                    }
                    if (warns.Length > 0)
                    {
                        IssueModel.Add($"Tag issues on track{warns}.", Severity.Warning);
                    }
                    if (errs.Length > 0)
                    {
                        IssueModel.Add($"Tag issues on track{errs}.", Severity.Error);
                    }

                    baddest = flacs.Max(tk => tk.Issues.MaxSeverity);
                    if (flacs.Count != flacs.Where(tk => tk.ActualAudioBlockCRC16 != null).Count())
                    {
                        IssueModel.Add("FLAC intrinsic CRC checks not performed.", Severity.Warning, IssueTags.StrictErr);
                    }

                    TracksModel.MatchFlacs(flacs);
                    if (TracksModel.Data.RipMismatchCount != 0)
                    {
                        Data.MhIssue = IssueModel.Add("Log CRC-32 match to FLAC PCM CRC-32 failed.", Severity.Error, IssueTags.Failure);
                    }
                    else
                    {
                        Data.MhIssue = IssueModel.Add("Log/FLAC CRC-32s match for all tracks.", Severity.Trivia);
                        if (checkTags)
                        {
                            CheckFlacRipTags(flacs);
                        }
                    }
                }
            }