Beispiel #1
0
		void FromCueInternal(Cue cue, string cueDir, CueBinPrefs prefs)
		{
			//TODO - add cue directory to CueBinPrefs???? could make things cleaner...

			var session = new DiscTOC.Session {num = 1};
			TOC.Sessions.Add(session);
			var pregap_sector = new Sector_Zero();

			int curr_track = 1;

			foreach (var cue_file in cue.Files)
			{
				//structural validation
				if (cue_file.Tracks.Count < 1) throw new Cue.CueBrokenException("`You must specify at least one track per file.`");

				string blobPath = Path.Combine(cueDir, cue_file.Path);

				if (CueFileResolver.ContainsKey(cue_file.Path))
					blobPath = CueFileResolver[cue_file.Path];

				int blob_sectorsize = Cue.BINSectorSizeForTrackType(cue_file.Tracks[0].TrackType);
				int blob_length_aba;
				long blob_length_bytes;
				IBlob cue_blob;

				//try any way we can to acquire a file
				if (!File.Exists(blobPath) && prefs.ExtensionAware)
				{
					blobPath = FindAlternateExtensionFile(blobPath, prefs.CaseSensitive, cueDir);
				}

				if (!File.Exists(blobPath))
					throw new DiscReferenceException(blobPath, "");

				//some simple rules to mutate the file type if we received something fishy
				string blobPathExt = Path.GetExtension(blobPath).ToLower();
				if (blobPathExt == ".ape") cue_file.FileType = Cue.CueFileType.Wave;
				if (blobPathExt == ".mp3") cue_file.FileType = Cue.CueFileType.Wave;
				if (blobPathExt == ".mpc") cue_file.FileType = Cue.CueFileType.Wave;
				if (blobPathExt == ".flac") cue_file.FileType = Cue.CueFileType.Wave;
				if (blobPathExt == ".ecm") cue_file.FileType = Cue.CueFileType.ECM;

				if (cue_file.FileType == Cue.CueFileType.Binary || cue_file.FileType == Cue.CueFileType.Unspecified)
				{
					//make a blob for the file
					Blob_RawFile blob = new Blob_RawFile {PhysicalPath = blobPath};
					Blobs.Add(blob);

					blob_length_aba = (int)(blob.Length / blob_sectorsize);
					blob_length_bytes = blob.Length;
					cue_blob = blob;
				}
				else if (cue_file.FileType == Cue.CueFileType.ECM)
				{
					if (!Blob_ECM.IsECM(blobPath))
					{
						throw new DiscReferenceException(blobPath, "an ECM file was specified or detected, but it isn't a valid ECM file. You've got issues. Consult your iso vendor.");
					}
					Blob_ECM blob = new Blob_ECM();
					Blobs.Add(blob);
					blob.Parse(blobPath);
					cue_blob = blob;
					blob_length_aba = (int)(blob.Length / blob_sectorsize);
					blob_length_bytes = blob.Length;
				}
				else if (cue_file.FileType == Cue.CueFileType.Wave)
				{
					Blob_WaveFile blob = new Blob_WaveFile();
					Blobs.Add(blob);

					try
					{
						//check whether we can load the wav directly
						bool loaded = false;
						if (File.Exists(blobPath) && Path.GetExtension(blobPath).ToUpper() == ".WAV")
						{
							try
							{
								blob.Load(blobPath);
								loaded = true;
							}
							catch
							{
							}
						}

						//if that didnt work or wasnt possible, try loading it through ffmpeg
						if (!loaded)
						{
							FFMpeg ffmpeg = new FFMpeg();
							if (!ffmpeg.QueryServiceAvailable())
							{
								throw new DiscReferenceException(blobPath, "No decoding service was available (make sure ffmpeg.exe is available. even though this may be a wav, ffmpeg is used to load oddly formatted wave files. If you object to this, please send us a note and we'll see what we can do. It shouldn't be too hard.)");
							}
							AudioDecoder dec = new AudioDecoder();
							byte[] buf = dec.AcquireWaveData(blobPath);
							blob.Load(new MemoryStream(buf));
							WasSlowLoad = true;
						}
					}
					catch (Exception ex)
					{
						throw new DiscReferenceException(blobPath, ex);
					}

					blob_length_aba = (int)(blob.Length / blob_sectorsize);
					blob_length_bytes = blob.Length;
					cue_blob = blob;
				}
				else throw new Exception("Internal error - Unhandled cue blob type");

				//TODO - make CueTimestamp better, and also make it a struct, and also just make it DiscTimestamp

				//start timekeeping for the blob. every time we hit an index, this will advance
				int blob_timestamp = 0;

				//because we can have different size sectors in a blob, we need to keep a file cursor within the blob
				long blob_cursor = 0;

				//the aba that this cue blob starts on
				int blob_disc_aba_start = Sectors.Count;

				//for each track within the file, create an index 0 if it is missing.
				//also check to make sure there is an index 1
				for (int t = 0; t < cue_file.Tracks.Count; t++)
				{
					var cue_track = cue_file.Tracks[t];
					if (!cue_track.Indexes.ContainsKey(1))
						throw new Cue.CueBrokenException("Track was missing an index 01");
					if (!cue_track.Indexes.ContainsKey(0))
					{
						//index 0 will default to the same as index 1.
						//i am not sure whether it is valid to have two indexes with the same timestamp.
						//we will do this to simplify some processing, but we can purge it in a later pass if we need to.
						var cti = new Cue.CueTrackIndex(0);
						cue_track.Indexes[0] = cti;
						cti.Timestamp = cue_track.Indexes[1].Timestamp;
					}
				}

				//validate that the first index in the file is 00:00:00
				if (cue_file.Tracks[0].Indexes[0].Timestamp.ABA != 0) throw new Cue.CueBrokenException("`The first index of a blob must start at 00:00:00.`");


				//for each track within the file:
				for (int t = 0; t < cue_file.Tracks.Count; t++)
				{
					var cue_track = cue_file.Tracks[t];

					//record the disc ABA that this track started on
					int track_disc_aba_start = Sectors.Count;

					//record the pregap location. it will default to the start of the track unless we supplied a pregap command
					int track_disc_pregap_aba = track_disc_aba_start;

					int blob_track_start = blob_timestamp;

					//once upon a time we had a check here to prevent a single blob from containing variant sector sizes. but we support that now.

					//check integrity of track sequence and setup data structures
					//TODO - check for skipped tracks in cue parser instead
					if (cue_track.TrackNum != curr_track) throw new Cue.CueBrokenException("Found a cue with skipped tracks");
					var toc_track = new DiscTOC.Track();
					toc_track.num = curr_track;
					toc_track.TrackType = cue_track.TrackType;
					session.Tracks.Add(toc_track);

					if (curr_track == 1)
					{
						if (cue_track.PreGap.ABA != 0)
							throw new InvalidOperationException("not supported (yet): cue files with track 1 pregaps");
						//but now we add one anyway, because every existing cue+bin seems to implicitly specify this
						cue_track.PreGap = new Timestamp(150);
					}

					//check whether a pregap is requested.
					//this causes empty sectors to get generated without consuming data from the blob
					if (cue_track.PreGap.ABA > 0)
					{
						for (int i = 0; i < cue_track.PreGap.ABA; i++)
						{
							Sectors.Add(new SectorEntry(pregap_sector));
						}
					}

					//look ahead to the next track's index 1 so we can see how long this track's last index is
					//or, for the last track, use the length of the file
					int track_length_aba;
					if (t == cue_file.Tracks.Count - 1)
						track_length_aba = blob_length_aba - blob_timestamp;
					else track_length_aba = cue_file.Tracks[t + 1].Indexes[1].Timestamp.ABA - blob_timestamp;
					//toc_track.length_aba = track_length_aba; //xxx

					//find out how many indexes we have
					int num_indexes = 0;
					for (num_indexes = 0; num_indexes <= 99; num_indexes++)
						if (!cue_track.Indexes.ContainsKey(num_indexes)) break;

					//for each index, calculate length of index and then emit it
					for (int index = 0; index < num_indexes; index++)
					{
						bool is_last_index = index == num_indexes - 1;

						//install index into hierarchy
						var toc_index = new DiscTOC.Index {num = index};
						toc_track.Indexes.Add(toc_index);
						if (index == 0)
						{
							toc_index.aba = track_disc_pregap_aba - (cue_track.Indexes[1].Timestamp.ABA - cue_track.Indexes[0].Timestamp.ABA);
						}
						else toc_index.aba = Sectors.Count;

						//calculate length of the index
						//if it is the last index then we use our calculation from before, otherwise we check the next index
						int index_length_aba;
						if (is_last_index)
							index_length_aba = track_length_aba - (blob_timestamp - blob_track_start);
						else index_length_aba = cue_track.Indexes[index + 1].Timestamp.ABA - blob_timestamp;

						//emit sectors
						for (int aba = 0; aba < index_length_aba; aba++)
						{
							bool is_last_aba_in_index = (aba == index_length_aba - 1);
							bool is_last_aba_in_track = is_last_aba_in_index && is_last_index;

							switch (cue_track.TrackType)
							{
								case ETrackType.Audio:  //all 2352 bytes are present
								case ETrackType.Mode1_2352: //2352 bytes are present, containing 2048 bytes of user data as well as ECM
								case ETrackType.Mode2_2352: //2352 bytes are present, containing the entirety of a mode2 sector (could be form0,1,2)
									{
										//these cases are all 2352 bytes
										//in all these cases, either no ECM is present or ECM is provided.
										//so we just emit a Sector_Raw
										Sector_RawBlob sector_rawblob = new Sector_RawBlob
											{
												Blob = cue_blob,
												Offset = blob_cursor
											};
										blob_cursor += 2352;
										Sector_Mode1_or_Mode2_2352 sector_raw;
										if(cue_track.TrackType == ETrackType.Mode1_2352)
											sector_raw  = new Sector_Mode1_2352();
										else if (cue_track.TrackType == ETrackType.Audio)
											sector_raw = new Sector_Mode1_2352(); //TODO should probably make a new sector adapter which errors if 2048B are requested
										else if (cue_track.TrackType == ETrackType.Mode2_2352)
											sector_raw = new Sector_Mode2_2352();
										else throw new InvalidOperationException();

										sector_raw.BaseSector = sector_rawblob;

										Sectors.Add(new SectorEntry(sector_raw));
										break;
									}
								case ETrackType.Mode1_2048:
									//2048 bytes are present. ECM needs to be generated to create a full sector
									{
										//ECM needs to know the sector number so we have to record that here
										int curr_disc_aba = Sectors.Count;
										var sector_2048 = new Sector_Mode1_2048(curr_disc_aba + 150)
											{
												Blob = new ECMCacheBlob(cue_blob),
												Offset = blob_cursor
											};
										blob_cursor += 2048;
										Sectors.Add(new SectorEntry(sector_2048));
										break;
									}
							} //switch(TrackType)

							//we've emitted an ABA, so consume it from the blob
							blob_timestamp++;

						} //aba emit loop

					} //index loop

					//check whether a postgap is requested. if it is, we need to generate silent sectors
					for (int i = 0; i < cue_track.PostGap.ABA; i++)
					{
						Sectors.Add(new SectorEntry(pregap_sector));
					}

					//we're done with the track now.
					//record its length:
					toc_track.length_aba = Sectors.Count - toc_track.Indexes[1].aba;
					curr_track++;

					//if we ran off the end of the blob, pad it with zeroes, I guess
					if (blob_cursor > blob_length_bytes)
					{
						//mutate the blob to an encapsulating Blob_ZeroPadAdapter
						Blobs[Blobs.Count - 1] = new Blob_ZeroPadAdapter(Blobs[Blobs.Count - 1], blob_length_bytes, blob_cursor - blob_length_bytes);
					}

				} //track loop
			} //file loop

			//finally, analyze the length of the sessions and the entire disc by summing the lengths of the tracks
			//this is a little more complex than it looks, because the length of a thing is not determined by summing it
			//but rather by the difference in abas between start and end
			TOC.length_aba = 0;
			foreach (var toc_session in TOC.Sessions)
			{
				var firstTrack = toc_session.Tracks[0];

				//track 0, index 0 is actually -150. but cue sheets will never say that
				//firstTrack.Indexes[0].aba -= 150;

				var lastTrack = toc_session.Tracks[toc_session.Tracks.Count - 1];
				session.length_aba = lastTrack.Indexes[1].aba + lastTrack.length_aba - firstTrack.Indexes[0].aba;
				TOC.length_aba += toc_session.length_aba;
			}
		}
Beispiel #2
0
        private void OpenFile(CUE_File.Command.FILE f)
        {
            if (curr_file != null)
            {
                CloseFile();
            }

            curr_blobIndex++;
            curr_fileHasTrack = false;

            var Resolver = IN_CueContext.Resolver;

            //TODO - smart audio file resolving only for AUDIO types. not BINARY or MOTOROLA or AIFF or ECM or what have you

            var    options = Resolver.Resolve(f.Path);
            string choice  = null;

            if (options.Count == 0)
            {
                Error($"Couldn't resolve referenced cue file: {f.Path} ; you can commonly repair the cue file yourself, or a file might be missing");
                //add a null entry to keep the count from being wrong later (quiets a warning)
                OUT_CompiledCueFiles.Add(null);
                return;
            }
            else
            {
                choice = options[0];
                if (options.Count > 1)
                {
                    Warn($"Multiple options resolving referenced cue file; choosing: {Path.GetFileName(choice)}");
                }
            }

            var cfi = new CompiledCueFile();

            curr_file = cfi;
            OUT_CompiledCueFiles.Add(cfi);

            cfi.FullPath = choice;

            //determine the CueFileInfo's type, based on extension and extra checking
            //TODO - once we reorganize the file ID stuff, do legit checks here (this is completely redundant with the fileID system
            //TODO - decode vs stream vs unpossible policies in input policies object (including ffmpeg availability-checking callback (results can be cached))
            string blobPathExt = Path.GetExtension(choice).ToUpperInvariant();

            if (blobPathExt == ".BIN" || blobPathExt == ".IMG")
            {
                cfi.Type = CompiledCueFileType.BIN;
            }
            else if (blobPathExt == ".ISO")
            {
                cfi.Type = CompiledCueFileType.BIN;
            }
            else if (blobPathExt == ".WAV")
            {
                //quickly, check the format. turn it to DecodeAudio if it can't be supported
                //TODO - fix exception-throwing inside
                //TODO - verify stream-disposing semantics
                var fs = File.OpenRead(choice);
                using var blob = new Blob_WaveFile();
                try
                {
                    blob.Load(fs);
                    cfi.Type = CompiledCueFileType.WAVE;
                }
                catch
                {
                    cfi.Type = CompiledCueFileType.DecodeAudio;
                }
            }
            else if (blobPathExt == ".APE")
            {
                cfi.Type = CompiledCueFileType.DecodeAudio;
            }
            else if (blobPathExt == ".MP3")
            {
                cfi.Type = CompiledCueFileType.DecodeAudio;
            }
            else if (blobPathExt == ".MPC")
            {
                cfi.Type = CompiledCueFileType.DecodeAudio;
            }
            else if (blobPathExt == ".FLAC")
            {
                cfi.Type = CompiledCueFileType.DecodeAudio;
            }
            else if (blobPathExt == ".ECM")
            {
                cfi.Type = CompiledCueFileType.ECM;
                if (!Blob_ECM.IsECM(choice))
                {
                    Error($"an ECM file was specified or detected, but it isn't a valid ECM file: {Path.GetFileName(choice)}");
                    cfi.Type = CompiledCueFileType.Unknown;
                }
            }
            else
            {
                Error($"Unknown cue file type. Since it's likely an unsupported compression, this is an error: {Path.GetFileName(choice)}");
                cfi.Type = CompiledCueFileType.Unknown;
            }

            //TODO - check for mismatches between track types and file types, or is that best done when interpreting the commands?
        }