string FindFontFileFromFontName(string fontname, string style)
        {
            string s, e;

            ExternalTool.Run("/bin/bash", string.Format("-c \"fc-match -f '%{{file}}:%{{family}}\\n' '{0}:style={1}'\"", fontname, style), out s, out e);
            s = s.Trim();

            var split = s.Split(':');

            //check font family, fontconfig might return a fallback
            if (split [1].Contains(","))    //this file defines multiple family names
            {
                var families = split [1].Split(',');
                foreach (var f in families)
                {
                    if (f.ToLowerInvariant() == fontname.ToLowerInvariant())
                    {
                        return(split [0]);
                    }
                }
                //didn't find it
                return(String.Empty);
            }
            else
            {
                if (split [1].ToLowerInvariant() != fontname.ToLowerInvariant())
                {
                    return(String.Empty);
                }
            }
            return(split [0]);
        }
        /// <summary>
        /// Processes the string representation of the specified effect into
        /// a platform-specific binary format using the specified context.
        /// </summary>
        /// <param name="input">The effect string to be processed.</param>
        /// <param name="context">Context for the specified processor.</param>
        /// <returns>A platform-specific compiled binary effect.</returns>
        /// <remarks>
        /// If you get an error during processing, compilation stops immediately.
        /// The effect processor displays an error message.
        /// Once you fix the current error, it is possible you may get more errors on subsequent compilation attempts.
        /// </remarks>
        public override CompiledEffectContent Process(EffectContent input, ContentProcessorContext context)
        {
            var mgfxc      = Path.Combine(Path.GetDirectoryName(typeof(EffectProcessor).Assembly.Location), "mgfxc.dll");
            var sourceFile = input.Identity.SourceFilename;
            var destFile   = Path.GetTempFileName();

            var arguments = "\"" + mgfxc + "\" \"" + sourceFile + "\" \"" + destFile +
                            "\" /Profile:" + GetProfileForPlatform(context.TargetPlatform);

            if (DebugMode == EffectProcessorDebugMode.Debug)
            {
                arguments += " /Debug";
            }

            if (!string.IsNullOrWhiteSpace(Defines))
            {
                arguments += " \"/Defines:" + Defines + "\"";
            }

            var success = ExternalTool.Run("dotnet", arguments, out var stdout, out var stderr) == 0;
            var ret     = success ? new CompiledEffectContent(File.ReadAllBytes(destFile)) : null;

            File.Delete(destFile);

            var stdOutLines = stdout.ToString().Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);

            foreach (var line in stdOutLines)
            {
                if (line.StartsWith("Dependency:") && line.Length > 12)
                {
                    context.AddDependency(line[12..]);
Пример #3
0
        /// <summary>
        /// Выполнить внешнее ПО
        /// </summary>
        /// <param name="Program"></param>
        /// <param name="ProgArgs"></param>
        /// <param name="Args"></param>
        /// <param name="Rewrite"></param>
        /// <returns></returns>
        public static ExternalToolResult Exec(string Program, string ProgArgs, ArgumentList Args, OptRewrite[] Rewrite)
        {
            var CmdPath = ProcessCommand(Program, Args, Rewrite);
            var CmdArgs = ProcessCommand(ProgArgs, Args, Rewrite);

            string Dir = Args.Get("$dir");

            return(ExternalTool.Run(CmdPath, CmdArgs, Dir));
        }
Пример #4
0
        private string FindFont(string name, string style)
        {
            if (CurrentPlatform.OS == OS.Windows)
            {
                var fontDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts");
                var key           = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts", false);

                foreach (var font in key.GetValueNames().OrderBy(x => x))
                {
                    if (font.StartsWith(name, StringComparison.OrdinalIgnoreCase))
                    {
                        var fontPath = key.GetValue(font).ToString();
                        return(Path.IsPathRooted(fontPath) ? fontPath : Path.Combine(fontDirectory, fontPath));
                    }
                }
            }
            else if (CurrentPlatform.OS == OS.Linux)
            {
                string s, e;
                ExternalTool.Run("/bin/bash", string.Format("-c \"fc-match -f '%{{file}}:%{{family}}\\n' '{0}:style={1}'\"", name, style), out s, out e);
                s = s.Trim();

                var split = s.Split(':');
                if (split.Length < 2)
                {
                    return(string.Empty);
                }

                // check font family, fontconfig might return a fallback
                if (split[1].Contains(","))
                {
                    // this file defines multiple family names
                    var families = split[1].Split(',');
                    foreach (var f in families)
                    {
                        if (f.ToLowerInvariant() == name.ToLowerInvariant())
                        {
                            return(split[0]);
                        }
                    }
                    // didn't find it
                    return(string.Empty);
                }
                else
                {
                    if (split[1].ToLowerInvariant() != name.ToLowerInvariant())
                    {
                        return(string.Empty);
                    }
                }

                return(split[0]);
            }

            return(String.Empty);
        }
        public static void WritePcmFile(AudioContent content, string saveToFile)
        {
            string ffmpegStdout, ffmpegStderr;
            var    ffmpegExitCode = ExternalTool.Run(
                "ffmpeg",
                string.Format(
                    "-y -i \"{0}\" -vn -c:a pcm_s16le -b:a 192000 -f:a wav -strict experimental \"{1}\"",
                    content.FileName,
                    saveToFile),
                out ffmpegStdout,
                out ffmpegStderr);

            if (ffmpegExitCode != 0)
            {
                throw new InvalidOperationException("ffmpeg exited with non-zero exit code: \n" + ffmpegStdout + "\n" + ffmpegStderr);
            }
        }
Пример #6
0
        public static void ProbeFormat(string sourceFile, out AudioFileType audioFileType, out AudioFormat audioFormat, out TimeSpan duration, out int loopStart, out int loopLength)
        {
            string ffprobeStdout, ffprobeStderr;
            var    ffprobeExitCode = ExternalTool.Run(
                "ffprobe",
                string.Format("-i \"{0}\" -show_format -show_entries streams -v quiet -of flat", sourceFile),
                out ffprobeStdout,
                out ffprobeStderr);

            if (ffprobeExitCode != 0)
            {
                throw new InvalidOperationException("ffprobe exited with non-zero exit code.");
            }

            // Set default values if information is not available.
            int    averageBytesPerSecond = 0;
            int    bitsPerSample         = 0;
            int    blockAlign            = 0;
            int    channelCount          = 0;
            int    sampleRate            = 0;
            int    format            = 0;
            string sampleFormat      = null;
            double durationInSeconds = 0;
            var    formatName        = string.Empty;

            try
            {
                var numberFormat = CultureInfo.InvariantCulture.NumberFormat;
                foreach (var line in ffprobeStdout.Split(new[] { '\r', '\n', '\0' }, StringSplitOptions.RemoveEmptyEntries))
                {
                    var kv = line.Split(new[] { '=' }, 2);

                    switch (kv[0])
                    {
                    case "streams.stream.0.sample_rate":
                        sampleRate = int.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.bits_per_sample":
                        bitsPerSample = int.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.start_time":
                    {
                        double seconds;
                        if (double.TryParse(kv[1].Trim('"'), NumberStyles.Any, numberFormat, out seconds))
                        {
                            durationInSeconds += seconds;
                        }
                        break;
                    }

                    case "streams.stream.0.duration":
                        durationInSeconds += double.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.channels":
                        channelCount = int.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.sample_fmt":
                        sampleFormat = kv[1].Trim('"').ToLowerInvariant();
                        break;

                    case "streams.stream.0.bit_rate":
                        averageBytesPerSecond = (int.Parse(kv[1].Trim('"'), numberFormat) / 8);
                        break;

                    case "format.format_name":
                        formatName = kv[1].Trim('"').ToLowerInvariant();
                        break;

                    case "streams.stream.0.codec_tag":
                    {
                        var hex = kv[1].Substring(3, kv[1].Length - 4);
                        format = int.Parse(hex, NumberStyles.HexNumber);
                        break;
                    }
                    }
                }
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("Failed to parse ffprobe output.", ex);
            }

            // XNA seems to use the sample format for the bits per sample
            // in the case of non-PCM formats like MP3 and WMA.
            if (bitsPerSample == 0 && sampleFormat != null)
            {
                switch (sampleFormat)
                {
                case "u8":
                case "u8p":
                    bitsPerSample = 8;
                    break;

                case "s16":
                case "s16p":
                    bitsPerSample = 16;
                    break;

                case "s32":
                case "s32p":
                case "flt":
                case "fltp":
                    bitsPerSample = 32;
                    break;

                case "dbl":
                case "dblp":
                    bitsPerSample = 64;
                    break;
                }
            }

            // Figure out the file type.
            var durationMs = (int)Math.Floor(durationInSeconds * 1000.0);

            if (formatName == "wav")
            {
                audioFileType = AudioFileType.Wav;
            }
            else if (formatName == "mp3")
            {
                audioFileType = AudioFileType.Mp3;
                format        = 1;
                durationMs    = (int)Math.Ceiling(durationInSeconds * 1000.0);
                bitsPerSample = Math.Min(bitsPerSample, 16);
            }
            else if (formatName == "wma" || formatName == "asf")
            {
                audioFileType = AudioFileType.Wma;
                format        = 1;
                durationMs    = (int)Math.Ceiling(durationInSeconds * 1000.0);
                bitsPerSample = Math.Min(bitsPerSample, 16);
            }
            else if (formatName == "ogg")
            {
                audioFileType = AudioFileType.Ogg;
                format        = 1;
                durationMs    = (int)Math.Ceiling(durationInSeconds * 1000.0);
                bitsPerSample = Math.Min(bitsPerSample, 16);
            }
            else
            {
                audioFileType = (AudioFileType)(-1);
            }

            // XNA seems to calculate the block alignment directly from
            // the bits per sample and channel count regardless of the
            // format of the audio data.
            // ffprobe doesn't report blockAlign for ADPCM and we cannot calculate it like this
            if (bitsPerSample > 0 && (format != 2 && format != 17))
            {
                blockAlign = (bitsPerSample * channelCount) / 8;
            }

            // XNA seems to only be accurate to the millisecond.
            duration = TimeSpan.FromMilliseconds(durationMs);

            // Looks like XNA calculates the average bps from
            // the sample rate and block alignment.
            if (blockAlign > 0)
            {
                averageBytesPerSecond = sampleRate * blockAlign;
            }

            audioFormat = new AudioFormat(
                averageBytesPerSecond,
                bitsPerSample,
                blockAlign,
                channelCount,
                format,
                sampleRate);

            // Loop start and length in number of samples.  For some
            // reason XNA doesn't report loop length for non-WAV sources.
            loopStart = 0;
            if (audioFileType != AudioFileType.Wav)
            {
                loopLength = 0;
            }
            else
            {
                loopLength = (int)Math.Floor(sampleRate * durationInSeconds);
            }
        }
Пример #7
0
        public static ConversionQuality ConvertToFormat(AudioContent content, ConversionFormat formatType, ConversionQuality quality, string saveToFile)
        {
            var temporaryOutput = Path.GetTempFileName();

            try
            {
                string ffmpegCodecName, ffmpegMuxerName;
                //int format;
                switch (formatType)
                {
                case ConversionFormat.Adpcm:
                    // ADPCM Microsoft
                    ffmpegCodecName = "adpcm_ms";
                    ffmpegMuxerName = "wav";
                    //format = 0x0002; /* WAVE_FORMAT_ADPCM */
                    break;

                case ConversionFormat.Pcm:
                    // XNA seems to preserve the bit size of the input
                    // format when converting to PCM.
                    if (content.Format.BitsPerSample == 8)
                    {
                        ffmpegCodecName = "pcm_u8";
                    }
                    else if (content.Format.BitsPerSample == 32 && content.Format.Format == 3)
                    {
                        ffmpegCodecName = "pcm_f32le";
                    }
                    else
                    {
                        ffmpegCodecName = "pcm_s16le";
                    }
                    ffmpegMuxerName = "wav";
                    //format = 0x0001; /* WAVE_FORMAT_PCM */
                    break;

                case ConversionFormat.WindowsMedia:
                    // Windows Media Audio 2
                    ffmpegCodecName = "wmav2";
                    ffmpegMuxerName = "asf";
                    //format = 0x0161; /* WAVE_FORMAT_WMAUDIO2 */
                    break;

                case ConversionFormat.Xma:
                    throw new NotSupportedException(
                              "XMA is not a supported encoding format. It is specific to the Xbox 360.");

                case ConversionFormat.ImaAdpcm:
                    // ADPCM IMA WAV
                    ffmpegCodecName = "adpcm_ima_wav";
                    ffmpegMuxerName = "wav";
                    //format = 0x0011; /* WAVE_FORMAT_IMA_ADPCM */
                    break;

                case ConversionFormat.Aac:
                    // AAC (Advanced Audio Coding)
                    // Requires -strict experimental
                    ffmpegCodecName = "aac";
                    ffmpegMuxerName = "ipod";
                    //format = 0x0000; /* WAVE_FORMAT_UNKNOWN */
                    break;

                case ConversionFormat.Vorbis:
                    // Vorbis
                    ffmpegCodecName = "libvorbis";
                    ffmpegMuxerName = "ogg";
                    //format = 0x0000; /* WAVE_FORMAT_UNKNOWN */
                    break;

                default:
                    // Unknown format
                    throw new NotSupportedException();
                }

                string ffmpegStdout, ffmpegStderr;
                int    ffmpegExitCode;
                do
                {
                    ffmpegExitCode = ExternalTool.Run(
                        "ffmpeg",
                        string.Format(
                            "-y -i \"{0}\" -vn -c:a {1} -b:a {2} -ar {3} -f:a {4} -strict experimental \"{5}\"",
                            content.FileName,
                            ffmpegCodecName,
                            QualityToBitRate(quality),
                            QualityToSampleRate(quality, content.Format.SampleRate),
                            ffmpegMuxerName,
                            temporaryOutput),
                        out ffmpegStdout,
                        out ffmpegStderr);
                    if (ffmpegExitCode != 0)
                    {
                        quality--;
                    }
                } while (quality >= 0 && ffmpegExitCode != 0);

                if (ffmpegExitCode != 0)
                {
                    throw new InvalidOperationException("ffmpeg exited with non-zero exit code: \n" + ffmpegStdout + "\n" + ffmpegStderr);
                }

                byte[] rawData;
                using (var fs = new FileStream(temporaryOutput, FileMode.Open, FileAccess.Read))
                {
                    rawData = new byte[fs.Length];
                    fs.Read(rawData, 0, rawData.Length);
                }

                if (saveToFile != null)
                {
                    using (var fs = new FileStream(saveToFile, FileMode.Create, FileAccess.Write))
                        fs.Write(rawData, 0, rawData.Length);
                }

                // Use probe to get the final format and information on the converted file.
                AudioFileType audioFileType;
                AudioFormat   audioFormat;
                TimeSpan      duration;
                int           loopStart, loopLength;
                ProbeFormat(temporaryOutput, out audioFileType, out audioFormat, out duration, out loopStart, out loopLength);

                AudioFormat riffAudioFormat;
                byte[]      data = StripRiffWaveHeader(rawData, out riffAudioFormat);

                // deal with adpcm
                if (audioFormat.Format == 2 || audioFormat.Format == 17)
                {
                    // riff contains correct blockAlign
                    audioFormat = riffAudioFormat;

                    // fix loopLength -> has to be multiple of sample per block
                    // see https://msdn.microsoft.com/de-de/library/windows/desktop/ee415711(v=vs.85).aspx
                    int samplesPerBlock = SampleAlignment(audioFormat);
                    loopLength = (int)(audioFormat.SampleRate * duration.TotalSeconds);
                    int remainder = loopLength % samplesPerBlock;
                    loopLength += samplesPerBlock - remainder;
                }

                content.SetData(data, audioFormat, duration, loopStart, loopLength);
            }
            finally
            {
                ExternalTool.DeleteFile(temporaryOutput);
            }

            return(quality);
        }
Пример #8
0
        /// <summary>
        /// Transcodes the source audio to the target format and quality.
        /// </summary>
        /// <param name="formatType">Format to convert this audio to.</param>
        /// <param name="quality">Quality of the processed output audio. For streaming formats, it can be one of the following: Low (96 kbps), Medium (128 kbps), Best (192 kbps).  For WAV formats, it can be one of the following: Low (11kHz ADPCM), Medium (22kHz ADPCM), Best (44kHz PCM)</param>
        /// <param name="saveToFile">
        /// The name of the file that the converted audio should be saved into.  This is used for SongContent, where
        /// the audio is stored external to the XNB file.  If this is null, then the converted audio is stored in
        /// the Data property.
        /// </param>
        public void ConvertFormat(ConversionFormat formatType, ConversionQuality quality, string saveToFile)
        {
            var temporarySource = Path.GetTempFileName();
            var temporaryOutput = Path.GetTempFileName();

            try
            {
                using (var fs = new FileStream(temporarySource, FileMode.Create, FileAccess.Write))
                {
                    var dataBytes = this.data.ToArray();
                    fs.Write(dataBytes, 0, dataBytes.Length);
                }

                string ffmpegCodecName, ffmpegMuxerName;
                int    format;
                switch (formatType)
                {
                case ConversionFormat.Adpcm:
                    // ADPCM Microsoft
                    ffmpegCodecName = "adpcm_ms";
                    ffmpegMuxerName = "wav";
                    format          = 0x0002; /* WAVE_FORMAT_ADPCM */
                    break;

                case ConversionFormat.Pcm:
                    // PCM signed 16-bit little-endian
                    ffmpegCodecName = "pcm_s16le";
                    ffmpegMuxerName = "wav";
                    format          = 0x0001; /* WAVE_FORMAT_PCM */
                    break;

                case ConversionFormat.WindowsMedia:
                    // Windows Media Audio 2
                    ffmpegCodecName = "wmav2";
                    ffmpegMuxerName = "asf";
                    format          = 0x0161; /* WAVE_FORMAT_WMAUDIO2 */
                    break;

                case ConversionFormat.Xma:
                    throw new NotSupportedException(
                              "XMA is not a supported encoding format. It is specific to the Xbox 360.");

                case ConversionFormat.ImaAdpcm:
                    // ADPCM IMA WAV
                    ffmpegCodecName = "adpcm_ima_wav";
                    ffmpegMuxerName = "wav";
                    format          = 0x0011; /* WAVE_FORMAT_IMA_ADPCM */
                    break;

                case ConversionFormat.Aac:
                    // AAC (Advanced Audio Coding)
                    // Requires -strict experimental
                    ffmpegCodecName = "aac";
                    ffmpegMuxerName = "ipod";
                    format          = 0x0000; /* WAVE_FORMAT_UNKNOWN */
                    break;

                case ConversionFormat.Vorbis:
                    // Vorbis
                    ffmpegCodecName = "libvorbis";
                    ffmpegMuxerName = "ogg";
                    format          = 0x0000; /* WAVE_FORMAT_UNKNOWN */
                    break;

                default:
                    // Unknown format
                    throw new NotSupportedException();
                }

                string ffmpegStdout, ffmpegStderr;
                var    ffmpegExitCode = ExternalTool.Run(
                    "ffmpeg",
                    string.Format(
                        "-y -i \"{0}\" -vn -c:a {1} -b:a {2} -f:a {3} -strict experimental \"{4}\"",
                        temporarySource,
                        ffmpegCodecName,
                        QualityToBitRate(quality),
                        ffmpegMuxerName,
                        temporaryOutput),
                    out ffmpegStdout,
                    out ffmpegStderr);
                if (ffmpegExitCode != 0)
                {
                    throw new InvalidOperationException("ffmpeg exited with non-zero exit code: \n" + ffmpegStdout + "\n" + ffmpegStderr);
                }

                byte[] rawData;
                using (var fs = new FileStream(temporaryOutput, FileMode.Open, FileAccess.Read))
                {
                    rawData = new byte[fs.Length];
                    fs.Read(rawData, 0, rawData.Length);
                }

                if (saveToFile != null)
                {
                    using (var fs = new FileStream(saveToFile, FileMode.Create, FileAccess.Write))
                    {
                        fs.Write(rawData, 0, rawData.Length);
                    }

                    this.data = null;
                }
                else
                {
                    this.data = rawData.ToList();
                }

                // Get the audio metadata from the output file
                string ffprobeStdout, ffprobeStderr;
                var    ffprobeExitCode = ExternalTool.Run(
                    "ffprobe",
                    string.Format("-i \"{0}\" -show_entries streams -v quiet -of flat", temporaryOutput),
                    out ffprobeStdout,
                    out ffprobeStderr);
                if (ffprobeExitCode != 0)
                {
                    throw new InvalidOperationException("ffprobe exited with non-zero exit code.");
                }

                // Set default values if information is not available.
                int    averageBytesPerSecond = 0;
                int    bitsPerSample         = 0;
                int    blockAlign            = 0;
                int    channelCount          = 0;
                int    sampleRate            = 0;
                double durationInSeconds     = 0;

                var numberFormat = System.Globalization.CultureInfo.InvariantCulture.NumberFormat;
                foreach (var line in ffprobeStdout.Split(new[] { '\r', '\n', '\0' }, StringSplitOptions.RemoveEmptyEntries))
                {
                    var kv = line.Split(new[] { '=' }, 2);

                    switch (kv[0])
                    {
                    case "streams.stream.0.sample_rate":
                        sampleRate = int.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.bits_per_sample":
                        bitsPerSample = int.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.duration":
                        durationInSeconds = double.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.channels":
                        channelCount = int.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.bit_rate":
                        averageBytesPerSecond = (int.Parse(kv[1].Trim('"'), numberFormat) / 8);
                        break;
                    }
                }

                // Calculate blockAlign.
                switch (formatType)
                {
                case ConversionFormat.Adpcm:
                case ConversionFormat.ImaAdpcm:
                case ConversionFormat.Pcm:
                    // Block alignment value is the number of bytes in an atomic unit (that is, a block) of audio for a particular format. For Pulse Code Modulation (PCM) formats, the formula for calculating block alignment is as follows:
                    //  •   Block Alignment = Bytes per Sample x Number of Channels
                    // For example, the block alignment value for 16-bit PCM format mono audio is 2 (2 bytes per sample x 1 channel). For 16-bit PCM format stereo audio, the block alignment value is 4.
                    // https://msdn.microsoft.com/en-us/library/system.speech.audioformat.speechaudioformatinfo.blockalign(v=vs.110).aspx
                    // Get the raw PCM from the output WAV file
                    using (var reader = new BinaryReader(new MemoryStream(rawData)))
                    {
                        data = GetRawWavData(reader, ref blockAlign).ToList();
                    }
                    break;

                default:
                    // blockAlign is not available from ffprobe (and may or may not
                    // be relevant for non-PCM formats anyway)
                    break;
                }

                this.duration = TimeSpan.FromSeconds(durationInSeconds);
                this.format   = new AudioFormat(
                    averageBytesPerSecond,
                    bitsPerSample,
                    blockAlign,
                    channelCount,
                    format,
                    sampleRate);

                // Loop start and length in number of samples. Defaults to entire sound
                loopStart = 0;
                if (data != null && bitsPerSample > 0 && channelCount > 0)
                {
                    loopLength = data.Count / ((bitsPerSample / 8) * channelCount);
                }
                else
                {
                    loopLength = 0;
                }
            }
            finally
            {
                File.Delete(temporarySource);
                File.Delete(temporaryOutput);
            }
        }
        public static void ProbeFormat(
            string sourceFile,
            out AudioFileType audioFileType,
            out AudioFormat audioFormat,
            out TimeSpan duration,
            out int loopStart,
            out int loopLength)
        {
            string ffprobeArguments = string.Format(
                "-i \"{0}\" -show_format -show_entries streams -v quiet -of flat", sourceFile);

            int ffprobeExitCode = ExternalTool.Run(
                "ffprobe",
                ffprobeArguments,
                out var ffprobeStdout,
                out var ffprobeStderr);

            if (ffprobeExitCode != 0)
            {
                throw new InvalidOperationException(
                          "ffprobe exited with non-zero exit code.",
                          new Exception(ffprobeStderr.ToString()));
            }

            // Set default values if information is not available.
            int    averageBytesPerSecond = 0;
            int    bitsPerSample         = 0;
            int    blockAlign            = 0;
            int    channelCount          = 0;
            int    sampleRate            = 0;
            int    format          = 0;
            string sampleFormat    = null;
            double durationSeconds = 0;
            var    formatName      = string.Empty;

            try
            {
                var numberFormat = CultureInfo.InvariantCulture.NumberFormat;
                foreach (var line in ffprobeStdout.ToString()
                         .Split(_newlineChars, StringSplitOptions.RemoveEmptyEntries))
                {
                    string[] kv = line.Split(new[] { '=' }, 2);
                    switch (kv[0])
                    {
                    case "streams.stream.0.sample_rate":
                        sampleRate = int.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.bits_per_sample":
                        bitsPerSample = int.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.start_time":
                        if (double.TryParse(
                                kv[1].Trim('"'), NumberStyles.Any, numberFormat, out double seconds))
                        {
                            durationSeconds += seconds;
                        }
                        break;

                    case "streams.stream.0.duration":
                        durationSeconds += double.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.channels":
                        channelCount = int.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.sample_fmt":
                        sampleFormat = kv[1].Trim('"').ToUpperInvariant();
                        break;

                    case "streams.stream.0.bit_rate":
                        if (averageBytesPerSecond != 0)
                        {
                            break;
                        }

                        if (int.TryParse(
                                kv[1].Trim('"'), NumberStyles.Integer, numberFormat, out int bitsPerSec))
                        {
                            averageBytesPerSecond = bitsPerSec / 8;
                        }
                        break;

                    case "streams.stream.0.codec_tag":
                        string hex = kv[1][3..^ 1];
Пример #10
0
        /// <summary>
        /// Transcodes the source audio to the target format and quality.
        /// </summary>
        /// <param name="formatType">Format to convert this audio to.</param>
        /// <param name="quality">Quality of the processed output audio. For streaming formats, it can be one of the following: Low (96 kbps), Medium (128 kbps), Best (192 kbps).  For WAV formats, it can be one of the following: Low (11kHz ADPCM), Medium (22kHz ADPCM), Best (44kHz PCM)</param>
        /// <param name="saveToFile">
        /// The name of the file that the converted audio should be saved into.  This is used for SongContent, where
        /// the audio is stored external to the XNB file.  If this is null, then the converted audio is stored in
        /// the Data property.
        /// </param>
        public void ConvertFormat(ConversionFormat formatType, ConversionQuality quality, string saveToFile)
        {
            if (disposed)
            {
                throw new ObjectDisposedException("AudioContent");
            }

            var temporarySource = Path.GetTempFileName();
            var temporaryOutput = Path.GetTempFileName();

            try
            {
                using (var fs = new FileStream(temporarySource, FileMode.Create, FileAccess.Write))
                {
                    var dataBytes = this.data.ToArray();
                    fs.Write(dataBytes, 0, dataBytes.Length);
                }

                string ffmpegCodecName, ffmpegMuxerName;
                int    format;
                switch (formatType)
                {
                case ConversionFormat.Adpcm:
                    // ADPCM Microsoft
                    ffmpegCodecName = "adpcm_ms";
                    ffmpegMuxerName = "wav";
                    format          = 0x0002; /* WAVE_FORMAT_ADPCM */
                    break;

                case ConversionFormat.Pcm:
                    // PCM signed 16-bit little-endian
                    ffmpegCodecName = "pcm_s16le";
                    ffmpegMuxerName = "s16le";
                    format          = 0x0001; /* WAVE_FORMAT_PCM */
                    break;

                case ConversionFormat.WindowsMedia:
                    // Windows Media Audio 2
                    ffmpegCodecName = "wmav2";
                    ffmpegMuxerName = "asf";
                    format          = 0x0161; /* WAVE_FORMAT_WMAUDIO2 */
                    break;

                case ConversionFormat.Xma:
                    throw new NotSupportedException(
                              "XMA is not a supported encoding format. It is specific to the Xbox 360.");

                case ConversionFormat.ImaAdpcm:
                    // ADPCM IMA WAV
                    ffmpegCodecName = "adpcm_ima_wav";
                    ffmpegMuxerName = "wav";
                    format          = 0x0011; /* WAVE_FORMAT_IMA_ADPCM */
                    break;

                case ConversionFormat.Aac:
                    // AAC (Advanced Audio Coding)
                    // Requires -strict experimental
                    ffmpegCodecName = "aac";
                    ffmpegMuxerName = "ipod";
                    format          = 0x0000; /* WAVE_FORMAT_UNKNOWN */
                    break;

                case ConversionFormat.Vorbis:
                    // Vorbis
                    ffmpegCodecName = "libvorbis";
                    ffmpegMuxerName = "ogg";
                    format          = 0x0000; /* WAVE_FORMAT_UNKNOWN */
                    break;

                default:
                    // Unknown format
                    throw new NotSupportedException();
                }

                string ffmpegStdout, ffmpegStderr;
                var    ffmpegExitCode = ExternalTool.Run(
                    "ffmpeg",
                    string.Format(
                        "-y -i \"{0}\" -vn -c:a {1} -b:a {2} -f:a {3} -strict experimental \"{4}\"",
                        temporarySource,
                        ffmpegCodecName,
                        QualityToBitRate(quality),
                        ffmpegMuxerName,
                        temporaryOutput),
                    out ffmpegStdout,
                    out ffmpegStderr);
                if (ffmpegExitCode != 0)
                {
                    throw new InvalidOperationException("ffmpeg exited with non-zero exit code: \n" + ffmpegStdout + "\n" + ffmpegStderr);
                }

                byte[] rawData;
                using (var fs = new FileStream(temporaryOutput, FileMode.Open, FileAccess.Read))
                {
                    rawData = new byte[fs.Length];
                    fs.Read(rawData, 0, rawData.Length);
                }

                if (saveToFile != null)
                {
                    using (var fs = new FileStream(saveToFile, FileMode.Create, FileAccess.Write))
                    {
                        fs.Write(rawData, 0, rawData.Length);
                    }

                    this.data = null;
                }
                else
                {
                    this.data = rawData.ToList();
                }

                string ffprobeStdout, ffprobeStderr;
                var    ffprobeExitCode = ExternalTool.Run(
                    "ffprobe",
                    string.Format("-i \"{0}\" -show_entries streams -v quiet -of flat", temporarySource),
                    out ffprobeStdout,
                    out ffprobeStderr);
                if (ffprobeExitCode != 0)
                {
                    throw new InvalidOperationException("ffprobe exited with non-zero exit code.");
                }

                // Set default values if information is not available.
                int    averageBytesPerSecond = 0;
                int    bitsPerSample         = 0;
                int    blockAlign            = 0;
                int    channelCount          = 0;
                int    sampleRate            = 0;
                double durationInSeconds     = 0;

                var numberFormat = System.Globalization.CultureInfo.InvariantCulture.NumberFormat;
                foreach (var line in ffprobeStdout.Split(new[] { '\r', '\n', '\0' }, StringSplitOptions.RemoveEmptyEntries))
                {
                    var kv = line.Split(new[] { '=' }, 2);

                    switch (kv[0])
                    {
                    case "streams.stream.0.sample_rate":
                        sampleRate = int.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.bits_per_sample":
                        bitsPerSample = int.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.duration":
                        durationInSeconds = double.Parse(kv[1].Trim('"'), numberFormat);
                        break;

                    case "streams.stream.0.channels":
                        channelCount = int.Parse(kv[1].Trim('"'), numberFormat);
                        break;
                    }
                }

                // This information is not available from ffprobe (and may or may not
                // be relevant for non-PCM formats anyway):
                //
                // * averageBytesPerSecond
                // * blockAlign

                this.duration = TimeSpan.FromSeconds(durationInSeconds);
                this.format   = new AudioFormat(
                    averageBytesPerSecond,
                    bitsPerSample,
                    blockAlign,
                    channelCount,
                    format,
                    sampleRate);
            }
            finally
            {
                File.Delete(temporarySource);
                File.Delete(temporaryOutput);
            }
        }