├── .gitignore ├── LICENSE ├── QiyiFLV2MP4 GUI ├── App.config ├── Library │ ├── FLVFile.cs │ ├── General.cs │ ├── MP4Box.exe │ ├── WAVFile.cs │ ├── js.dll │ ├── js32.dll │ └── libgpac.dll ├── Main.Designer.cs ├── Main.cs ├── Main.resx ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings └── QiyiFLV2MP4 GUI.csproj ├── QiyiFLV2MP4.sln ├── QiyiFLV2MP4 ├── App.config ├── Library │ ├── FLVFile.cs │ ├── General.cs │ ├── MP4Box.exe │ ├── WAVFile.cs │ ├── js.dll │ ├── js32.dll │ └── libgpac.dll ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx └── QiyiFLV2MP4.csproj └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "{}" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright 2016 风漠兮 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Library/FLVFile.cs: -------------------------------------------------------------------------------- 1 | // **************************************************************************** 2 | // 3 | // FLV Extract 4 | // Copyright (C) 2006-2011 J.D. Purcell (moitah@yahoo.com) 5 | // 6 | // This program is free software; you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation; either version 2 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program; if not, write to the Free Software 18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | // 20 | // **************************************************************************** 21 | 22 | using System; 23 | using System.Collections.Generic; 24 | using System.IO; 25 | using System.Text; 26 | 27 | namespace QiyiFLV2MP4_GUI 28 | { 29 | interface IAudioWriter { 30 | void WriteChunk(byte[] chunk, uint timeStamp); 31 | void Finish(); 32 | string Path { get; } 33 | } 34 | 35 | interface IVideoWriter { 36 | void WriteChunk(byte[] chunk, uint timeStamp, int frameType); 37 | void Finish(FractionUInt32 averageFrameRate); 38 | string Path { get; } 39 | } 40 | 41 | public delegate bool OverwriteDelegate(string destPath); 42 | 43 | public class FLVFile : IDisposable { 44 | static readonly string[] _outputExtensions = new string[] { ".avi", ".mp3", ".264", ".aac", ".spx", ".txt" }; 45 | 46 | string _inputPath, _outputDirectory, _outputPathBase; 47 | OverwriteDelegate _overwrite; 48 | FileStream _fs; 49 | long _fileOffset, _fileLength; 50 | IAudioWriter _audioWriter; 51 | IVideoWriter _videoWriter; 52 | TimeCodeWriter _timeCodeWriter; 53 | List _videoTimeStamps; 54 | bool _extractAudio, _extractVideo, _extractTimeCodes; 55 | bool _extractedAudio, _extractedVideo, _extractedTimeCodes; 56 | FractionUInt32? _averageFrameRate, _trueFrameRate; 57 | List _warnings; 58 | 59 | public FLVFile(string path) { 60 | _inputPath = path; 61 | _outputDirectory = Path.GetDirectoryName(path); 62 | _warnings = new List(); 63 | _fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 65536); 64 | _fileOffset = 0; 65 | _fileLength = _fs.Length; 66 | } 67 | 68 | public void Dispose() { 69 | if (_fs != null) { 70 | _fs.Close(); 71 | _fs = null; 72 | } 73 | CloseOutput(null, true); 74 | } 75 | 76 | public void Close() { 77 | Dispose(); 78 | } 79 | 80 | public string OutputDirectory { 81 | get { return _outputDirectory; } 82 | set { _outputDirectory = value; } 83 | } 84 | 85 | public FractionUInt32? AverageFrameRate { 86 | get { return _averageFrameRate; } 87 | } 88 | 89 | public FractionUInt32? TrueFrameRate { 90 | get { return _trueFrameRate; } 91 | } 92 | 93 | public string[] Warnings { 94 | get { return _warnings.ToArray(); } 95 | } 96 | 97 | public bool ExtractedAudio { 98 | get { return _extractedAudio; } 99 | } 100 | 101 | public bool ExtractedVideo { 102 | get { return _extractedVideo; } 103 | } 104 | 105 | public bool ExtractedTimeCodes { 106 | get { return _extractedTimeCodes; } 107 | } 108 | 109 | public void ExtractStreams(bool extractAudio, bool extractVideo, bool extractTimeCodes, OverwriteDelegate overwrite) { 110 | uint dataOffset, flags, prevTagSize; 111 | 112 | _outputPathBase = Path.Combine(_outputDirectory, Path.GetFileNameWithoutExtension(_inputPath)); 113 | _overwrite = overwrite; 114 | _extractAudio = extractAudio; 115 | _extractVideo = extractVideo; 116 | _extractTimeCodes = extractTimeCodes; 117 | _videoTimeStamps = new List(); 118 | 119 | Seek(0); 120 | if (_fileLength < 4 || ReadUInt32() != 0x464C5601) { 121 | if (_fileLength >= 8 && ReadUInt32() == 0x66747970) { 122 | throw new Exception("This is a MP4 file. Please input a FLV file!"); 123 | } 124 | else { 125 | throw new Exception("This isn't a FLV file. Please input a FLV file!"); 126 | } 127 | } 128 | 129 | if (Array.IndexOf(_outputExtensions, Path.GetExtension(_inputPath).ToLowerInvariant()) != -1) { 130 | // Can't have the same extension as files we output 131 | throw new Exception("Please change the extension of this FLV file."); 132 | } 133 | 134 | if (!Directory.Exists(_outputDirectory)) { 135 | throw new Exception("Output directory doesn't exist."); 136 | } 137 | 138 | flags = ReadUInt8(); 139 | dataOffset = ReadUInt32(); 140 | 141 | Seek(dataOffset); 142 | 143 | prevTagSize = ReadUInt32(); 144 | while (_fileOffset < _fileLength) { 145 | if (!ReadTag()) break; 146 | if ((_fileLength - _fileOffset) < 4) break; 147 | prevTagSize = ReadUInt32(); 148 | } 149 | 150 | _averageFrameRate = CalculateAverageFrameRate(); 151 | _trueFrameRate = CalculateTrueFrameRate(); 152 | 153 | CloseOutput(_averageFrameRate, false); 154 | } 155 | 156 | private void CloseOutput(FractionUInt32? averageFrameRate, bool disposing) { 157 | if (_videoWriter != null) { 158 | _videoWriter.Finish(averageFrameRate ?? new FractionUInt32(25, 1)); 159 | if (disposing && (_videoWriter.Path != null)) { 160 | try { File.Delete(_videoWriter.Path); } 161 | catch { } 162 | } 163 | _videoWriter = null; 164 | } 165 | if (_audioWriter != null) { 166 | _audioWriter.Finish(); 167 | if (disposing && (_audioWriter.Path != null)) { 168 | try { File.Delete(_audioWriter.Path); } 169 | catch { } 170 | } 171 | _audioWriter = null; 172 | } 173 | if (_timeCodeWriter != null) { 174 | _timeCodeWriter.Finish(); 175 | if (disposing && (_timeCodeWriter.Path != null)) { 176 | try { File.Delete(_timeCodeWriter.Path); } 177 | catch { } 178 | } 179 | _timeCodeWriter = null; 180 | } 181 | } 182 | 183 | private bool ReadTag() { 184 | uint tagType, dataSize, timeStamp, streamID, mediaInfo; 185 | byte[] data; 186 | 187 | if ((_fileLength - _fileOffset) < 11) { 188 | return false; 189 | } 190 | 191 | // Read tag header 192 | tagType = ReadUInt8(); 193 | dataSize = ReadUInt24(); 194 | timeStamp = ReadUInt24(); 195 | timeStamp |= ReadUInt8() << 24; 196 | streamID = ReadUInt24(); 197 | 198 | // Read tag data 199 | if (dataSize == 0) { 200 | return true; 201 | } 202 | if ((_fileLength - _fileOffset) < dataSize) { 203 | return false; 204 | } 205 | mediaInfo = ReadUInt8(); 206 | dataSize -= 1; 207 | data = ReadBytes((int)dataSize); 208 | 209 | if (tagType == 0x8) { // Audio 210 | if (_audioWriter == null) { 211 | _audioWriter = _extractAudio ? GetAudioWriter(mediaInfo) : new DummyAudioWriter(); 212 | _extractedAudio = !(_audioWriter is DummyAudioWriter); 213 | } 214 | _audioWriter.WriteChunk(data, timeStamp); 215 | } 216 | else if ((tagType == 0x9) && ((mediaInfo >> 4) != 5)) { // Video 217 | if (_videoWriter == null) { 218 | _videoWriter = _extractVideo ? GetVideoWriter(mediaInfo) : new DummyVideoWriter(); 219 | _extractedVideo = !(_videoWriter is DummyVideoWriter); 220 | } 221 | if (_timeCodeWriter == null) { 222 | string path = _outputPathBase + ".txt"; 223 | _timeCodeWriter = new TimeCodeWriter((_extractTimeCodes && CanWriteTo(path)) ? path : null); 224 | _extractedTimeCodes = _extractTimeCodes; 225 | } 226 | _videoTimeStamps.Add(timeStamp); 227 | _videoWriter.WriteChunk(data, timeStamp, (int)((mediaInfo & 0xF0) >> 4)); 228 | _timeCodeWriter.Write(timeStamp); 229 | } 230 | 231 | return true; 232 | } 233 | 234 | private IAudioWriter GetAudioWriter(uint mediaInfo) { 235 | uint format = mediaInfo >> 4; 236 | uint rate = (mediaInfo >> 2) & 0x3; 237 | uint bits = (mediaInfo >> 1) & 0x1; 238 | uint chans = mediaInfo & 0x1; 239 | string path; 240 | 241 | if ((format == 2) || (format == 14)) { // MP3 242 | path = _outputPathBase + ".mp3"; 243 | if (!CanWriteTo(path)) return new DummyAudioWriter(); 244 | return new MP3Writer(path, _warnings); 245 | } 246 | else if ((format == 0) || (format == 3)) { // PCM 247 | int sampleRate = 0; 248 | switch (rate) { 249 | case 0: sampleRate = 5512; break; 250 | case 1: sampleRate = 11025; break; 251 | case 2: sampleRate = 22050; break; 252 | case 3: sampleRate = 44100; break; 253 | } 254 | path = _outputPathBase + ".wav"; 255 | if (!CanWriteTo(path)) return new DummyAudioWriter(); 256 | if (format == 0) { 257 | _warnings.Add("PCM byte order unspecified, assuming little endian."); 258 | } 259 | return new WAVWriter(path, (bits == 1) ? 16 : 8, 260 | (chans == 1) ? 2 : 1, sampleRate); 261 | } 262 | else if (format == 10) { // AAC 263 | path = _outputPathBase + ".aac"; 264 | if (!CanWriteTo(path)) return new DummyAudioWriter(); 265 | return new AACWriter(path); 266 | } 267 | else if (format == 11) { // Speex 268 | path = _outputPathBase + ".spx"; 269 | if (!CanWriteTo(path)) return new DummyAudioWriter(); 270 | return new SpeexWriter(path, (int)(_fileLength & 0xFFFFFFFF)); 271 | } 272 | else { 273 | string typeStr; 274 | 275 | if (format == 1) 276 | typeStr = "ADPCM"; 277 | else if ((format == 4) || (format == 5) || (format == 6)) 278 | typeStr = "Nellymoser"; 279 | else 280 | typeStr = "format=" + format.ToString(); 281 | 282 | _warnings.Add("Unable to extract audio (" + typeStr + " is unsupported)."); 283 | 284 | return new DummyAudioWriter(); 285 | } 286 | } 287 | 288 | private IVideoWriter GetVideoWriter(uint mediaInfo) { 289 | uint codecID = mediaInfo & 0x0F; 290 | string path; 291 | 292 | if ((codecID == 2) || (codecID == 4) || (codecID == 5)) { 293 | path = _outputPathBase + ".avi"; 294 | if (!CanWriteTo(path)) return new DummyVideoWriter(); 295 | return new AVIWriter(path, (int)codecID, _warnings); 296 | } 297 | else if (codecID == 7) { 298 | path = _outputPathBase + ".264"; 299 | if (!CanWriteTo(path)) return new DummyVideoWriter(); 300 | return new RawH264Writer(path); 301 | } 302 | else { 303 | string typeStr; 304 | 305 | if (codecID == 3) 306 | typeStr = "Screen"; 307 | else if (codecID == 6) 308 | typeStr = "Screen2"; 309 | else 310 | typeStr = "codecID=" + codecID.ToString(); 311 | 312 | _warnings.Add("Unable to extract video (" + typeStr + " is unsupported)."); 313 | 314 | return new DummyVideoWriter(); 315 | } 316 | } 317 | 318 | private bool CanWriteTo(string path) { 319 | if (File.Exists(path) && (_overwrite != null)) { 320 | return _overwrite(path); 321 | } 322 | return true; 323 | } 324 | 325 | private FractionUInt32? CalculateAverageFrameRate() { 326 | FractionUInt32 frameRate; 327 | int frameCount = _videoTimeStamps.Count; 328 | 329 | if (frameCount > 1) { 330 | frameRate.N = (uint)(frameCount - 1) * 1000; 331 | frameRate.D = _videoTimeStamps[frameCount - 1] - _videoTimeStamps[0]; 332 | frameRate.Reduce(); 333 | return frameRate; 334 | } 335 | else { 336 | return null; 337 | } 338 | } 339 | 340 | private FractionUInt32? CalculateTrueFrameRate() { 341 | FractionUInt32 frameRate; 342 | Dictionary deltaCount = new Dictionary(); 343 | int i, threshold; 344 | uint delta, count, minDelta; 345 | 346 | // Calculate the distance between the timestamps, count how many times each delta appears 347 | for (i = 1; i < _videoTimeStamps.Count; i++) { 348 | int deltaS = (int)((long)_videoTimeStamps[i] - (long)_videoTimeStamps[i - 1]); 349 | 350 | if (deltaS <= 0) continue; 351 | delta = (uint)deltaS; 352 | 353 | if (deltaCount.ContainsKey(delta)) { 354 | deltaCount[delta] += 1; 355 | } 356 | else { 357 | deltaCount.Add(delta, 1); 358 | } 359 | } 360 | 361 | threshold = _videoTimeStamps.Count / 10; 362 | minDelta = UInt32.MaxValue; 363 | 364 | // Find the smallest delta that made up at least 10% of the frames (grouping in delta+1 365 | // because of rounding, e.g. a NTSC video will have deltas of 33 and 34 ms) 366 | foreach (KeyValuePair deltaItem in deltaCount) { 367 | delta = deltaItem.Key; 368 | count = deltaItem.Value; 369 | 370 | if (deltaCount.ContainsKey(delta + 1)) { 371 | count += deltaCount[delta + 1]; 372 | } 373 | 374 | if ((count >= threshold) && (delta < minDelta)) { 375 | minDelta = delta; 376 | } 377 | } 378 | 379 | // Calculate the frame rate based on the smallest delta, and delta+1 if present 380 | if (minDelta != UInt32.MaxValue) { 381 | uint totalTime, totalFrames; 382 | 383 | count = deltaCount[minDelta]; 384 | totalTime = minDelta * count; 385 | totalFrames = count; 386 | 387 | if (deltaCount.ContainsKey(minDelta + 1)) { 388 | count = deltaCount[minDelta + 1]; 389 | totalTime += (minDelta + 1) * count; 390 | totalFrames += count; 391 | } 392 | 393 | if (totalTime != 0) { 394 | frameRate.N = totalFrames * 1000; 395 | frameRate.D = totalTime; 396 | frameRate.Reduce(); 397 | return frameRate; 398 | } 399 | } 400 | 401 | // Unable to calculate frame rate 402 | return null; 403 | } 404 | 405 | private void Seek(long offset) { 406 | _fs.Seek(offset, SeekOrigin.Begin); 407 | _fileOffset = offset; 408 | } 409 | 410 | private uint ReadUInt8() { 411 | _fileOffset += 1; 412 | return (uint)_fs.ReadByte(); 413 | } 414 | 415 | private uint ReadUInt24() { 416 | byte[] x = new byte[4]; 417 | _fs.Read(x, 1, 3); 418 | _fileOffset += 3; 419 | return BitConverterBE.ToUInt32(x, 0); 420 | } 421 | 422 | private uint ReadUInt32() { 423 | byte[] x = new byte[4]; 424 | _fs.Read(x, 0, 4); 425 | _fileOffset += 4; 426 | return BitConverterBE.ToUInt32(x, 0); 427 | } 428 | 429 | private byte[] ReadBytes(int length) { 430 | byte[] buff = new byte[length]; 431 | _fs.Read(buff, 0, length); 432 | _fileOffset += length; 433 | return buff; 434 | } 435 | } 436 | 437 | class DummyAudioWriter : IAudioWriter { 438 | public DummyAudioWriter() { 439 | } 440 | 441 | public void WriteChunk(byte[] chunk, uint timeStamp) { 442 | } 443 | 444 | public void Finish() { 445 | } 446 | 447 | public string Path { 448 | get { 449 | return null; 450 | } 451 | } 452 | } 453 | 454 | class DummyVideoWriter : IVideoWriter { 455 | public DummyVideoWriter() { 456 | } 457 | 458 | public void WriteChunk(byte[] chunk, uint timeStamp, int frameType) { 459 | } 460 | 461 | public void Finish(FractionUInt32 averageFrameRate) { 462 | } 463 | 464 | public string Path { 465 | get { 466 | return null; 467 | } 468 | } 469 | } 470 | 471 | class MP3Writer : IAudioWriter { 472 | string _path; 473 | FileStream _fs; 474 | List _warnings; 475 | List _chunkBuffer; 476 | List _frameOffsets; 477 | uint _totalFrameLength; 478 | bool _isVBR; 479 | bool _delayWrite; 480 | bool _hasVBRHeader; 481 | bool _writeVBRHeader; 482 | int _firstBitRate; 483 | int _mpegVersion; 484 | int _sampleRate; 485 | int _channelMode; 486 | uint _firstFrameHeader; 487 | 488 | public MP3Writer(string path, List warnings) { 489 | _path = path; 490 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536); 491 | _warnings = warnings; 492 | _chunkBuffer = new List(); 493 | _frameOffsets = new List(); 494 | _delayWrite = true; 495 | } 496 | 497 | public void WriteChunk(byte[] chunk, uint timeStamp) { 498 | _chunkBuffer.Add(chunk); 499 | ParseMP3Frames(chunk); 500 | if (_delayWrite && _totalFrameLength >= 65536) { 501 | _delayWrite = false; 502 | } 503 | if (!_delayWrite) { 504 | Flush(); 505 | } 506 | } 507 | 508 | public void Finish() { 509 | Flush(); 510 | if (_writeVBRHeader) { 511 | _fs.Seek(0, SeekOrigin.Begin); 512 | WriteVBRHeader(false); 513 | } 514 | _fs.Close(); 515 | } 516 | 517 | public string Path { 518 | get { 519 | return _path; 520 | } 521 | } 522 | 523 | private void Flush() { 524 | foreach (byte[] chunk in _chunkBuffer) { 525 | _fs.Write(chunk, 0, chunk.Length); 526 | } 527 | _chunkBuffer.Clear(); 528 | } 529 | 530 | private void ParseMP3Frames(byte[] buff) { 531 | int[] MPEG1BitRate = new int[] { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }; 532 | int[] MPEG2XBitRate = new int[] { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }; 533 | int[] MPEG1SampleRate = new int[] { 44100, 48000, 32000, 0 }; 534 | int[] MPEG20SampleRate = new int[] { 22050, 24000, 16000, 0 }; 535 | int[] MPEG25SampleRate = new int[] { 11025, 12000, 8000, 0 }; 536 | 537 | int offset = 0; 538 | int length = buff.Length; 539 | 540 | while (length >= 4) { 541 | ulong header; 542 | int mpegVersion, layer, bitRate, sampleRate, padding, channelMode; 543 | int frameLen; 544 | 545 | header = (ulong)BitConverterBE.ToUInt32(buff, offset) << 32; 546 | if (BitHelper.Read(ref header, 11) != 0x7FF) { 547 | break; 548 | } 549 | mpegVersion = BitHelper.Read(ref header, 2); 550 | layer = BitHelper.Read(ref header, 2); 551 | BitHelper.Read(ref header, 1); 552 | bitRate = BitHelper.Read(ref header, 4); 553 | sampleRate = BitHelper.Read(ref header, 2); 554 | padding = BitHelper.Read(ref header, 1); 555 | BitHelper.Read(ref header, 1); 556 | channelMode = BitHelper.Read(ref header, 2); 557 | 558 | if ((mpegVersion == 1) || (layer != 1) || (bitRate == 0) || (bitRate == 15) || (sampleRate == 3)) { 559 | break; 560 | } 561 | 562 | bitRate = ((mpegVersion == 3) ? MPEG1BitRate[bitRate] : MPEG2XBitRate[bitRate]) * 1000; 563 | 564 | if (mpegVersion == 3) 565 | sampleRate = MPEG1SampleRate[sampleRate]; 566 | else if (mpegVersion == 2) 567 | sampleRate = MPEG20SampleRate[sampleRate]; 568 | else 569 | sampleRate = MPEG25SampleRate[sampleRate]; 570 | 571 | frameLen = GetFrameLength(mpegVersion, bitRate, sampleRate, padding); 572 | if (frameLen > length) { 573 | break; 574 | } 575 | 576 | bool isVBRHeaderFrame = false; 577 | if (_frameOffsets.Count == 0) { 578 | // Check for an existing VBR header just to be safe (I haven't seen any in FLVs) 579 | int o = offset + GetFrameDataOffset(mpegVersion, channelMode); 580 | if (BitConverterBE.ToUInt32(buff, o) == 0x58696E67) { // "Xing" 581 | isVBRHeaderFrame = true; 582 | _delayWrite = false; 583 | _hasVBRHeader = true; 584 | } 585 | } 586 | 587 | if (isVBRHeaderFrame) { } 588 | else if (_firstBitRate == 0) { 589 | _firstBitRate = bitRate; 590 | _mpegVersion = mpegVersion; 591 | _sampleRate = sampleRate; 592 | _channelMode = channelMode; 593 | _firstFrameHeader = BitConverterBE.ToUInt32(buff, offset); 594 | } 595 | else if (!_isVBR && (bitRate != _firstBitRate)) { 596 | _isVBR = true; 597 | if (_hasVBRHeader) { } 598 | else if (_delayWrite) { 599 | WriteVBRHeader(true); 600 | _writeVBRHeader = true; 601 | _delayWrite = false; 602 | } 603 | else { 604 | _warnings.Add("Detected VBR too late, cannot add VBR header."); 605 | } 606 | } 607 | 608 | _frameOffsets.Add(_totalFrameLength + (uint)offset); 609 | 610 | offset += frameLen; 611 | length -= frameLen; 612 | } 613 | 614 | _totalFrameLength += (uint)buff.Length; 615 | } 616 | 617 | private void WriteVBRHeader(bool isPlaceholder) { 618 | byte[] buff = new byte[GetFrameLength(_mpegVersion, 64000, _sampleRate, 0)]; 619 | if (!isPlaceholder) { 620 | uint header = _firstFrameHeader; 621 | int dataOffset = GetFrameDataOffset(_mpegVersion, _channelMode); 622 | header &= 0xFFFF0DFF; // Clear bitrate and padding fields 623 | header |= 0x00010000; // Set protection bit (indicates that CRC is NOT present) 624 | header |= (uint)((_mpegVersion == 3) ? 5 : 8) << 12; // 64 kbit/sec 625 | General.CopyBytes(buff, 0, BitConverterBE.GetBytes(header)); 626 | General.CopyBytes(buff, dataOffset, BitConverterBE.GetBytes((uint)0x58696E67)); // "Xing" 627 | General.CopyBytes(buff, dataOffset + 4, BitConverterBE.GetBytes((uint)0x7)); // Flags 628 | General.CopyBytes(buff, dataOffset + 8, BitConverterBE.GetBytes((uint)_frameOffsets.Count)); // Frame count 629 | General.CopyBytes(buff, dataOffset + 12, BitConverterBE.GetBytes((uint)_totalFrameLength)); // File length 630 | for (int i = 0; i < 100; i++) { 631 | int frameIndex = (int)((i / 100.0) * _frameOffsets.Count); 632 | buff[dataOffset + 16 + i] = (byte)((_frameOffsets[frameIndex] / (double)_totalFrameLength) * 256.0); 633 | } 634 | } 635 | _fs.Write(buff, 0, buff.Length); 636 | } 637 | 638 | private int GetFrameLength(int mpegVersion, int bitRate, int sampleRate, int padding) { 639 | return ((mpegVersion == 3) ? 144 : 72) * bitRate / sampleRate + padding; 640 | } 641 | 642 | private int GetFrameDataOffset(int mpegVersion, int channelMode) { 643 | return 4 + ((mpegVersion == 3) ? 644 | ((channelMode == 3) ? 17 : 32) : 645 | ((channelMode == 3) ? 9 : 17)); 646 | } 647 | } 648 | 649 | class SpeexWriter : IAudioWriter { 650 | const string _vendorString = "FLV Extract"; 651 | const uint _sampleRate = 16000; 652 | const uint _msPerFrame = 20; 653 | const uint _samplesPerFrame = _sampleRate / (1000 / _msPerFrame); 654 | const int _targetPageDataSize = 4096; 655 | 656 | string _path; 657 | FileStream _fs; 658 | int _serialNumber; 659 | List _packetList; 660 | int _packetListDataSize; 661 | byte[] _pageBuff; 662 | int _pageBuffOffset; 663 | uint _pageSequenceNumber; 664 | ulong _granulePosition; 665 | 666 | public SpeexWriter(string path, int serialNumber) { 667 | _path = path; 668 | _serialNumber = serialNumber; 669 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536); 670 | _fs.Seek((28 + 80) + (28 + 8 + _vendorString.Length), SeekOrigin.Begin); // Speex header + Vorbis comment 671 | _packetList = new List(); 672 | _packetListDataSize = 0; 673 | _pageBuff = new byte[27 + 255 + _targetPageDataSize + 254]; // Header + max segment table + target data size + extra segment 674 | _pageBuffOffset = 0; 675 | _pageSequenceNumber = 2; // First audio packet 676 | _granulePosition = 0; 677 | } 678 | 679 | public void WriteChunk(byte[] chunk, uint timeStamp) { 680 | int[] subModeSizes = new int[] { 0, 43, 119, 160, 220, 300, 364, 492, 79 }; 681 | int[] wideBandSizes = new int[] { 0, 36, 112, 192, 352 }; 682 | int[] inBandSignalSizes = new int[] { 1, 1, 4, 4, 4, 4, 4, 4, 8, 8, 16, 16, 32, 32, 64, 64 }; 683 | int frameStart = -1; 684 | int frameEnd = 0; 685 | int offset = 0; 686 | int length = chunk.Length * 8; 687 | int x; 688 | 689 | while (length - offset >= 5) { 690 | x = BitHelper.Read(chunk, ref offset, 1); 691 | if (x != 0) { 692 | // wideband frame 693 | x = BitHelper.Read(chunk, ref offset, 3); 694 | if (x < 1 || x > 4) goto Error; 695 | offset += wideBandSizes[x] - 4; 696 | } 697 | else { 698 | x = BitHelper.Read(chunk, ref offset, 4); 699 | if (x >= 1 && x <= 8) { 700 | // narrowband frame 701 | if (frameStart != -1) { 702 | WriteFramePacket(chunk, frameStart, frameEnd); 703 | } 704 | frameStart = frameEnd; 705 | offset += subModeSizes[x] - 5; 706 | } 707 | else if (x == 15) { 708 | // terminator 709 | break; 710 | } 711 | else if (x == 14) { 712 | // in-band signal 713 | if (length - offset < 4) goto Error; 714 | x = BitHelper.Read(chunk, ref offset, 4); 715 | offset += inBandSignalSizes[x]; 716 | } 717 | else if (x == 13) { 718 | // custom in-band signal 719 | if (length - offset < 5) goto Error; 720 | x = BitHelper.Read(chunk, ref offset, 5); 721 | offset += x * 8; 722 | } 723 | else goto Error; 724 | } 725 | frameEnd = offset; 726 | } 727 | if (offset > length) goto Error; 728 | 729 | if (frameStart != -1) { 730 | WriteFramePacket(chunk, frameStart, frameEnd); 731 | } 732 | 733 | return; 734 | 735 | Error: 736 | throw new Exception("Invalid Speex data."); 737 | } 738 | 739 | public void Finish() { 740 | WritePage(); 741 | FlushPage(true); 742 | _fs.Seek(0, SeekOrigin.Begin); 743 | _pageSequenceNumber = 0; 744 | _granulePosition = 0; 745 | WriteSpeexHeaderPacket(); 746 | WriteVorbisCommentPacket(); 747 | FlushPage(false); 748 | _fs.Close(); 749 | } 750 | 751 | public string Path { 752 | get { 753 | return _path; 754 | } 755 | } 756 | 757 | private void WriteFramePacket(byte[] data, int startBit, int endBit) { 758 | int lengthBits = endBit - startBit; 759 | byte[] frame = BitHelper.CopyBlock(data, startBit, lengthBits); 760 | if (lengthBits % 8 != 0) { 761 | frame[frame.Length - 1] |= (byte)(0xFF >> ((lengthBits % 8) + 1)); // padding 762 | } 763 | AddPacket(frame, _samplesPerFrame, true); 764 | } 765 | 766 | private void WriteSpeexHeaderPacket() { 767 | byte[] data = new byte[80]; 768 | General.CopyBytes(data, 0, Encoding.ASCII.GetBytes("Speex ")); // speex_string 769 | General.CopyBytes(data, 8, Encoding.ASCII.GetBytes("unknown")); // speex_version 770 | data[28] = 1; // speex_version_id 771 | data[32] = 80; // header_size 772 | General.CopyBytes(data, 36, BitConverterLE.GetBytes((uint)_sampleRate)); // rate 773 | data[40] = 1; // mode (e.g. narrowband, wideband) 774 | data[44] = 4; // mode_bitstream_version 775 | data[48] = 1; // nb_channels 776 | General.CopyBytes(data, 52, BitConverterLE.GetBytes(unchecked((uint)-1))); // bitrate 777 | General.CopyBytes(data, 56, BitConverterLE.GetBytes((uint)_samplesPerFrame)); // frame_size 778 | data[60] = 0; // vbr 779 | data[64] = 1; // frames_per_packet 780 | AddPacket(data, 0, false); 781 | } 782 | 783 | private void WriteVorbisCommentPacket() { 784 | byte[] vendorStringBytes = Encoding.ASCII.GetBytes(_vendorString); 785 | byte[] data = new byte[8 + vendorStringBytes.Length]; 786 | data[0] = (byte)vendorStringBytes.Length; 787 | General.CopyBytes(data, 4, vendorStringBytes); 788 | AddPacket(data, 0, false); 789 | } 790 | 791 | private void AddPacket(byte[] data, uint sampleLength, bool delayWrite) { 792 | OggPacket packet = new OggPacket(); 793 | if (data.Length >= 255) { 794 | throw new Exception("Packet exceeds maximum size."); 795 | } 796 | _granulePosition += sampleLength; 797 | packet.Data = data; 798 | packet.GranulePosition = _granulePosition; 799 | _packetList.Add(packet); 800 | _packetListDataSize += data.Length; 801 | if (!delayWrite || (_packetListDataSize >= _targetPageDataSize) || (_packetList.Count == 255)) { 802 | WritePage(); 803 | } 804 | } 805 | 806 | private void WritePage() { 807 | if (_packetList.Count == 0) return; 808 | FlushPage(false); 809 | WriteToPage(BitConverterBE.GetBytes(0x4F676753U), 0, 4); // "OggS" 810 | WriteToPage((byte)0); // Stream structure version 811 | WriteToPage((byte)((_pageSequenceNumber == 0) ? 0x02 : 0)); // Page flags 812 | WriteToPage((ulong)_packetList[_packetList.Count - 1].GranulePosition); // Position in samples 813 | WriteToPage((uint)_serialNumber); // Stream serial number 814 | WriteToPage((uint)_pageSequenceNumber); // Page sequence number 815 | WriteToPage((uint)0); // Checksum 816 | WriteToPage((byte)_packetList.Count); // Page segment count 817 | foreach (OggPacket packet in _packetList) { 818 | WriteToPage((byte)packet.Data.Length); 819 | } 820 | foreach (OggPacket packet in _packetList) { 821 | WriteToPage(packet.Data, 0, packet.Data.Length); 822 | } 823 | _packetList.Clear(); 824 | _packetListDataSize = 0; 825 | _pageSequenceNumber++; 826 | } 827 | 828 | private void FlushPage(bool isLastPage) { 829 | if (_pageBuffOffset == 0) return; 830 | if (isLastPage) _pageBuff[5] |= 0x04; 831 | uint crc = OggCRC.Calculate(_pageBuff, 0, _pageBuffOffset); 832 | General.CopyBytes(_pageBuff, 22, BitConverterLE.GetBytes(crc)); 833 | _fs.Write(_pageBuff, 0, _pageBuffOffset); 834 | _pageBuffOffset = 0; 835 | } 836 | 837 | private void WriteToPage(byte[] data, int offset, int length) { 838 | Buffer.BlockCopy(data, offset, _pageBuff, _pageBuffOffset, length); 839 | _pageBuffOffset += length; 840 | } 841 | 842 | private void WriteToPage(byte data) { 843 | WriteToPage(new byte[] { data }, 0, 1); 844 | } 845 | 846 | private void WriteToPage(uint data) { 847 | WriteToPage(BitConverterLE.GetBytes(data), 0, 4); 848 | } 849 | 850 | private void WriteToPage(ulong data) { 851 | WriteToPage(BitConverterLE.GetBytes(data), 0, 8); 852 | } 853 | 854 | class OggPacket { 855 | public ulong GranulePosition; 856 | public byte[] Data; 857 | } 858 | } 859 | 860 | class AACWriter : IAudioWriter { 861 | string _path; 862 | FileStream _fs; 863 | int _aacProfile; 864 | int _sampleRateIndex; 865 | int _channelConfig; 866 | 867 | public AACWriter(string path) { 868 | _path = path; 869 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536); 870 | } 871 | 872 | public void WriteChunk(byte[] chunk, uint timeStamp) { 873 | if (chunk.Length < 1) return; 874 | 875 | if (chunk[0] == 0) { // Header 876 | if (chunk.Length < 3) return; 877 | 878 | ulong bits = (ulong)BitConverterBE.ToUInt16(chunk, 1) << 48; 879 | 880 | _aacProfile = BitHelper.Read(ref bits, 5) - 1; 881 | _sampleRateIndex = BitHelper.Read(ref bits, 4); 882 | _channelConfig = BitHelper.Read(ref bits, 4); 883 | 884 | if ((_aacProfile < 0) || (_aacProfile > 3)) 885 | throw new Exception("Unsupported AAC profile."); 886 | if (_sampleRateIndex > 12) 887 | throw new Exception("Invalid AAC sample rate index."); 888 | if (_channelConfig > 6) 889 | throw new Exception("Invalid AAC channel configuration."); 890 | } 891 | else { // Audio data 892 | int dataSize = chunk.Length - 1; 893 | ulong bits = 0; 894 | 895 | // Reference: WriteADTSHeader from FAAC's bitstream.c 896 | 897 | BitHelper.Write(ref bits, 12, 0xFFF); 898 | BitHelper.Write(ref bits, 1, 0); 899 | BitHelper.Write(ref bits, 2, 0); 900 | BitHelper.Write(ref bits, 1, 1); 901 | BitHelper.Write(ref bits, 2, _aacProfile); 902 | BitHelper.Write(ref bits, 4, _sampleRateIndex); 903 | BitHelper.Write(ref bits, 1, 0); 904 | BitHelper.Write(ref bits, 3, _channelConfig); 905 | BitHelper.Write(ref bits, 1, 0); 906 | BitHelper.Write(ref bits, 1, 0); 907 | BitHelper.Write(ref bits, 1, 0); 908 | BitHelper.Write(ref bits, 1, 0); 909 | BitHelper.Write(ref bits, 13, 7 + dataSize); 910 | BitHelper.Write(ref bits, 11, 0x7FF); 911 | BitHelper.Write(ref bits, 2, 0); 912 | 913 | _fs.Write(BitConverterBE.GetBytes(bits), 1, 7); 914 | _fs.Write(chunk, 1, dataSize); 915 | } 916 | } 917 | 918 | public void Finish() { 919 | _fs.Close(); 920 | } 921 | 922 | public string Path { 923 | get { 924 | return _path; 925 | } 926 | } 927 | } 928 | 929 | class RawH264Writer : IVideoWriter { 930 | static readonly byte[] _startCode = new byte[] { 0, 0, 0, 1 }; 931 | 932 | string _path; 933 | FileStream _fs; 934 | int _nalLengthSize; 935 | 936 | public RawH264Writer(string path) { 937 | _path = path; 938 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536); 939 | } 940 | 941 | public void WriteChunk(byte[] chunk, uint timeStamp, int frameType) { 942 | if (chunk.Length < 4) return; 943 | 944 | // Reference: decode_frame from libavcodec's h264.c 945 | 946 | if (chunk[0] == 0) { // Headers 947 | if (chunk.Length < 10) return; 948 | 949 | int offset, spsCount, ppsCount; 950 | 951 | offset = 8; 952 | _nalLengthSize = (chunk[offset++] & 0x03) + 1; 953 | spsCount = chunk[offset++] & 0x1F; 954 | ppsCount = -1; 955 | 956 | while (offset <= chunk.Length - 2) { 957 | if ((spsCount == 0) && (ppsCount == -1)) { 958 | ppsCount = chunk[offset++]; 959 | continue; 960 | } 961 | 962 | if (spsCount > 0) spsCount--; 963 | else if (ppsCount > 0) ppsCount--; 964 | else break; 965 | 966 | int len = (int)BitConverterBE.ToUInt16(chunk, offset); 967 | offset += 2; 968 | if (offset + len > chunk.Length) break; 969 | _fs.Write(_startCode, 0, _startCode.Length); 970 | _fs.Write(chunk, offset, len); 971 | offset += len; 972 | } 973 | } 974 | else { // Video data 975 | int offset = 4; 976 | 977 | if (_nalLengthSize != 2) { 978 | _nalLengthSize = 4; 979 | } 980 | 981 | while (offset <= chunk.Length - _nalLengthSize) { 982 | int len = (_nalLengthSize == 2) ? 983 | (int)BitConverterBE.ToUInt16(chunk, offset) : 984 | (int)BitConverterBE.ToUInt32(chunk, offset); 985 | offset += _nalLengthSize; 986 | if (offset + len > chunk.Length) break; 987 | _fs.Write(_startCode, 0, _startCode.Length); 988 | _fs.Write(chunk, offset, len); 989 | offset += len; 990 | } 991 | } 992 | } 993 | 994 | public void Finish(FractionUInt32 averageFrameRate) { 995 | _fs.Close(); 996 | } 997 | 998 | public string Path { 999 | get { 1000 | return _path; 1001 | } 1002 | } 1003 | } 1004 | 1005 | class WAVWriter : IAudioWriter { 1006 | string _path; 1007 | WAVTools.WAVWriter _wr; 1008 | int blockAlign; 1009 | 1010 | public WAVWriter(string path, int bitsPerSample, int channelCount, int sampleRate) { 1011 | _path = path; 1012 | _wr = new WAVTools.WAVWriter(path, bitsPerSample, channelCount, sampleRate); 1013 | blockAlign = (bitsPerSample / 8) * channelCount; 1014 | } 1015 | 1016 | public void WriteChunk(byte[] chunk, uint timeStamp) { 1017 | _wr.Write(chunk, chunk.Length / blockAlign); 1018 | } 1019 | 1020 | public void Finish() { 1021 | _wr.Close(); 1022 | } 1023 | 1024 | public string Path { 1025 | get { 1026 | return _path; 1027 | } 1028 | } 1029 | } 1030 | 1031 | class AVIWriter : IVideoWriter { 1032 | string _path; 1033 | BinaryWriter _bw; 1034 | int _codecID; 1035 | int _width, _height, _frameCount; 1036 | uint _moviDataSize, _indexChunkSize; 1037 | List _index; 1038 | bool _isAlphaWriter; 1039 | AVIWriter _alphaWriter; 1040 | List _warnings; 1041 | 1042 | // Chunk: Off: Len: 1043 | // 1044 | // RIFF AVI 0 12 1045 | // LIST hdrl 12 12 1046 | // avih 24 64 1047 | // LIST strl 88 12 1048 | // strh 100 64 1049 | // strf 164 48 1050 | // LIST movi 212 12 1051 | // (frames) 224 ??? 1052 | // idx1 ??? ??? 1053 | 1054 | public AVIWriter(string path, int codecID, List warnings) : 1055 | this(path, codecID, warnings, false) { } 1056 | 1057 | private AVIWriter(string path, int codecID, List warnings, bool isAlphaWriter) { 1058 | if ((codecID != 2) && (codecID != 4) && (codecID != 5)) { 1059 | throw new Exception("Unsupported video codec."); 1060 | } 1061 | 1062 | FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read); 1063 | 1064 | _path = path; 1065 | _bw = new BinaryWriter(fs); 1066 | _codecID = codecID; 1067 | _warnings = warnings; 1068 | _isAlphaWriter = isAlphaWriter; 1069 | 1070 | if ((codecID == 5) && !_isAlphaWriter) { 1071 | _alphaWriter = new AVIWriter(path.Substring(0, path.Length - 4) + ".alpha.avi", codecID, warnings, true); 1072 | } 1073 | 1074 | WriteFourCC("RIFF"); 1075 | _bw.Write((uint)0); // chunk size 1076 | WriteFourCC("AVI "); 1077 | 1078 | WriteFourCC("LIST"); 1079 | _bw.Write((uint)192); 1080 | WriteFourCC("hdrl"); 1081 | 1082 | WriteFourCC("avih"); 1083 | _bw.Write((uint)56); 1084 | _bw.Write((uint)0); 1085 | _bw.Write((uint)0); 1086 | _bw.Write((uint)0); 1087 | _bw.Write((uint)0x10); 1088 | _bw.Write((uint)0); // frame count 1089 | _bw.Write((uint)0); 1090 | _bw.Write((uint)1); 1091 | _bw.Write((uint)0); 1092 | _bw.Write((uint)0); // width 1093 | _bw.Write((uint)0); // height 1094 | _bw.Write((uint)0); 1095 | _bw.Write((uint)0); 1096 | _bw.Write((uint)0); 1097 | _bw.Write((uint)0); 1098 | 1099 | WriteFourCC("LIST"); 1100 | _bw.Write((uint)116); 1101 | WriteFourCC("strl"); 1102 | 1103 | WriteFourCC("strh"); 1104 | _bw.Write((uint)56); 1105 | WriteFourCC("vids"); 1106 | WriteFourCC(CodecFourCC); 1107 | _bw.Write((uint)0); 1108 | _bw.Write((uint)0); 1109 | _bw.Write((uint)0); 1110 | _bw.Write((uint)0); // frame rate denominator 1111 | _bw.Write((uint)0); // frame rate numerator 1112 | _bw.Write((uint)0); 1113 | _bw.Write((uint)0); // frame count 1114 | _bw.Write((uint)0); 1115 | _bw.Write((int)-1); 1116 | _bw.Write((uint)0); 1117 | _bw.Write((ushort)0); 1118 | _bw.Write((ushort)0); 1119 | _bw.Write((ushort)0); // width 1120 | _bw.Write((ushort)0); // height 1121 | 1122 | WriteFourCC("strf"); 1123 | _bw.Write((uint)40); 1124 | _bw.Write((uint)40); 1125 | _bw.Write((uint)0); // width 1126 | _bw.Write((uint)0); // height 1127 | _bw.Write((ushort)1); 1128 | _bw.Write((ushort)24); 1129 | WriteFourCC(CodecFourCC); 1130 | _bw.Write((uint)0); // biSizeImage 1131 | _bw.Write((uint)0); 1132 | _bw.Write((uint)0); 1133 | _bw.Write((uint)0); 1134 | _bw.Write((uint)0); 1135 | 1136 | WriteFourCC("LIST"); 1137 | _bw.Write((uint)0); // chunk size 1138 | WriteFourCC("movi"); 1139 | 1140 | _index = new List(); 1141 | } 1142 | 1143 | public void WriteChunk(byte[] chunk, uint timeStamp, int frameType) { 1144 | int offset, len; 1145 | 1146 | offset = 0; 1147 | len = chunk.Length; 1148 | if (_codecID == 4) { 1149 | offset = 1; 1150 | len -= 1; 1151 | } 1152 | if (_codecID == 5) { 1153 | offset = 4; 1154 | if (len >= 4) { 1155 | int alphaOffset = (int)BitConverterBE.ToUInt32(chunk, 0) & 0xFFFFFF; 1156 | if (!_isAlphaWriter) { 1157 | len = alphaOffset; 1158 | } 1159 | else { 1160 | offset += alphaOffset; 1161 | len -= offset; 1162 | } 1163 | } 1164 | else { 1165 | len = 0; 1166 | } 1167 | } 1168 | len = Math.Max(len, 0); 1169 | len = Math.Min(len, chunk.Length - offset); 1170 | 1171 | _index.Add((frameType == 1) ? (uint)0x10 : (uint)0); 1172 | _index.Add(_moviDataSize + 4); 1173 | _index.Add((uint)len); 1174 | 1175 | if ((_width == 0) && (_height == 0)) { 1176 | GetFrameSize(chunk); 1177 | } 1178 | 1179 | WriteFourCC("00dc"); 1180 | _bw.Write(len); 1181 | _bw.Write(chunk, offset, len); 1182 | 1183 | if ((len % 2) != 0) { 1184 | _bw.Write((byte)0); 1185 | len++; 1186 | } 1187 | _moviDataSize += (uint)len + 8; 1188 | _frameCount++; 1189 | 1190 | if (_alphaWriter != null) { 1191 | _alphaWriter.WriteChunk(chunk, timeStamp, frameType); 1192 | } 1193 | } 1194 | 1195 | private void GetFrameSize(byte[] chunk) { 1196 | if (_codecID == 2) { 1197 | // Reference: flv_h263_decode_picture_header from libavcodec's h263.c 1198 | 1199 | if (chunk.Length < 10) return; 1200 | 1201 | if ((chunk[0] != 0) || (chunk[1] != 0)) { 1202 | return; 1203 | } 1204 | 1205 | ulong x = BitConverterBE.ToUInt64(chunk, 2); 1206 | int format; 1207 | 1208 | if (BitHelper.Read(ref x, 1) != 1) { 1209 | return; 1210 | } 1211 | BitHelper.Read(ref x, 5); 1212 | BitHelper.Read(ref x, 8); 1213 | 1214 | format = BitHelper.Read(ref x, 3); 1215 | switch (format) { 1216 | case 0: 1217 | _width = BitHelper.Read(ref x, 8); 1218 | _height = BitHelper.Read(ref x, 8); 1219 | break; 1220 | case 1: 1221 | _width = BitHelper.Read(ref x, 16); 1222 | _height = BitHelper.Read(ref x, 16); 1223 | break; 1224 | case 2: 1225 | _width = 352; 1226 | _height = 288; 1227 | break; 1228 | case 3: 1229 | _width = 176; 1230 | _height = 144; 1231 | break; 1232 | case 4: 1233 | _width = 128; 1234 | _height = 96; 1235 | break; 1236 | case 5: 1237 | _width = 320; 1238 | _height = 240; 1239 | break; 1240 | case 6: 1241 | _width = 160; 1242 | _height = 120; 1243 | break; 1244 | default: 1245 | return; 1246 | } 1247 | } 1248 | else if ((_codecID == 4) || (_codecID == 5)) { 1249 | // Reference: vp6_parse_header from libavcodec's vp6.c 1250 | 1251 | int skip = (_codecID == 4) ? 1 : 4; 1252 | if (chunk.Length < (skip + 8)) return; 1253 | ulong x = BitConverterBE.ToUInt64(chunk, skip); 1254 | 1255 | int deltaFrameFlag = BitHelper.Read(ref x, 1); 1256 | int quant = BitHelper.Read(ref x, 6); 1257 | int separatedCoeffFlag = BitHelper.Read(ref x, 1); 1258 | int subVersion = BitHelper.Read(ref x, 5); 1259 | int filterHeader = BitHelper.Read(ref x, 2); 1260 | int interlacedFlag = BitHelper.Read(ref x, 1); 1261 | 1262 | if (deltaFrameFlag != 0) { 1263 | return; 1264 | } 1265 | if ((separatedCoeffFlag != 0) || (filterHeader == 0)) { 1266 | BitHelper.Read(ref x, 16); 1267 | } 1268 | 1269 | _height = BitHelper.Read(ref x, 8) * 16; 1270 | _width = BitHelper.Read(ref x, 8) * 16; 1271 | 1272 | // chunk[0] contains the width and height (4 bits each, respectively) that should 1273 | // be cropped off during playback, which will be non-zero if the encoder padded 1274 | // the frames to a macroblock boundary. But if you use this adjusted size in the 1275 | // AVI header, DirectShow seems to ignore it, and it can cause stride or chroma 1276 | // alignment problems with VFW if the width/height aren't multiples of 4. 1277 | if (!_isAlphaWriter) { 1278 | int cropX = chunk[0] >> 4; 1279 | int cropY = chunk[0] & 0x0F; 1280 | if (((cropX != 0) || (cropY != 0)) && !_isAlphaWriter) { 1281 | _warnings.Add(String.Format("Suggested cropping: {0} pixels from right, {1} pixels from bottom.", cropX, cropY)); 1282 | } 1283 | } 1284 | } 1285 | } 1286 | 1287 | private string CodecFourCC { 1288 | get { 1289 | if (_codecID == 2) { 1290 | return "FLV1"; 1291 | } 1292 | if ((_codecID == 4) || (_codecID == 5)) { 1293 | return "VP6F"; 1294 | } 1295 | return "NULL"; 1296 | } 1297 | } 1298 | 1299 | private void WriteIndexChunk() { 1300 | uint indexDataSize = (uint)_frameCount * 16; 1301 | 1302 | WriteFourCC("idx1"); 1303 | _bw.Write(indexDataSize); 1304 | 1305 | for (int i = 0; i < _frameCount; i++) { 1306 | WriteFourCC("00dc"); 1307 | _bw.Write(_index[(i * 3) + 0]); 1308 | _bw.Write(_index[(i * 3) + 1]); 1309 | _bw.Write(_index[(i * 3) + 2]); 1310 | } 1311 | 1312 | _indexChunkSize = indexDataSize + 8; 1313 | } 1314 | 1315 | public void Finish(FractionUInt32 averageFrameRate) { 1316 | WriteIndexChunk(); 1317 | 1318 | _bw.BaseStream.Seek(4, SeekOrigin.Begin); 1319 | _bw.Write((uint)(224 + _moviDataSize + _indexChunkSize - 8)); 1320 | 1321 | _bw.BaseStream.Seek(24 + 8, SeekOrigin.Begin); 1322 | _bw.Write((uint)0); 1323 | _bw.BaseStream.Seek(12, SeekOrigin.Current); 1324 | _bw.Write((uint)_frameCount); 1325 | _bw.BaseStream.Seek(12, SeekOrigin.Current); 1326 | _bw.Write((uint)_width); 1327 | _bw.Write((uint)_height); 1328 | 1329 | _bw.BaseStream.Seek(100 + 28, SeekOrigin.Begin); 1330 | _bw.Write((uint)averageFrameRate.D); 1331 | _bw.Write((uint)averageFrameRate.N); 1332 | _bw.BaseStream.Seek(4, SeekOrigin.Current); 1333 | _bw.Write((uint)_frameCount); 1334 | _bw.BaseStream.Seek(16, SeekOrigin.Current); 1335 | _bw.Write((ushort)_width); 1336 | _bw.Write((ushort)_height); 1337 | 1338 | _bw.BaseStream.Seek(164 + 12, SeekOrigin.Begin); 1339 | _bw.Write((uint)_width); 1340 | _bw.Write((uint)_height); 1341 | _bw.BaseStream.Seek(8, SeekOrigin.Current); 1342 | _bw.Write((uint)(_width * _height * 6)); 1343 | 1344 | _bw.BaseStream.Seek(212 + 4, SeekOrigin.Begin); 1345 | _bw.Write((uint)(_moviDataSize + 4)); 1346 | 1347 | _bw.Close(); 1348 | 1349 | if (_alphaWriter != null) { 1350 | _alphaWriter.Finish(averageFrameRate); 1351 | } 1352 | } 1353 | 1354 | private void WriteFourCC(string fourCC) { 1355 | byte[] bytes = System.Text.Encoding.ASCII.GetBytes(fourCC); 1356 | if (bytes.Length != 4) { 1357 | throw new Exception("Invalid FourCC length."); 1358 | } 1359 | _bw.Write(bytes); 1360 | } 1361 | 1362 | public string Path { 1363 | get { 1364 | return _path; 1365 | } 1366 | } 1367 | } 1368 | 1369 | class TimeCodeWriter { 1370 | string _path; 1371 | StreamWriter _sw; 1372 | 1373 | public TimeCodeWriter(string path) { 1374 | _path = path; 1375 | if (path != null) { 1376 | _sw = new StreamWriter(path, false, Encoding.ASCII); 1377 | _sw.WriteLine("# timecode format v2"); 1378 | } 1379 | } 1380 | 1381 | public void Write(uint timeStamp) { 1382 | if (_sw != null) { 1383 | _sw.WriteLine(timeStamp); 1384 | } 1385 | } 1386 | 1387 | public void Finish() { 1388 | if (_sw != null) { 1389 | _sw.Close(); 1390 | _sw = null; 1391 | } 1392 | } 1393 | 1394 | public string Path { 1395 | get { 1396 | return _path; 1397 | } 1398 | } 1399 | } 1400 | 1401 | public struct FractionUInt32 { 1402 | public uint N; 1403 | public uint D; 1404 | 1405 | public FractionUInt32(uint n, uint d) { 1406 | N = n; 1407 | D = d; 1408 | } 1409 | 1410 | public double ToDouble() { 1411 | return (double)N / (double)D; 1412 | } 1413 | 1414 | public void Reduce() { 1415 | uint gcd = GCD(N, D); 1416 | N /= gcd; 1417 | D /= gcd; 1418 | } 1419 | 1420 | private uint GCD(uint a, uint b) { 1421 | uint r; 1422 | 1423 | while (b != 0) { 1424 | r = a % b; 1425 | a = b; 1426 | b = r; 1427 | } 1428 | 1429 | return a; 1430 | } 1431 | 1432 | public override string ToString() { 1433 | return ToString(true); 1434 | } 1435 | 1436 | public string ToString(bool full) { 1437 | if (full) { 1438 | return ToDouble().ToString() + " (" + N.ToString() + "/" + D.ToString() + ")"; 1439 | } 1440 | else { 1441 | return ToDouble().ToString("0.####"); 1442 | } 1443 | } 1444 | } 1445 | } 1446 | -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Library/General.cs: -------------------------------------------------------------------------------- 1 | // **************************************************************************** 2 | // 3 | // FLV Extract 4 | // Copyright (C) 2006-2011 J.D. Purcell (moitah@yahoo.com) 5 | // 6 | // This program is free software; you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation; either version 2 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program; if not, write to the Free Software 18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | // 20 | // **************************************************************************** 21 | 22 | using System; 23 | using System.Reflection; 24 | 25 | namespace QiyiFLV2MP4_GUI 26 | { 27 | public static class General { 28 | public static string Version { 29 | get { 30 | Version ver = Assembly.GetExecutingAssembly().GetName().Version; 31 | return ver.Major + "." + ver.Minor + "." + ver.Revision; 32 | } 33 | } 34 | 35 | public static void CopyBytes(byte[] dst, int dstOffset, byte[] src) { 36 | Buffer.BlockCopy(src, 0, dst, dstOffset, src.Length); 37 | } 38 | } 39 | 40 | public static class BitHelper { 41 | public static int Read(ref ulong x, int length) { 42 | int r = (int)(x >> (64 - length)); 43 | x <<= length; 44 | return r; 45 | } 46 | 47 | public static int Read(byte[] bytes, ref int offset, int length) { 48 | int startByte = offset / 8; 49 | int endByte = (offset + length - 1) / 8; 50 | int skipBits = offset % 8; 51 | ulong bits = 0; 52 | for (int i = 0; i <= Math.Min(endByte - startByte, 7); i++) { 53 | bits |= (ulong)bytes[startByte + i] << (56 - (i * 8)); 54 | } 55 | if (skipBits != 0) Read(ref bits, skipBits); 56 | offset += length; 57 | return Read(ref bits, length); 58 | } 59 | 60 | public static void Write(ref ulong x, int length, int value) { 61 | ulong mask = 0xFFFFFFFFFFFFFFFF >> (64 - length); 62 | x = (x << length) | ((ulong)value & mask); 63 | } 64 | 65 | public static byte[] CopyBlock(byte[] bytes, int offset, int length) { 66 | int startByte = offset / 8; 67 | int endByte = (offset + length - 1) / 8; 68 | int shiftA = offset % 8; 69 | int shiftB = 8 - shiftA; 70 | byte[] dst = new byte[(length + 7) / 8]; 71 | if (shiftA == 0) { 72 | Buffer.BlockCopy(bytes, startByte, dst, 0, dst.Length); 73 | } 74 | else { 75 | int i; 76 | for (i = 0; i < endByte - startByte; i++) { 77 | dst[i] = (byte)((bytes[startByte + i] << shiftA) | (bytes[startByte + i + 1] >> shiftB)); 78 | } 79 | if (i < dst.Length) { 80 | dst[i] = (byte)(bytes[startByte + i] << shiftA); 81 | } 82 | } 83 | dst[dst.Length - 1] &= (byte)(0xFF << ((dst.Length * 8) - length)); 84 | return dst; 85 | } 86 | } 87 | 88 | public static class BitConverterBE { 89 | public static ulong ToUInt64(byte[] value, int startIndex) { 90 | return 91 | ((ulong)value[startIndex ] << 56) | 92 | ((ulong)value[startIndex + 1] << 48) | 93 | ((ulong)value[startIndex + 2] << 40) | 94 | ((ulong)value[startIndex + 3] << 32) | 95 | ((ulong)value[startIndex + 4] << 24) | 96 | ((ulong)value[startIndex + 5] << 16) | 97 | ((ulong)value[startIndex + 6] << 8) | 98 | ((ulong)value[startIndex + 7] ); 99 | } 100 | 101 | public static uint ToUInt32(byte[] value, int startIndex) { 102 | return 103 | ((uint)value[startIndex ] << 24) | 104 | ((uint)value[startIndex + 1] << 16) | 105 | ((uint)value[startIndex + 2] << 8) | 106 | ((uint)value[startIndex + 3] ); 107 | } 108 | 109 | public static ushort ToUInt16(byte[] value, int startIndex) { 110 | return (ushort)( 111 | (value[startIndex ] << 8) | 112 | (value[startIndex + 1] )); 113 | } 114 | 115 | public static byte[] GetBytes(ulong value) { 116 | byte[] buff = new byte[8]; 117 | buff[0] = (byte)(value >> 56); 118 | buff[1] = (byte)(value >> 48); 119 | buff[2] = (byte)(value >> 40); 120 | buff[3] = (byte)(value >> 32); 121 | buff[4] = (byte)(value >> 24); 122 | buff[5] = (byte)(value >> 16); 123 | buff[6] = (byte)(value >> 8); 124 | buff[7] = (byte)(value ); 125 | return buff; 126 | } 127 | 128 | public static byte[] GetBytes(uint value) { 129 | byte[] buff = new byte[4]; 130 | buff[0] = (byte)(value >> 24); 131 | buff[1] = (byte)(value >> 16); 132 | buff[2] = (byte)(value >> 8); 133 | buff[3] = (byte)(value ); 134 | return buff; 135 | } 136 | 137 | public static byte[] GetBytes(ushort value) { 138 | byte[] buff = new byte[2]; 139 | buff[0] = (byte)(value >> 8); 140 | buff[1] = (byte)(value ); 141 | return buff; 142 | } 143 | } 144 | 145 | public static class BitConverterLE { 146 | public static byte[] GetBytes(ulong value) { 147 | byte[] buff = new byte[8]; 148 | buff[0] = (byte)(value ); 149 | buff[1] = (byte)(value >> 8); 150 | buff[2] = (byte)(value >> 16); 151 | buff[3] = (byte)(value >> 24); 152 | buff[4] = (byte)(value >> 32); 153 | buff[5] = (byte)(value >> 40); 154 | buff[6] = (byte)(value >> 48); 155 | buff[7] = (byte)(value >> 56); 156 | return buff; 157 | } 158 | 159 | public static byte[] GetBytes(uint value) { 160 | byte[] buff = new byte[4]; 161 | buff[0] = (byte)(value ); 162 | buff[1] = (byte)(value >> 8); 163 | buff[2] = (byte)(value >> 16); 164 | buff[3] = (byte)(value >> 24); 165 | return buff; 166 | } 167 | 168 | public static byte[] GetBytes(ushort value) { 169 | byte[] buff = new byte[2]; 170 | buff[0] = (byte)(value ); 171 | buff[1] = (byte)(value >> 8); 172 | return buff; 173 | } 174 | } 175 | 176 | public static class OggCRC { 177 | static uint[] _lut = new uint[256]; 178 | 179 | static OggCRC() { 180 | for (uint i = 0; i < 256; i++) { 181 | uint x = i << 24; 182 | for (uint j = 0; j < 8; j++) { 183 | x = ((x & 0x80000000U) != 0) ? ((x << 1) ^ 0x04C11DB7) : (x << 1); 184 | } 185 | _lut[i] = x; 186 | } 187 | } 188 | 189 | public static uint Calculate(byte[] buff, int offset, int length) { 190 | uint crc = 0; 191 | for (int i = 0; i < length; i++) { 192 | crc = _lut[((crc >> 24) ^ buff[offset + i]) & 0xFF] ^ (crc << 8); 193 | } 194 | return crc; 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Library/MP4Box.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4 GUI/Library/MP4Box.exe -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Library/WAVFile.cs: -------------------------------------------------------------------------------- 1 | // **************************************************************************** 2 | // 3 | // FLV Extract 4 | // Copyright (C) 2006-2011 J.D. Purcell (moitah@yahoo.com) 5 | // 6 | // This program is free software; you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation; either version 2 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program; if not, write to the Free Software 18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | // 20 | // **************************************************************************** 21 | 22 | using System; 23 | using System.IO; 24 | 25 | namespace WAVTools { 26 | public class WAVWriter { 27 | BinaryWriter _bw; 28 | bool _canSeek; 29 | bool _wroteHeaders; 30 | int _bitsPerSample, _channelCount, _sampleRate, _blockAlign; 31 | long _finalSampleLen, _sampleLen; 32 | 33 | public WAVWriter(string path, int bitsPerSample, int channelCount, int sampleRate) : 34 | this(new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read), 35 | bitsPerSample, channelCount, sampleRate) 36 | { 37 | } 38 | 39 | public WAVWriter(Stream stream, int bitsPerSample, int channelCount, int sampleRate) { 40 | _bitsPerSample = bitsPerSample; 41 | _channelCount = channelCount; 42 | _sampleRate = sampleRate; 43 | _blockAlign = _channelCount * ((_bitsPerSample + 7) / 8); 44 | 45 | _bw = new BinaryWriter(stream); 46 | _canSeek = stream.CanSeek; 47 | } 48 | 49 | private void WriteHeaders() { 50 | const uint fccRIFF = 0x46464952; 51 | const uint fccWAVE = 0x45564157; 52 | const uint fccFormat = 0x20746D66; 53 | const uint fccData = 0x61746164; 54 | 55 | uint dataChunkSize = GetDataChunkSize(_finalSampleLen); 56 | 57 | _bw.Write(fccRIFF); 58 | _bw.Write((uint)(dataChunkSize + (dataChunkSize & 1) + 36)); 59 | _bw.Write(fccWAVE); 60 | 61 | _bw.Write(fccFormat); 62 | _bw.Write((uint)16); 63 | _bw.Write((ushort)1); 64 | _bw.Write((ushort)_channelCount); 65 | _bw.Write((uint)_sampleRate); 66 | _bw.Write((uint)(_sampleRate * _blockAlign)); 67 | _bw.Write((ushort)_blockAlign); 68 | _bw.Write((ushort)_bitsPerSample); 69 | 70 | _bw.Write(fccData); 71 | _bw.Write((uint)dataChunkSize); 72 | } 73 | 74 | private uint GetDataChunkSize(long sampleCount) { 75 | const long maxFileSize = 0x7FFFFFFEL; 76 | long dataSize = sampleCount * _blockAlign; 77 | if ((dataSize + 44) > maxFileSize) { 78 | dataSize = ((maxFileSize - 44) / _blockAlign) * _blockAlign; 79 | } 80 | return (uint)dataSize; 81 | } 82 | 83 | public void Close() { 84 | if (((_sampleLen * _blockAlign) & 1) == 1) { 85 | _bw.Write((byte)0); 86 | } 87 | 88 | try { 89 | if (_sampleLen != _finalSampleLen) { 90 | if (_canSeek) { 91 | uint dataChunkSize = GetDataChunkSize(_sampleLen); 92 | _bw.Seek(4, SeekOrigin.Begin); 93 | _bw.Write((uint)(dataChunkSize + (dataChunkSize & 1) + 36)); 94 | _bw.Seek(40, SeekOrigin.Begin); 95 | _bw.Write((uint)dataChunkSize); 96 | } 97 | else { 98 | throw new Exception("Samples written differs from the expected sample count."); 99 | } 100 | } 101 | } 102 | finally { 103 | _bw.Close(); 104 | _bw = null; 105 | } 106 | } 107 | 108 | public long Position { 109 | get { 110 | return _sampleLen; 111 | } 112 | } 113 | 114 | public long FinalSampleCount { 115 | set { 116 | _finalSampleLen = value; 117 | } 118 | } 119 | 120 | public void Write(byte[] buff, int sampleCount) { 121 | if (sampleCount <= 0) return; 122 | 123 | if (!_wroteHeaders) { 124 | WriteHeaders(); 125 | _wroteHeaders = true; 126 | } 127 | 128 | _bw.Write(buff, 0, sampleCount * _blockAlign); 129 | _sampleLen += sampleCount; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Library/js.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4 GUI/Library/js.dll -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Library/js32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4 GUI/Library/js32.dll -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Library/libgpac.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4 GUI/Library/libgpac.dll -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Main.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace QiyiFLV2MP4_GUI 2 | { 3 | partial class Main 4 | { 5 | /// 6 | /// 必需的设计器变量。 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// 清理所有正在使用的资源。 12 | /// 13 | /// 如果应释放托管资源,为 true;否则为 false。 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows 窗体设计器生成的代码 24 | 25 | /// 26 | /// 设计器支持所需的方法 - 不要修改 27 | /// 使用代码编辑器修改此方法的内容。 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.groupBox1 = new System.Windows.Forms.GroupBox(); 32 | this.groupBox2 = new System.Windows.Forms.GroupBox(); 33 | this.listBox1 = new System.Windows.Forms.ListBox(); 34 | this.button1 = new System.Windows.Forms.Button(); 35 | this.button2 = new System.Windows.Forms.Button(); 36 | this.button3 = new System.Windows.Forms.Button(); 37 | this.button4 = new System.Windows.Forms.Button(); 38 | this.label1 = new System.Windows.Forms.Label(); 39 | this.label2 = new System.Windows.Forms.Label(); 40 | this.label3 = new System.Windows.Forms.Label(); 41 | this.richTextBox1 = new System.Windows.Forms.RichTextBox(); 42 | this.groupBox1.SuspendLayout(); 43 | this.groupBox2.SuspendLayout(); 44 | this.SuspendLayout(); 45 | // 46 | // groupBox1 47 | // 48 | this.groupBox1.Controls.Add(this.button4); 49 | this.groupBox1.Controls.Add(this.button3); 50 | this.groupBox1.Controls.Add(this.button2); 51 | this.groupBox1.Controls.Add(this.button1); 52 | this.groupBox1.Controls.Add(this.listBox1); 53 | this.groupBox1.Location = new System.Drawing.Point(12, 12); 54 | this.groupBox1.Name = "groupBox1"; 55 | this.groupBox1.Size = new System.Drawing.Size(448, 144); 56 | this.groupBox1.TabIndex = 0; 57 | this.groupBox1.TabStop = false; 58 | this.groupBox1.Text = "操作"; 59 | // 60 | // groupBox2 61 | // 62 | this.groupBox2.Controls.Add(this.richTextBox1); 63 | this.groupBox2.Controls.Add(this.label3); 64 | this.groupBox2.Controls.Add(this.label2); 65 | this.groupBox2.Controls.Add(this.label1); 66 | this.groupBox2.Location = new System.Drawing.Point(12, 162); 67 | this.groupBox2.Name = "groupBox2"; 68 | this.groupBox2.Size = new System.Drawing.Size(448, 121); 69 | this.groupBox2.TabIndex = 1; 70 | this.groupBox2.TabStop = false; 71 | this.groupBox2.Text = "状态"; 72 | // 73 | // listBox1 74 | // 75 | this.listBox1.AllowDrop = true; 76 | this.listBox1.FormattingEnabled = true; 77 | this.listBox1.ItemHeight = 12; 78 | this.listBox1.Location = new System.Drawing.Point(6, 20); 79 | this.listBox1.Name = "listBox1"; 80 | this.listBox1.Size = new System.Drawing.Size(351, 112); 81 | this.listBox1.TabIndex = 0; 82 | this.listBox1.DragDrop += new System.Windows.Forms.DragEventHandler(this.listBox1_DragDrop); 83 | this.listBox1.DragEnter += new System.Windows.Forms.DragEventHandler(this.listBox1_DragEnter); 84 | this.listBox1.DragOver += new System.Windows.Forms.DragEventHandler(this.listBox1_DragOver); 85 | // 86 | // button1 87 | // 88 | this.button1.Location = new System.Drawing.Point(364, 21); 89 | this.button1.Name = "button1"; 90 | this.button1.Size = new System.Drawing.Size(75, 23); 91 | this.button1.TabIndex = 1; 92 | this.button1.Text = "添加文件"; 93 | this.button1.UseVisualStyleBackColor = true; 94 | this.button1.Click += new System.EventHandler(this.button1_Click); 95 | // 96 | // button2 97 | // 98 | this.button2.Location = new System.Drawing.Point(364, 50); 99 | this.button2.Name = "button2"; 100 | this.button2.Size = new System.Drawing.Size(75, 23); 101 | this.button2.TabIndex = 2; 102 | this.button2.Text = "删除文件"; 103 | this.button2.UseVisualStyleBackColor = true; 104 | this.button2.Click += new System.EventHandler(this.button2_Click); 105 | // 106 | // button3 107 | // 108 | this.button3.Location = new System.Drawing.Point(364, 79); 109 | this.button3.Name = "button3"; 110 | this.button3.Size = new System.Drawing.Size(75, 23); 111 | this.button3.TabIndex = 3; 112 | this.button3.Text = "清空列表"; 113 | this.button3.UseVisualStyleBackColor = true; 114 | this.button3.Click += new System.EventHandler(this.button3_Click); 115 | // 116 | // button4 117 | // 118 | this.button4.Enabled = false; 119 | this.button4.Location = new System.Drawing.Point(364, 108); 120 | this.button4.Name = "button4"; 121 | this.button4.Size = new System.Drawing.Size(75, 23); 122 | this.button4.TabIndex = 4; 123 | this.button4.Text = "开始封装"; 124 | this.button4.UseVisualStyleBackColor = true; 125 | this.button4.Click += new System.EventHandler(this.button4_Click); 126 | // 127 | // label1 128 | // 129 | this.label1.AutoSize = true; 130 | this.label1.Location = new System.Drawing.Point(6, 17); 131 | this.label1.Name = "label1"; 132 | this.label1.Size = new System.Drawing.Size(89, 12); 133 | this.label1.TabIndex = 0; 134 | this.label1.Text = "当前封装文件:"; 135 | // 136 | // label2 137 | // 138 | this.label2.AutoSize = true; 139 | this.label2.Location = new System.Drawing.Point(6, 31); 140 | this.label2.Name = "label2"; 141 | this.label2.Size = new System.Drawing.Size(65, 12); 142 | this.label2.TabIndex = 1; 143 | this.label2.Text = "调试输出:"; 144 | // 145 | // label3 146 | // 147 | this.label3.AutoSize = true; 148 | this.label3.Location = new System.Drawing.Point(90, 17); 149 | this.label3.Name = "label3"; 150 | this.label3.Size = new System.Drawing.Size(101, 12); 151 | this.label3.TabIndex = 2; 152 | this.label3.Text = "尚未执行封装操作"; 153 | // 154 | // richTextBox1 155 | // 156 | this.richTextBox1.Location = new System.Drawing.Point(8, 45); 157 | this.richTextBox1.Name = "richTextBox1"; 158 | this.richTextBox1.ReadOnly = true; 159 | this.richTextBox1.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.ForcedVertical; 160 | this.richTextBox1.Size = new System.Drawing.Size(431, 70); 161 | this.richTextBox1.TabIndex = 3; 162 | this.richTextBox1.Text = ""; 163 | this.richTextBox1.TextChanged += new System.EventHandler(this.richTextBox1_TextChanged); 164 | // 165 | // Main 166 | // 167 | this.AllowDrop = true; 168 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); 169 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 170 | this.ClientSize = new System.Drawing.Size(472, 290); 171 | this.Controls.Add(this.groupBox2); 172 | this.Controls.Add(this.groupBox1); 173 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 174 | this.MaximizeBox = false; 175 | this.Name = "Main"; 176 | this.ShowIcon = false; 177 | this.Text = "爱奇艺FLV重封装工具"; 178 | this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.Main_FormClosed); 179 | this.Load += new System.EventHandler(this.Main_Load); 180 | this.groupBox1.ResumeLayout(false); 181 | this.groupBox2.ResumeLayout(false); 182 | this.groupBox2.PerformLayout(); 183 | this.ResumeLayout(false); 184 | 185 | } 186 | 187 | #endregion 188 | 189 | private System.Windows.Forms.GroupBox groupBox1; 190 | private System.Windows.Forms.GroupBox groupBox2; 191 | private System.Windows.Forms.Button button4; 192 | private System.Windows.Forms.Button button3; 193 | private System.Windows.Forms.Button button2; 194 | private System.Windows.Forms.Button button1; 195 | private System.Windows.Forms.ListBox listBox1; 196 | private System.Windows.Forms.RichTextBox richTextBox1; 197 | private System.Windows.Forms.Label label3; 198 | private System.Windows.Forms.Label label2; 199 | private System.Windows.Forms.Label label1; 200 | } 201 | } 202 | 203 | -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Diagnostics; 6 | using System.Drawing; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using System.Windows.Forms; 12 | 13 | namespace QiyiFLV2MP4_GUI 14 | { 15 | public partial class Main : Form 16 | { 17 | public Main() 18 | { 19 | InitializeComponent(); 20 | } 21 | 22 | public int ConvertSuccess = 0; 23 | 24 | private void button1_Click(object sender, EventArgs e) 25 | { 26 | OpenFileDialog dlg = new OpenFileDialog(); 27 | dlg.Multiselect = true; 28 | dlg.Filter = "FLV文件|*.FLV"; 29 | if (dlg.ShowDialog() == DialogResult.OK) 30 | { 31 | foreach (string s in dlg.FileNames) 32 | { 33 | if (s.Contains("•")) 34 | { 35 | MessageBox.Show("文件路径中发现非法字符\"•\",请重命名 " + s + " 后重试!", "温馨提示"); 36 | listBox1.Items.Clear(); 37 | break; 38 | } 39 | listBox1.Items.Add(s); 40 | } 41 | } 42 | if (listBox1.Items.Count == 0) 43 | { 44 | button4.Enabled = false; 45 | } 46 | else 47 | { 48 | button4.Enabled = true; 49 | } 50 | } 51 | 52 | private void button2_Click(object sender, EventArgs e) 53 | { 54 | try 55 | { 56 | listBox1.Items.RemoveAt(listBox1.SelectedIndex); 57 | } 58 | catch 59 | { 60 | MessageBox.Show("请选择你要从列表中删除的文件!", "温馨提示"); 61 | } 62 | if (listBox1.Items.Count == 0) 63 | { 64 | button4.Enabled = false; 65 | } 66 | else 67 | { 68 | button4.Enabled = true; 69 | } 70 | } 71 | 72 | private void button3_Click(object sender, EventArgs e) 73 | { 74 | listBox1.Items.Clear(); 75 | button4.Enabled = false; 76 | } 77 | 78 | private void button4_Click(object sender, EventArgs e) 79 | { 80 | 81 | DialogResult dr = MessageBox.Show("封装完成后是否删除FLV文件?", "提示", MessageBoxButtons.OKCancel); 82 | if (dr == DialogResult.OK) 83 | { 84 | //用户选择确认的操作 85 | foreach (string s in listBox1.Items) 86 | { 87 | label3.Text = s; 88 | if (!convert(s,true)) 89 | { 90 | MessageBox.Show("转换失败,请尝试重试!", "提示"); 91 | } 92 | } 93 | } 94 | else if (dr == DialogResult.Cancel) 95 | { 96 | //用户选择取消的操作 97 | foreach (string s in listBox1.Items) 98 | { 99 | label3.Text = s; 100 | if (!convert(s,false)) 101 | { 102 | MessageBox.Show("转换失败,请尝试重试!", "提示"); 103 | } 104 | } 105 | } 106 | button4.Enabled = false; 107 | MessageBox.Show(ConvertSuccess.ToString() + "个FLV文件封装成功!" + (listBox1.Items.Count - ConvertSuccess).ToString() + "个FLV文件封装失败!列表已清空!"); 108 | label3.Text = "尚未执行封装操作"; 109 | listBox1.Items.Clear(); 110 | } 111 | 112 | static bool _autoOverwrite; 113 | private static StringBuilder sortOutput = null; 114 | public static string inputPath = ""; 115 | 116 | private bool convert(string args,bool delete) 117 | { 118 | if(File.Exists(args.Substring(0, args.Length - 4) + ".mp4")) 119 | { 120 | DialogResult dr = MessageBox.Show("发现重名文件 "+ args.Substring(0, args.Length - 4) + ".mp4 ,是否删除?", "提示", MessageBoxButtons.OKCancel); 121 | if (dr == DialogResult.OK) 122 | { 123 | File.Delete(args.Substring(0, args.Length - 4) + ".mp4"); 124 | } 125 | else if (dr == DialogResult.Cancel) 126 | { 127 | return false; 128 | } 129 | } 130 | bool extractVideo = false; 131 | bool extractAudio = false; 132 | bool extractTimeCodes = false; 133 | string outputDirectory = null; 134 | 135 | try 136 | { 137 | _autoOverwrite = true; 138 | extractVideo = true; 139 | extractAudio = true; 140 | inputPath = args; 141 | } 142 | catch 143 | { 144 | Console.WriteLine("Arguments: source_path"); 145 | Console.WriteLine(); 146 | return false; 147 | } 148 | 149 | richTextBox1.Text += "正在抽取视音频数据!\r\n"; 150 | try 151 | { 152 | using (FLVFile flvFile = new FLVFile(Path.GetFullPath(inputPath))) 153 | { 154 | if (outputDirectory != null) 155 | { 156 | flvFile.OutputDirectory = Path.GetFullPath(outputDirectory); 157 | } 158 | flvFile.ExtractStreams(extractAudio, extractVideo, extractTimeCodes, PromptOverwrite); 159 | if ((flvFile.TrueFrameRate != null) || (flvFile.AverageFrameRate != null)) 160 | { 161 | if (flvFile.TrueFrameRate != null) 162 | { 163 | richTextBox1.Text += "True Frame Rate: " + flvFile.TrueFrameRate.ToString() + "\r\n"; 164 | } 165 | if (flvFile.AverageFrameRate != null) 166 | { 167 | richTextBox1.Text += "Average Frame Rate: " + flvFile.AverageFrameRate.ToString() + "\r\n"; 168 | } 169 | } 170 | if (flvFile.Warnings.Length != 0) 171 | { 172 | foreach (string warning in flvFile.Warnings) 173 | { 174 | richTextBox1.Text += "Warning: " + warning +"\r\n"; 175 | } 176 | } 177 | } 178 | } 179 | catch (Exception ex) 180 | { 181 | richTextBox1.Text += "Error: " + ex.Message + "\r\n"; 182 | return false; 183 | } 184 | 185 | if (!File.Exists(inputPath.Substring(0, inputPath.Length - 4) + ".aac")) 186 | { 187 | MessageBox.Show("数据文件异常,请检查FLV文件后尝试!", "温馨提示"); 188 | return false; 189 | } 190 | if (!File.Exists(inputPath.Substring(0, inputPath.Length - 4) + ".264")) 191 | { 192 | MessageBox.Show("数据文件异常,请检查FLV文件后尝试!", "温馨提示"); 193 | return false; 194 | } 195 | 196 | Process sortProcess; 197 | sortProcess = new Process(); 198 | sortOutput = new StringBuilder(""); 199 | sortProcess.StartInfo.FileName = "cmd.exe"; 200 | sortProcess.StartInfo.UseShellExecute = false;// 必须禁用操作系统外壳程序 201 | sortProcess.StartInfo.RedirectStandardOutput = true; 202 | sortProcess.StartInfo.RedirectStandardError = true; //重定向错误输出 203 | sortProcess.StartInfo.CreateNoWindow = true; //设置不显示窗口 204 | sortProcess.StartInfo.RedirectStandardInput = true; 205 | sortProcess.StartInfo.Arguments = "/c mp4box.exe -add \"" + inputPath.Substring(0, inputPath.Length - 4) + ".264#trackID=1:par=1:1:name=\" -add \"" + inputPath.Substring(0, inputPath.Length - 4) + ".aac:name=\" -new \"" + inputPath.Substring(0, inputPath.Length - 4) + ".mp4\""; //设定程式执行参数 206 | sortProcess.Start(); 207 | richTextBox1.Text += "封装为MP4中!请耐心等待!\r\n"; 208 | sortProcess.BeginOutputReadLine();// 异步获取命令行内容 209 | sortProcess.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler); // 为异步获取订阅事件 210 | sortProcess.WaitForExit();//等待程序执行完退出进程 211 | sortProcess.Close(); 212 | if (File.Exists(inputPath.Substring(0, inputPath.Length - 4) + ".aac")) 213 | File.Delete(inputPath.Substring(0, inputPath.Length - 4) + ".aac"); 214 | if (File.Exists(inputPath.Substring(0, inputPath.Length - 4) + ".264")) 215 | File.Delete(inputPath.Substring(0, inputPath.Length - 4) + ".264"); 216 | if(!richTextBox1.Text.Contains("ISO File Writing")) 217 | { 218 | MessageBox.Show("文件路径中可能存在非法字符,请重命名 " + args + " 后重试!", "温馨提示"); 219 | return false; 220 | } 221 | if (delete) 222 | File.Delete(inputPath); 223 | richTextBox1.Text = ""; 224 | ConvertSuccess++; 225 | return true; 226 | } 227 | 228 | private void SortOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) 229 | { 230 | if (!String.IsNullOrEmpty(outLine.Data)) 231 | { 232 | this.richTextBox1.Text += outLine.Data + Environment.NewLine; 233 | } 234 | } 235 | 236 | private static bool PromptOverwrite(string path) 237 | { 238 | if (_autoOverwrite) return true; 239 | bool? overwrite = null; 240 | Console.Write("Output file \"" + Path.GetFileName(path) + "\" already exists, overwrite? (y/n): "); 241 | while (overwrite == null) 242 | { 243 | char c = Console.ReadKey(true).KeyChar; 244 | if (c == 'y') overwrite = true; 245 | if (c == 'n') overwrite = false; 246 | } 247 | Console.WriteLine(overwrite.Value ? "y" : "n"); 248 | Console.WriteLine(); 249 | return overwrite.Value; 250 | } 251 | 252 | private void Main_Load(object sender, EventArgs e) 253 | { 254 | Control.CheckForIllegalCrossThreadCalls = false; 255 | if (!File.Exists("js.dll")) 256 | { 257 | byte[] buffer = Properties.Resources.js; 258 | string path = AppDomain.CurrentDomain.BaseDirectory + "js.dll"; 259 | FileStream FS = new FileStream(path, FileMode.Create); 260 | BinaryWriter BWriter = new BinaryWriter(FS); 261 | BWriter.Write(buffer, 0, buffer.Length); 262 | BWriter.Close(); 263 | } 264 | 265 | if (!File.Exists("js32.dll")) 266 | { 267 | byte[] buffer = Properties.Resources.js32; 268 | string path = AppDomain.CurrentDomain.BaseDirectory + "js32.dll"; 269 | FileStream FS = new FileStream(path, FileMode.Create); 270 | BinaryWriter BWriter = new BinaryWriter(FS); 271 | BWriter.Write(buffer, 0, buffer.Length); 272 | BWriter.Close(); 273 | } 274 | 275 | if (!File.Exists("libgpac.dll")) 276 | { 277 | byte[] buffer = Properties.Resources.libgpac; 278 | string path = AppDomain.CurrentDomain.BaseDirectory + "libgpac.dll"; 279 | FileStream FS = new FileStream(path, FileMode.Create); 280 | BinaryWriter BWriter = new BinaryWriter(FS); 281 | BWriter.Write(buffer, 0, buffer.Length); 282 | BWriter.Close(); 283 | } 284 | 285 | if (!File.Exists("MP4Box.exe")) 286 | { 287 | byte[] buffer = Properties.Resources.MP4Box; 288 | string path = AppDomain.CurrentDomain.BaseDirectory + "MP4Box.exe"; 289 | FileStream FS = new FileStream(path, FileMode.Create); 290 | BinaryWriter BWriter = new BinaryWriter(FS); 291 | BWriter.Write(buffer, 0, buffer.Length); 292 | BWriter.Close(); 293 | } 294 | } 295 | 296 | private void listBox1_DragDrop(object sender, DragEventArgs e) 297 | { 298 | if (e.Data.GetDataPresent(DataFormats.FileDrop, false)) 299 | { 300 | String[] files = (String[])e.Data.GetData(DataFormats.FileDrop); 301 | foreach (String s in files) 302 | { 303 | if(s.Substring(s.Length - 4, 4) != ".flv" && s.Substring(s.Length - 4, 4) != ".FLV") 304 | { 305 | MessageBox.Show("您拖入的文件 " + s + " 并不是FLV文件,请检查!", "温馨提示"); 306 | } 307 | else 308 | { 309 | if (s.Contains("•")) 310 | { 311 | MessageBox.Show("文件路径中发现非法字符\"•\",请重命名 " + s + " 后重试!", "温馨提示"); 312 | listBox1.Items.Clear(); 313 | break; 314 | } 315 | (sender as ListBox).Items.Add(s); 316 | } 317 | } 318 | } 319 | if (listBox1.Items.Count == 0) 320 | { 321 | button4.Enabled = false; 322 | } 323 | else 324 | { 325 | button4.Enabled = true; 326 | } 327 | } 328 | 329 | private void listBox1_DragEnter(object sender, DragEventArgs e) 330 | { 331 | e.Effect = DragDropEffects.All; 332 | } 333 | 334 | private void listBox1_DragOver(object sender, DragEventArgs e) 335 | { 336 | e.Effect = DragDropEffects.All; 337 | } 338 | 339 | private void richTextBox1_TextChanged(object sender, EventArgs e) 340 | { 341 | richTextBox1.SelectionStart = richTextBox1.Text.Length; 342 | richTextBox1.ScrollToCaret(); 343 | } 344 | 345 | private void Main_FormClosed(object sender, FormClosedEventArgs e) 346 | { 347 | File.Delete("js.dll"); 348 | File.Delete("js32.dll"); 349 | File.Delete("libgpac.dll"); 350 | File.Delete("MP4Box.exe"); 351 | } 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Main.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Windows.Forms; 6 | 7 | namespace QiyiFLV2MP4_GUI 8 | { 9 | static class Program 10 | { 11 | /// 12 | /// 应用程序的主入口点。 13 | /// 14 | [STAThread] 15 | static void Main() 16 | { 17 | Application.EnableVisualStyles(); 18 | Application.SetCompatibleTextRenderingDefault(false); 19 | Application.Run(new Main()); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("爱奇艺FLV重封装工具")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("爱奇艺FLV重封装工具")] 13 | [assembly: AssemblyCopyright("Copyright © 风漠兮 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | //将 ComVisible 设置为 false 将使此程序集中的类型 18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("aa4c7964-3baa-4eab-830b-f35194ecba88")] 24 | 25 | // 程序集的版本信息由下列四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: : 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.1.0")] 36 | [assembly: AssemblyFileVersion("1.0.1.0")] 37 | -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace QiyiFLV2MP4_GUI.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// 一个强类型的资源类,用于查找本地化的字符串等。 17 | /// 18 | // 此类是由 StronglyTypedResourceBuilder 19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen 21 | // (以 /str 作为命令选项),或重新生成 VS 项目。 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// 返回此类使用的缓存的 ResourceManager 实例。 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("QiyiFLV2MP4_GUI.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// 使用此强类型资源类,为所有资源查找 51 | /// 重写当前线程的 CurrentUICulture 属性。 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// 查找 System.Byte[] 类型的本地化资源。 65 | /// 66 | internal static byte[] js { 67 | get { 68 | object obj = ResourceManager.GetObject("js", resourceCulture); 69 | return ((byte[])(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// 查找 System.Byte[] 类型的本地化资源。 75 | /// 76 | internal static byte[] js32 { 77 | get { 78 | object obj = ResourceManager.GetObject("js32", resourceCulture); 79 | return ((byte[])(obj)); 80 | } 81 | } 82 | 83 | /// 84 | /// 查找 System.Byte[] 类型的本地化资源。 85 | /// 86 | internal static byte[] libgpac { 87 | get { 88 | object obj = ResourceManager.GetObject("libgpac", resourceCulture); 89 | return ((byte[])(obj)); 90 | } 91 | } 92 | 93 | /// 94 | /// 查找 System.Byte[] 类型的本地化资源。 95 | /// 96 | internal static byte[] MP4Box { 97 | get { 98 | object obj = ResourceManager.GetObject("MP4Box", resourceCulture); 99 | return ((byte[])(obj)); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Library\js.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 123 | 124 | 125 | ..\Library\js32.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 126 | 127 | 128 | ..\Library\libgpac.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 129 | 130 | 131 | ..\Library\MP4Box.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 132 | 133 | -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace QiyiFLV2MP4_GUI.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /QiyiFLV2MP4 GUI/QiyiFLV2MP4 GUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AA4C7964-3BAA-4EAB-830B-F35194ECBA88} 8 | WinExe 9 | Properties 10 | QiyiFLV2MP4_GUI 11 | QiyiFLV2MP4 GUI 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Form 54 | 55 | 56 | Main.cs 57 | 58 | 59 | 60 | 61 | Main.cs 62 | 63 | 64 | ResXFileCodeGenerator 65 | Resources.Designer.cs 66 | Designer 67 | 68 | 69 | True 70 | Resources.resx 71 | True 72 | 73 | 74 | SettingsSingleFileGenerator 75 | Settings.Designer.cs 76 | 77 | 78 | True 79 | Settings.settings 80 | True 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 106 | -------------------------------------------------------------------------------- /QiyiFLV2MP4.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QiyiFLV2MP4", "QiyiFLV2MP4\QiyiFLV2MP4.csproj", "{A964CB68-E117-4CB7-B526-CD6076AAE07E}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QiyiFLV2MP4 GUI", "QiyiFLV2MP4 GUI\QiyiFLV2MP4 GUI.csproj", "{AA4C7964-3BAA-4EAB-830B-F35194ECBA88}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {A964CB68-E117-4CB7-B526-CD6076AAE07E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {A964CB68-E117-4CB7-B526-CD6076AAE07E}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {A964CB68-E117-4CB7-B526-CD6076AAE07E}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {A964CB68-E117-4CB7-B526-CD6076AAE07E}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {AA4C7964-3BAA-4EAB-830B-F35194ECBA88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {AA4C7964-3BAA-4EAB-830B-F35194ECBA88}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {AA4C7964-3BAA-4EAB-830B-F35194ECBA88}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {AA4C7964-3BAA-4EAB-830B-F35194ECBA88}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /QiyiFLV2MP4/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /QiyiFLV2MP4/Library/FLVFile.cs: -------------------------------------------------------------------------------- 1 | // **************************************************************************** 2 | // 3 | // FLV Extract 4 | // Copyright (C) 2006-2011 J.D. Purcell (moitah@yahoo.com) 5 | // 6 | // This program is free software; you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation; either version 2 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program; if not, write to the Free Software 18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | // 20 | // **************************************************************************** 21 | 22 | using System; 23 | using System.Collections.Generic; 24 | using System.IO; 25 | using System.Text; 26 | 27 | namespace QiyiFLV2MP4 28 | { 29 | interface IAudioWriter { 30 | void WriteChunk(byte[] chunk, uint timeStamp); 31 | void Finish(); 32 | string Path { get; } 33 | } 34 | 35 | interface IVideoWriter { 36 | void WriteChunk(byte[] chunk, uint timeStamp, int frameType); 37 | void Finish(FractionUInt32 averageFrameRate); 38 | string Path { get; } 39 | } 40 | 41 | public delegate bool OverwriteDelegate(string destPath); 42 | 43 | public class FLVFile : IDisposable { 44 | static readonly string[] _outputExtensions = new string[] { ".avi", ".mp3", ".264", ".aac", ".spx", ".txt" }; 45 | 46 | string _inputPath, _outputDirectory, _outputPathBase; 47 | OverwriteDelegate _overwrite; 48 | FileStream _fs; 49 | long _fileOffset, _fileLength; 50 | IAudioWriter _audioWriter; 51 | IVideoWriter _videoWriter; 52 | TimeCodeWriter _timeCodeWriter; 53 | List _videoTimeStamps; 54 | bool _extractAudio, _extractVideo, _extractTimeCodes; 55 | bool _extractedAudio, _extractedVideo, _extractedTimeCodes; 56 | FractionUInt32? _averageFrameRate, _trueFrameRate; 57 | List _warnings; 58 | 59 | public FLVFile(string path) { 60 | _inputPath = path; 61 | _outputDirectory = Path.GetDirectoryName(path); 62 | _warnings = new List(); 63 | _fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 65536); 64 | _fileOffset = 0; 65 | _fileLength = _fs.Length; 66 | } 67 | 68 | public void Dispose() { 69 | if (_fs != null) { 70 | _fs.Close(); 71 | _fs = null; 72 | } 73 | CloseOutput(null, true); 74 | } 75 | 76 | public void Close() { 77 | Dispose(); 78 | } 79 | 80 | public string OutputDirectory { 81 | get { return _outputDirectory; } 82 | set { _outputDirectory = value; } 83 | } 84 | 85 | public FractionUInt32? AverageFrameRate { 86 | get { return _averageFrameRate; } 87 | } 88 | 89 | public FractionUInt32? TrueFrameRate { 90 | get { return _trueFrameRate; } 91 | } 92 | 93 | public string[] Warnings { 94 | get { return _warnings.ToArray(); } 95 | } 96 | 97 | public bool ExtractedAudio { 98 | get { return _extractedAudio; } 99 | } 100 | 101 | public bool ExtractedVideo { 102 | get { return _extractedVideo; } 103 | } 104 | 105 | public bool ExtractedTimeCodes { 106 | get { return _extractedTimeCodes; } 107 | } 108 | 109 | public void ExtractStreams(bool extractAudio, bool extractVideo, bool extractTimeCodes, OverwriteDelegate overwrite) { 110 | uint dataOffset, flags, prevTagSize; 111 | 112 | _outputPathBase = Path.Combine(_outputDirectory, Path.GetFileNameWithoutExtension(_inputPath)); 113 | _overwrite = overwrite; 114 | _extractAudio = extractAudio; 115 | _extractVideo = extractVideo; 116 | _extractTimeCodes = extractTimeCodes; 117 | _videoTimeStamps = new List(); 118 | 119 | Seek(0); 120 | if (_fileLength < 4 || ReadUInt32() != 0x464C5601) { 121 | if (_fileLength >= 8 && ReadUInt32() == 0x66747970) { 122 | throw new Exception("This is a MP4 file. Please input a FLV file!"); 123 | } 124 | else { 125 | throw new Exception("This isn't a FLV file. Please input a FLV file!"); 126 | } 127 | } 128 | 129 | if (Array.IndexOf(_outputExtensions, Path.GetExtension(_inputPath).ToLowerInvariant()) != -1) { 130 | // Can't have the same extension as files we output 131 | throw new Exception("Please change the extension of this FLV file."); 132 | } 133 | 134 | if (!Directory.Exists(_outputDirectory)) { 135 | throw new Exception("Output directory doesn't exist."); 136 | } 137 | 138 | flags = ReadUInt8(); 139 | dataOffset = ReadUInt32(); 140 | 141 | Seek(dataOffset); 142 | 143 | prevTagSize = ReadUInt32(); 144 | while (_fileOffset < _fileLength) { 145 | if (!ReadTag()) break; 146 | if ((_fileLength - _fileOffset) < 4) break; 147 | prevTagSize = ReadUInt32(); 148 | } 149 | 150 | _averageFrameRate = CalculateAverageFrameRate(); 151 | _trueFrameRate = CalculateTrueFrameRate(); 152 | 153 | CloseOutput(_averageFrameRate, false); 154 | } 155 | 156 | private void CloseOutput(FractionUInt32? averageFrameRate, bool disposing) { 157 | if (_videoWriter != null) { 158 | _videoWriter.Finish(averageFrameRate ?? new FractionUInt32(25, 1)); 159 | if (disposing && (_videoWriter.Path != null)) { 160 | try { File.Delete(_videoWriter.Path); } 161 | catch { } 162 | } 163 | _videoWriter = null; 164 | } 165 | if (_audioWriter != null) { 166 | _audioWriter.Finish(); 167 | if (disposing && (_audioWriter.Path != null)) { 168 | try { File.Delete(_audioWriter.Path); } 169 | catch { } 170 | } 171 | _audioWriter = null; 172 | } 173 | if (_timeCodeWriter != null) { 174 | _timeCodeWriter.Finish(); 175 | if (disposing && (_timeCodeWriter.Path != null)) { 176 | try { File.Delete(_timeCodeWriter.Path); } 177 | catch { } 178 | } 179 | _timeCodeWriter = null; 180 | } 181 | } 182 | 183 | private bool ReadTag() { 184 | uint tagType, dataSize, timeStamp, streamID, mediaInfo; 185 | byte[] data; 186 | 187 | if ((_fileLength - _fileOffset) < 11) { 188 | return false; 189 | } 190 | 191 | // Read tag header 192 | tagType = ReadUInt8(); 193 | dataSize = ReadUInt24(); 194 | timeStamp = ReadUInt24(); 195 | timeStamp |= ReadUInt8() << 24; 196 | streamID = ReadUInt24(); 197 | 198 | // Read tag data 199 | if (dataSize == 0) { 200 | return true; 201 | } 202 | if ((_fileLength - _fileOffset) < dataSize) { 203 | return false; 204 | } 205 | mediaInfo = ReadUInt8(); 206 | dataSize -= 1; 207 | data = ReadBytes((int)dataSize); 208 | 209 | if (tagType == 0x8) { // Audio 210 | if (_audioWriter == null) { 211 | _audioWriter = _extractAudio ? GetAudioWriter(mediaInfo) : new DummyAudioWriter(); 212 | _extractedAudio = !(_audioWriter is DummyAudioWriter); 213 | } 214 | _audioWriter.WriteChunk(data, timeStamp); 215 | } 216 | else if ((tagType == 0x9) && ((mediaInfo >> 4) != 5)) { // Video 217 | if (_videoWriter == null) { 218 | _videoWriter = _extractVideo ? GetVideoWriter(mediaInfo) : new DummyVideoWriter(); 219 | _extractedVideo = !(_videoWriter is DummyVideoWriter); 220 | } 221 | if (_timeCodeWriter == null) { 222 | string path = _outputPathBase + ".txt"; 223 | _timeCodeWriter = new TimeCodeWriter((_extractTimeCodes && CanWriteTo(path)) ? path : null); 224 | _extractedTimeCodes = _extractTimeCodes; 225 | } 226 | _videoTimeStamps.Add(timeStamp); 227 | _videoWriter.WriteChunk(data, timeStamp, (int)((mediaInfo & 0xF0) >> 4)); 228 | _timeCodeWriter.Write(timeStamp); 229 | } 230 | 231 | return true; 232 | } 233 | 234 | private IAudioWriter GetAudioWriter(uint mediaInfo) { 235 | uint format = mediaInfo >> 4; 236 | uint rate = (mediaInfo >> 2) & 0x3; 237 | uint bits = (mediaInfo >> 1) & 0x1; 238 | uint chans = mediaInfo & 0x1; 239 | string path; 240 | 241 | if ((format == 2) || (format == 14)) { // MP3 242 | path = _outputPathBase + ".mp3"; 243 | if (!CanWriteTo(path)) return new DummyAudioWriter(); 244 | return new MP3Writer(path, _warnings); 245 | } 246 | else if ((format == 0) || (format == 3)) { // PCM 247 | int sampleRate = 0; 248 | switch (rate) { 249 | case 0: sampleRate = 5512; break; 250 | case 1: sampleRate = 11025; break; 251 | case 2: sampleRate = 22050; break; 252 | case 3: sampleRate = 44100; break; 253 | } 254 | path = _outputPathBase + ".wav"; 255 | if (!CanWriteTo(path)) return new DummyAudioWriter(); 256 | if (format == 0) { 257 | _warnings.Add("PCM byte order unspecified, assuming little endian."); 258 | } 259 | return new WAVWriter(path, (bits == 1) ? 16 : 8, 260 | (chans == 1) ? 2 : 1, sampleRate); 261 | } 262 | else if (format == 10) { // AAC 263 | path = _outputPathBase + ".aac"; 264 | if (!CanWriteTo(path)) return new DummyAudioWriter(); 265 | return new AACWriter(path); 266 | } 267 | else if (format == 11) { // Speex 268 | path = _outputPathBase + ".spx"; 269 | if (!CanWriteTo(path)) return new DummyAudioWriter(); 270 | return new SpeexWriter(path, (int)(_fileLength & 0xFFFFFFFF)); 271 | } 272 | else { 273 | string typeStr; 274 | 275 | if (format == 1) 276 | typeStr = "ADPCM"; 277 | else if ((format == 4) || (format == 5) || (format == 6)) 278 | typeStr = "Nellymoser"; 279 | else 280 | typeStr = "format=" + format.ToString(); 281 | 282 | _warnings.Add("Unable to extract audio (" + typeStr + " is unsupported)."); 283 | 284 | return new DummyAudioWriter(); 285 | } 286 | } 287 | 288 | private IVideoWriter GetVideoWriter(uint mediaInfo) { 289 | uint codecID = mediaInfo & 0x0F; 290 | string path; 291 | 292 | if ((codecID == 2) || (codecID == 4) || (codecID == 5)) { 293 | path = _outputPathBase + ".avi"; 294 | if (!CanWriteTo(path)) return new DummyVideoWriter(); 295 | return new AVIWriter(path, (int)codecID, _warnings); 296 | } 297 | else if (codecID == 7) { 298 | path = _outputPathBase + ".264"; 299 | if (!CanWriteTo(path)) return new DummyVideoWriter(); 300 | return new RawH264Writer(path); 301 | } 302 | else { 303 | string typeStr; 304 | 305 | if (codecID == 3) 306 | typeStr = "Screen"; 307 | else if (codecID == 6) 308 | typeStr = "Screen2"; 309 | else 310 | typeStr = "codecID=" + codecID.ToString(); 311 | 312 | _warnings.Add("Unable to extract video (" + typeStr + " is unsupported)."); 313 | 314 | return new DummyVideoWriter(); 315 | } 316 | } 317 | 318 | private bool CanWriteTo(string path) { 319 | if (File.Exists(path) && (_overwrite != null)) { 320 | return _overwrite(path); 321 | } 322 | return true; 323 | } 324 | 325 | private FractionUInt32? CalculateAverageFrameRate() { 326 | FractionUInt32 frameRate; 327 | int frameCount = _videoTimeStamps.Count; 328 | 329 | if (frameCount > 1) { 330 | frameRate.N = (uint)(frameCount - 1) * 1000; 331 | frameRate.D = _videoTimeStamps[frameCount - 1] - _videoTimeStamps[0]; 332 | frameRate.Reduce(); 333 | return frameRate; 334 | } 335 | else { 336 | return null; 337 | } 338 | } 339 | 340 | private FractionUInt32? CalculateTrueFrameRate() { 341 | FractionUInt32 frameRate; 342 | Dictionary deltaCount = new Dictionary(); 343 | int i, threshold; 344 | uint delta, count, minDelta; 345 | 346 | // Calculate the distance between the timestamps, count how many times each delta appears 347 | for (i = 1; i < _videoTimeStamps.Count; i++) { 348 | int deltaS = (int)((long)_videoTimeStamps[i] - (long)_videoTimeStamps[i - 1]); 349 | 350 | if (deltaS <= 0) continue; 351 | delta = (uint)deltaS; 352 | 353 | if (deltaCount.ContainsKey(delta)) { 354 | deltaCount[delta] += 1; 355 | } 356 | else { 357 | deltaCount.Add(delta, 1); 358 | } 359 | } 360 | 361 | threshold = _videoTimeStamps.Count / 10; 362 | minDelta = UInt32.MaxValue; 363 | 364 | // Find the smallest delta that made up at least 10% of the frames (grouping in delta+1 365 | // because of rounding, e.g. a NTSC video will have deltas of 33 and 34 ms) 366 | foreach (KeyValuePair deltaItem in deltaCount) { 367 | delta = deltaItem.Key; 368 | count = deltaItem.Value; 369 | 370 | if (deltaCount.ContainsKey(delta + 1)) { 371 | count += deltaCount[delta + 1]; 372 | } 373 | 374 | if ((count >= threshold) && (delta < minDelta)) { 375 | minDelta = delta; 376 | } 377 | } 378 | 379 | // Calculate the frame rate based on the smallest delta, and delta+1 if present 380 | if (minDelta != UInt32.MaxValue) { 381 | uint totalTime, totalFrames; 382 | 383 | count = deltaCount[minDelta]; 384 | totalTime = minDelta * count; 385 | totalFrames = count; 386 | 387 | if (deltaCount.ContainsKey(minDelta + 1)) { 388 | count = deltaCount[minDelta + 1]; 389 | totalTime += (minDelta + 1) * count; 390 | totalFrames += count; 391 | } 392 | 393 | if (totalTime != 0) { 394 | frameRate.N = totalFrames * 1000; 395 | frameRate.D = totalTime; 396 | frameRate.Reduce(); 397 | return frameRate; 398 | } 399 | } 400 | 401 | // Unable to calculate frame rate 402 | return null; 403 | } 404 | 405 | private void Seek(long offset) { 406 | _fs.Seek(offset, SeekOrigin.Begin); 407 | _fileOffset = offset; 408 | } 409 | 410 | private uint ReadUInt8() { 411 | _fileOffset += 1; 412 | return (uint)_fs.ReadByte(); 413 | } 414 | 415 | private uint ReadUInt24() { 416 | byte[] x = new byte[4]; 417 | _fs.Read(x, 1, 3); 418 | _fileOffset += 3; 419 | return BitConverterBE.ToUInt32(x, 0); 420 | } 421 | 422 | private uint ReadUInt32() { 423 | byte[] x = new byte[4]; 424 | _fs.Read(x, 0, 4); 425 | _fileOffset += 4; 426 | return BitConverterBE.ToUInt32(x, 0); 427 | } 428 | 429 | private byte[] ReadBytes(int length) { 430 | byte[] buff = new byte[length]; 431 | _fs.Read(buff, 0, length); 432 | _fileOffset += length; 433 | return buff; 434 | } 435 | } 436 | 437 | class DummyAudioWriter : IAudioWriter { 438 | public DummyAudioWriter() { 439 | } 440 | 441 | public void WriteChunk(byte[] chunk, uint timeStamp) { 442 | } 443 | 444 | public void Finish() { 445 | } 446 | 447 | public string Path { 448 | get { 449 | return null; 450 | } 451 | } 452 | } 453 | 454 | class DummyVideoWriter : IVideoWriter { 455 | public DummyVideoWriter() { 456 | } 457 | 458 | public void WriteChunk(byte[] chunk, uint timeStamp, int frameType) { 459 | } 460 | 461 | public void Finish(FractionUInt32 averageFrameRate) { 462 | } 463 | 464 | public string Path { 465 | get { 466 | return null; 467 | } 468 | } 469 | } 470 | 471 | class MP3Writer : IAudioWriter { 472 | string _path; 473 | FileStream _fs; 474 | List _warnings; 475 | List _chunkBuffer; 476 | List _frameOffsets; 477 | uint _totalFrameLength; 478 | bool _isVBR; 479 | bool _delayWrite; 480 | bool _hasVBRHeader; 481 | bool _writeVBRHeader; 482 | int _firstBitRate; 483 | int _mpegVersion; 484 | int _sampleRate; 485 | int _channelMode; 486 | uint _firstFrameHeader; 487 | 488 | public MP3Writer(string path, List warnings) { 489 | _path = path; 490 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536); 491 | _warnings = warnings; 492 | _chunkBuffer = new List(); 493 | _frameOffsets = new List(); 494 | _delayWrite = true; 495 | } 496 | 497 | public void WriteChunk(byte[] chunk, uint timeStamp) { 498 | _chunkBuffer.Add(chunk); 499 | ParseMP3Frames(chunk); 500 | if (_delayWrite && _totalFrameLength >= 65536) { 501 | _delayWrite = false; 502 | } 503 | if (!_delayWrite) { 504 | Flush(); 505 | } 506 | } 507 | 508 | public void Finish() { 509 | Flush(); 510 | if (_writeVBRHeader) { 511 | _fs.Seek(0, SeekOrigin.Begin); 512 | WriteVBRHeader(false); 513 | } 514 | _fs.Close(); 515 | } 516 | 517 | public string Path { 518 | get { 519 | return _path; 520 | } 521 | } 522 | 523 | private void Flush() { 524 | foreach (byte[] chunk in _chunkBuffer) { 525 | _fs.Write(chunk, 0, chunk.Length); 526 | } 527 | _chunkBuffer.Clear(); 528 | } 529 | 530 | private void ParseMP3Frames(byte[] buff) { 531 | int[] MPEG1BitRate = new int[] { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }; 532 | int[] MPEG2XBitRate = new int[] { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }; 533 | int[] MPEG1SampleRate = new int[] { 44100, 48000, 32000, 0 }; 534 | int[] MPEG20SampleRate = new int[] { 22050, 24000, 16000, 0 }; 535 | int[] MPEG25SampleRate = new int[] { 11025, 12000, 8000, 0 }; 536 | 537 | int offset = 0; 538 | int length = buff.Length; 539 | 540 | while (length >= 4) { 541 | ulong header; 542 | int mpegVersion, layer, bitRate, sampleRate, padding, channelMode; 543 | int frameLen; 544 | 545 | header = (ulong)BitConverterBE.ToUInt32(buff, offset) << 32; 546 | if (BitHelper.Read(ref header, 11) != 0x7FF) { 547 | break; 548 | } 549 | mpegVersion = BitHelper.Read(ref header, 2); 550 | layer = BitHelper.Read(ref header, 2); 551 | BitHelper.Read(ref header, 1); 552 | bitRate = BitHelper.Read(ref header, 4); 553 | sampleRate = BitHelper.Read(ref header, 2); 554 | padding = BitHelper.Read(ref header, 1); 555 | BitHelper.Read(ref header, 1); 556 | channelMode = BitHelper.Read(ref header, 2); 557 | 558 | if ((mpegVersion == 1) || (layer != 1) || (bitRate == 0) || (bitRate == 15) || (sampleRate == 3)) { 559 | break; 560 | } 561 | 562 | bitRate = ((mpegVersion == 3) ? MPEG1BitRate[bitRate] : MPEG2XBitRate[bitRate]) * 1000; 563 | 564 | if (mpegVersion == 3) 565 | sampleRate = MPEG1SampleRate[sampleRate]; 566 | else if (mpegVersion == 2) 567 | sampleRate = MPEG20SampleRate[sampleRate]; 568 | else 569 | sampleRate = MPEG25SampleRate[sampleRate]; 570 | 571 | frameLen = GetFrameLength(mpegVersion, bitRate, sampleRate, padding); 572 | if (frameLen > length) { 573 | break; 574 | } 575 | 576 | bool isVBRHeaderFrame = false; 577 | if (_frameOffsets.Count == 0) { 578 | // Check for an existing VBR header just to be safe (I haven't seen any in FLVs) 579 | int o = offset + GetFrameDataOffset(mpegVersion, channelMode); 580 | if (BitConverterBE.ToUInt32(buff, o) == 0x58696E67) { // "Xing" 581 | isVBRHeaderFrame = true; 582 | _delayWrite = false; 583 | _hasVBRHeader = true; 584 | } 585 | } 586 | 587 | if (isVBRHeaderFrame) { } 588 | else if (_firstBitRate == 0) { 589 | _firstBitRate = bitRate; 590 | _mpegVersion = mpegVersion; 591 | _sampleRate = sampleRate; 592 | _channelMode = channelMode; 593 | _firstFrameHeader = BitConverterBE.ToUInt32(buff, offset); 594 | } 595 | else if (!_isVBR && (bitRate != _firstBitRate)) { 596 | _isVBR = true; 597 | if (_hasVBRHeader) { } 598 | else if (_delayWrite) { 599 | WriteVBRHeader(true); 600 | _writeVBRHeader = true; 601 | _delayWrite = false; 602 | } 603 | else { 604 | _warnings.Add("Detected VBR too late, cannot add VBR header."); 605 | } 606 | } 607 | 608 | _frameOffsets.Add(_totalFrameLength + (uint)offset); 609 | 610 | offset += frameLen; 611 | length -= frameLen; 612 | } 613 | 614 | _totalFrameLength += (uint)buff.Length; 615 | } 616 | 617 | private void WriteVBRHeader(bool isPlaceholder) { 618 | byte[] buff = new byte[GetFrameLength(_mpegVersion, 64000, _sampleRate, 0)]; 619 | if (!isPlaceholder) { 620 | uint header = _firstFrameHeader; 621 | int dataOffset = GetFrameDataOffset(_mpegVersion, _channelMode); 622 | header &= 0xFFFF0DFF; // Clear bitrate and padding fields 623 | header |= 0x00010000; // Set protection bit (indicates that CRC is NOT present) 624 | header |= (uint)((_mpegVersion == 3) ? 5 : 8) << 12; // 64 kbit/sec 625 | General.CopyBytes(buff, 0, BitConverterBE.GetBytes(header)); 626 | General.CopyBytes(buff, dataOffset, BitConverterBE.GetBytes((uint)0x58696E67)); // "Xing" 627 | General.CopyBytes(buff, dataOffset + 4, BitConverterBE.GetBytes((uint)0x7)); // Flags 628 | General.CopyBytes(buff, dataOffset + 8, BitConverterBE.GetBytes((uint)_frameOffsets.Count)); // Frame count 629 | General.CopyBytes(buff, dataOffset + 12, BitConverterBE.GetBytes((uint)_totalFrameLength)); // File length 630 | for (int i = 0; i < 100; i++) { 631 | int frameIndex = (int)((i / 100.0) * _frameOffsets.Count); 632 | buff[dataOffset + 16 + i] = (byte)((_frameOffsets[frameIndex] / (double)_totalFrameLength) * 256.0); 633 | } 634 | } 635 | _fs.Write(buff, 0, buff.Length); 636 | } 637 | 638 | private int GetFrameLength(int mpegVersion, int bitRate, int sampleRate, int padding) { 639 | return ((mpegVersion == 3) ? 144 : 72) * bitRate / sampleRate + padding; 640 | } 641 | 642 | private int GetFrameDataOffset(int mpegVersion, int channelMode) { 643 | return 4 + ((mpegVersion == 3) ? 644 | ((channelMode == 3) ? 17 : 32) : 645 | ((channelMode == 3) ? 9 : 17)); 646 | } 647 | } 648 | 649 | class SpeexWriter : IAudioWriter { 650 | const string _vendorString = "FLV Extract"; 651 | const uint _sampleRate = 16000; 652 | const uint _msPerFrame = 20; 653 | const uint _samplesPerFrame = _sampleRate / (1000 / _msPerFrame); 654 | const int _targetPageDataSize = 4096; 655 | 656 | string _path; 657 | FileStream _fs; 658 | int _serialNumber; 659 | List _packetList; 660 | int _packetListDataSize; 661 | byte[] _pageBuff; 662 | int _pageBuffOffset; 663 | uint _pageSequenceNumber; 664 | ulong _granulePosition; 665 | 666 | public SpeexWriter(string path, int serialNumber) { 667 | _path = path; 668 | _serialNumber = serialNumber; 669 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536); 670 | _fs.Seek((28 + 80) + (28 + 8 + _vendorString.Length), SeekOrigin.Begin); // Speex header + Vorbis comment 671 | _packetList = new List(); 672 | _packetListDataSize = 0; 673 | _pageBuff = new byte[27 + 255 + _targetPageDataSize + 254]; // Header + max segment table + target data size + extra segment 674 | _pageBuffOffset = 0; 675 | _pageSequenceNumber = 2; // First audio packet 676 | _granulePosition = 0; 677 | } 678 | 679 | public void WriteChunk(byte[] chunk, uint timeStamp) { 680 | int[] subModeSizes = new int[] { 0, 43, 119, 160, 220, 300, 364, 492, 79 }; 681 | int[] wideBandSizes = new int[] { 0, 36, 112, 192, 352 }; 682 | int[] inBandSignalSizes = new int[] { 1, 1, 4, 4, 4, 4, 4, 4, 8, 8, 16, 16, 32, 32, 64, 64 }; 683 | int frameStart = -1; 684 | int frameEnd = 0; 685 | int offset = 0; 686 | int length = chunk.Length * 8; 687 | int x; 688 | 689 | while (length - offset >= 5) { 690 | x = BitHelper.Read(chunk, ref offset, 1); 691 | if (x != 0) { 692 | // wideband frame 693 | x = BitHelper.Read(chunk, ref offset, 3); 694 | if (x < 1 || x > 4) goto Error; 695 | offset += wideBandSizes[x] - 4; 696 | } 697 | else { 698 | x = BitHelper.Read(chunk, ref offset, 4); 699 | if (x >= 1 && x <= 8) { 700 | // narrowband frame 701 | if (frameStart != -1) { 702 | WriteFramePacket(chunk, frameStart, frameEnd); 703 | } 704 | frameStart = frameEnd; 705 | offset += subModeSizes[x] - 5; 706 | } 707 | else if (x == 15) { 708 | // terminator 709 | break; 710 | } 711 | else if (x == 14) { 712 | // in-band signal 713 | if (length - offset < 4) goto Error; 714 | x = BitHelper.Read(chunk, ref offset, 4); 715 | offset += inBandSignalSizes[x]; 716 | } 717 | else if (x == 13) { 718 | // custom in-band signal 719 | if (length - offset < 5) goto Error; 720 | x = BitHelper.Read(chunk, ref offset, 5); 721 | offset += x * 8; 722 | } 723 | else goto Error; 724 | } 725 | frameEnd = offset; 726 | } 727 | if (offset > length) goto Error; 728 | 729 | if (frameStart != -1) { 730 | WriteFramePacket(chunk, frameStart, frameEnd); 731 | } 732 | 733 | return; 734 | 735 | Error: 736 | throw new Exception("Invalid Speex data."); 737 | } 738 | 739 | public void Finish() { 740 | WritePage(); 741 | FlushPage(true); 742 | _fs.Seek(0, SeekOrigin.Begin); 743 | _pageSequenceNumber = 0; 744 | _granulePosition = 0; 745 | WriteSpeexHeaderPacket(); 746 | WriteVorbisCommentPacket(); 747 | FlushPage(false); 748 | _fs.Close(); 749 | } 750 | 751 | public string Path { 752 | get { 753 | return _path; 754 | } 755 | } 756 | 757 | private void WriteFramePacket(byte[] data, int startBit, int endBit) { 758 | int lengthBits = endBit - startBit; 759 | byte[] frame = BitHelper.CopyBlock(data, startBit, lengthBits); 760 | if (lengthBits % 8 != 0) { 761 | frame[frame.Length - 1] |= (byte)(0xFF >> ((lengthBits % 8) + 1)); // padding 762 | } 763 | AddPacket(frame, _samplesPerFrame, true); 764 | } 765 | 766 | private void WriteSpeexHeaderPacket() { 767 | byte[] data = new byte[80]; 768 | General.CopyBytes(data, 0, Encoding.ASCII.GetBytes("Speex ")); // speex_string 769 | General.CopyBytes(data, 8, Encoding.ASCII.GetBytes("unknown")); // speex_version 770 | data[28] = 1; // speex_version_id 771 | data[32] = 80; // header_size 772 | General.CopyBytes(data, 36, BitConverterLE.GetBytes((uint)_sampleRate)); // rate 773 | data[40] = 1; // mode (e.g. narrowband, wideband) 774 | data[44] = 4; // mode_bitstream_version 775 | data[48] = 1; // nb_channels 776 | General.CopyBytes(data, 52, BitConverterLE.GetBytes(unchecked((uint)-1))); // bitrate 777 | General.CopyBytes(data, 56, BitConverterLE.GetBytes((uint)_samplesPerFrame)); // frame_size 778 | data[60] = 0; // vbr 779 | data[64] = 1; // frames_per_packet 780 | AddPacket(data, 0, false); 781 | } 782 | 783 | private void WriteVorbisCommentPacket() { 784 | byte[] vendorStringBytes = Encoding.ASCII.GetBytes(_vendorString); 785 | byte[] data = new byte[8 + vendorStringBytes.Length]; 786 | data[0] = (byte)vendorStringBytes.Length; 787 | General.CopyBytes(data, 4, vendorStringBytes); 788 | AddPacket(data, 0, false); 789 | } 790 | 791 | private void AddPacket(byte[] data, uint sampleLength, bool delayWrite) { 792 | OggPacket packet = new OggPacket(); 793 | if (data.Length >= 255) { 794 | throw new Exception("Packet exceeds maximum size."); 795 | } 796 | _granulePosition += sampleLength; 797 | packet.Data = data; 798 | packet.GranulePosition = _granulePosition; 799 | _packetList.Add(packet); 800 | _packetListDataSize += data.Length; 801 | if (!delayWrite || (_packetListDataSize >= _targetPageDataSize) || (_packetList.Count == 255)) { 802 | WritePage(); 803 | } 804 | } 805 | 806 | private void WritePage() { 807 | if (_packetList.Count == 0) return; 808 | FlushPage(false); 809 | WriteToPage(BitConverterBE.GetBytes(0x4F676753U), 0, 4); // "OggS" 810 | WriteToPage((byte)0); // Stream structure version 811 | WriteToPage((byte)((_pageSequenceNumber == 0) ? 0x02 : 0)); // Page flags 812 | WriteToPage((ulong)_packetList[_packetList.Count - 1].GranulePosition); // Position in samples 813 | WriteToPage((uint)_serialNumber); // Stream serial number 814 | WriteToPage((uint)_pageSequenceNumber); // Page sequence number 815 | WriteToPage((uint)0); // Checksum 816 | WriteToPage((byte)_packetList.Count); // Page segment count 817 | foreach (OggPacket packet in _packetList) { 818 | WriteToPage((byte)packet.Data.Length); 819 | } 820 | foreach (OggPacket packet in _packetList) { 821 | WriteToPage(packet.Data, 0, packet.Data.Length); 822 | } 823 | _packetList.Clear(); 824 | _packetListDataSize = 0; 825 | _pageSequenceNumber++; 826 | } 827 | 828 | private void FlushPage(bool isLastPage) { 829 | if (_pageBuffOffset == 0) return; 830 | if (isLastPage) _pageBuff[5] |= 0x04; 831 | uint crc = OggCRC.Calculate(_pageBuff, 0, _pageBuffOffset); 832 | General.CopyBytes(_pageBuff, 22, BitConverterLE.GetBytes(crc)); 833 | _fs.Write(_pageBuff, 0, _pageBuffOffset); 834 | _pageBuffOffset = 0; 835 | } 836 | 837 | private void WriteToPage(byte[] data, int offset, int length) { 838 | Buffer.BlockCopy(data, offset, _pageBuff, _pageBuffOffset, length); 839 | _pageBuffOffset += length; 840 | } 841 | 842 | private void WriteToPage(byte data) { 843 | WriteToPage(new byte[] { data }, 0, 1); 844 | } 845 | 846 | private void WriteToPage(uint data) { 847 | WriteToPage(BitConverterLE.GetBytes(data), 0, 4); 848 | } 849 | 850 | private void WriteToPage(ulong data) { 851 | WriteToPage(BitConverterLE.GetBytes(data), 0, 8); 852 | } 853 | 854 | class OggPacket { 855 | public ulong GranulePosition; 856 | public byte[] Data; 857 | } 858 | } 859 | 860 | class AACWriter : IAudioWriter { 861 | string _path; 862 | FileStream _fs; 863 | int _aacProfile; 864 | int _sampleRateIndex; 865 | int _channelConfig; 866 | 867 | public AACWriter(string path) { 868 | _path = path; 869 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536); 870 | } 871 | 872 | public void WriteChunk(byte[] chunk, uint timeStamp) { 873 | if (chunk.Length < 1) return; 874 | 875 | if (chunk[0] == 0) { // Header 876 | if (chunk.Length < 3) return; 877 | 878 | ulong bits = (ulong)BitConverterBE.ToUInt16(chunk, 1) << 48; 879 | 880 | _aacProfile = BitHelper.Read(ref bits, 5) - 1; 881 | _sampleRateIndex = BitHelper.Read(ref bits, 4); 882 | _channelConfig = BitHelper.Read(ref bits, 4); 883 | 884 | if ((_aacProfile < 0) || (_aacProfile > 3)) 885 | throw new Exception("Unsupported AAC profile."); 886 | if (_sampleRateIndex > 12) 887 | throw new Exception("Invalid AAC sample rate index."); 888 | if (_channelConfig > 6) 889 | throw new Exception("Invalid AAC channel configuration."); 890 | } 891 | else { // Audio data 892 | int dataSize = chunk.Length - 1; 893 | ulong bits = 0; 894 | 895 | // Reference: WriteADTSHeader from FAAC's bitstream.c 896 | 897 | BitHelper.Write(ref bits, 12, 0xFFF); 898 | BitHelper.Write(ref bits, 1, 0); 899 | BitHelper.Write(ref bits, 2, 0); 900 | BitHelper.Write(ref bits, 1, 1); 901 | BitHelper.Write(ref bits, 2, _aacProfile); 902 | BitHelper.Write(ref bits, 4, _sampleRateIndex); 903 | BitHelper.Write(ref bits, 1, 0); 904 | BitHelper.Write(ref bits, 3, _channelConfig); 905 | BitHelper.Write(ref bits, 1, 0); 906 | BitHelper.Write(ref bits, 1, 0); 907 | BitHelper.Write(ref bits, 1, 0); 908 | BitHelper.Write(ref bits, 1, 0); 909 | BitHelper.Write(ref bits, 13, 7 + dataSize); 910 | BitHelper.Write(ref bits, 11, 0x7FF); 911 | BitHelper.Write(ref bits, 2, 0); 912 | 913 | _fs.Write(BitConverterBE.GetBytes(bits), 1, 7); 914 | _fs.Write(chunk, 1, dataSize); 915 | } 916 | } 917 | 918 | public void Finish() { 919 | _fs.Close(); 920 | } 921 | 922 | public string Path { 923 | get { 924 | return _path; 925 | } 926 | } 927 | } 928 | 929 | class RawH264Writer : IVideoWriter { 930 | static readonly byte[] _startCode = new byte[] { 0, 0, 0, 1 }; 931 | 932 | string _path; 933 | FileStream _fs; 934 | int _nalLengthSize; 935 | 936 | public RawH264Writer(string path) { 937 | _path = path; 938 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536); 939 | } 940 | 941 | public void WriteChunk(byte[] chunk, uint timeStamp, int frameType) { 942 | if (chunk.Length < 4) return; 943 | 944 | // Reference: decode_frame from libavcodec's h264.c 945 | 946 | if (chunk[0] == 0) { // Headers 947 | if (chunk.Length < 10) return; 948 | 949 | int offset, spsCount, ppsCount; 950 | 951 | offset = 8; 952 | _nalLengthSize = (chunk[offset++] & 0x03) + 1; 953 | spsCount = chunk[offset++] & 0x1F; 954 | ppsCount = -1; 955 | 956 | while (offset <= chunk.Length - 2) { 957 | if ((spsCount == 0) && (ppsCount == -1)) { 958 | ppsCount = chunk[offset++]; 959 | continue; 960 | } 961 | 962 | if (spsCount > 0) spsCount--; 963 | else if (ppsCount > 0) ppsCount--; 964 | else break; 965 | 966 | int len = (int)BitConverterBE.ToUInt16(chunk, offset); 967 | offset += 2; 968 | if (offset + len > chunk.Length) break; 969 | _fs.Write(_startCode, 0, _startCode.Length); 970 | _fs.Write(chunk, offset, len); 971 | offset += len; 972 | } 973 | } 974 | else { // Video data 975 | int offset = 4; 976 | 977 | if (_nalLengthSize != 2) { 978 | _nalLengthSize = 4; 979 | } 980 | 981 | while (offset <= chunk.Length - _nalLengthSize) { 982 | int len = (_nalLengthSize == 2) ? 983 | (int)BitConverterBE.ToUInt16(chunk, offset) : 984 | (int)BitConverterBE.ToUInt32(chunk, offset); 985 | offset += _nalLengthSize; 986 | if (offset + len > chunk.Length) break; 987 | _fs.Write(_startCode, 0, _startCode.Length); 988 | _fs.Write(chunk, offset, len); 989 | offset += len; 990 | } 991 | } 992 | } 993 | 994 | public void Finish(FractionUInt32 averageFrameRate) { 995 | _fs.Close(); 996 | } 997 | 998 | public string Path { 999 | get { 1000 | return _path; 1001 | } 1002 | } 1003 | } 1004 | 1005 | class WAVWriter : IAudioWriter { 1006 | string _path; 1007 | WAVTools.WAVWriter _wr; 1008 | int blockAlign; 1009 | 1010 | public WAVWriter(string path, int bitsPerSample, int channelCount, int sampleRate) { 1011 | _path = path; 1012 | _wr = new WAVTools.WAVWriter(path, bitsPerSample, channelCount, sampleRate); 1013 | blockAlign = (bitsPerSample / 8) * channelCount; 1014 | } 1015 | 1016 | public void WriteChunk(byte[] chunk, uint timeStamp) { 1017 | _wr.Write(chunk, chunk.Length / blockAlign); 1018 | } 1019 | 1020 | public void Finish() { 1021 | _wr.Close(); 1022 | } 1023 | 1024 | public string Path { 1025 | get { 1026 | return _path; 1027 | } 1028 | } 1029 | } 1030 | 1031 | class AVIWriter : IVideoWriter { 1032 | string _path; 1033 | BinaryWriter _bw; 1034 | int _codecID; 1035 | int _width, _height, _frameCount; 1036 | uint _moviDataSize, _indexChunkSize; 1037 | List _index; 1038 | bool _isAlphaWriter; 1039 | AVIWriter _alphaWriter; 1040 | List _warnings; 1041 | 1042 | // Chunk: Off: Len: 1043 | // 1044 | // RIFF AVI 0 12 1045 | // LIST hdrl 12 12 1046 | // avih 24 64 1047 | // LIST strl 88 12 1048 | // strh 100 64 1049 | // strf 164 48 1050 | // LIST movi 212 12 1051 | // (frames) 224 ??? 1052 | // idx1 ??? ??? 1053 | 1054 | public AVIWriter(string path, int codecID, List warnings) : 1055 | this(path, codecID, warnings, false) { } 1056 | 1057 | private AVIWriter(string path, int codecID, List warnings, bool isAlphaWriter) { 1058 | if ((codecID != 2) && (codecID != 4) && (codecID != 5)) { 1059 | throw new Exception("Unsupported video codec."); 1060 | } 1061 | 1062 | FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read); 1063 | 1064 | _path = path; 1065 | _bw = new BinaryWriter(fs); 1066 | _codecID = codecID; 1067 | _warnings = warnings; 1068 | _isAlphaWriter = isAlphaWriter; 1069 | 1070 | if ((codecID == 5) && !_isAlphaWriter) { 1071 | _alphaWriter = new AVIWriter(path.Substring(0, path.Length - 4) + ".alpha.avi", codecID, warnings, true); 1072 | } 1073 | 1074 | WriteFourCC("RIFF"); 1075 | _bw.Write((uint)0); // chunk size 1076 | WriteFourCC("AVI "); 1077 | 1078 | WriteFourCC("LIST"); 1079 | _bw.Write((uint)192); 1080 | WriteFourCC("hdrl"); 1081 | 1082 | WriteFourCC("avih"); 1083 | _bw.Write((uint)56); 1084 | _bw.Write((uint)0); 1085 | _bw.Write((uint)0); 1086 | _bw.Write((uint)0); 1087 | _bw.Write((uint)0x10); 1088 | _bw.Write((uint)0); // frame count 1089 | _bw.Write((uint)0); 1090 | _bw.Write((uint)1); 1091 | _bw.Write((uint)0); 1092 | _bw.Write((uint)0); // width 1093 | _bw.Write((uint)0); // height 1094 | _bw.Write((uint)0); 1095 | _bw.Write((uint)0); 1096 | _bw.Write((uint)0); 1097 | _bw.Write((uint)0); 1098 | 1099 | WriteFourCC("LIST"); 1100 | _bw.Write((uint)116); 1101 | WriteFourCC("strl"); 1102 | 1103 | WriteFourCC("strh"); 1104 | _bw.Write((uint)56); 1105 | WriteFourCC("vids"); 1106 | WriteFourCC(CodecFourCC); 1107 | _bw.Write((uint)0); 1108 | _bw.Write((uint)0); 1109 | _bw.Write((uint)0); 1110 | _bw.Write((uint)0); // frame rate denominator 1111 | _bw.Write((uint)0); // frame rate numerator 1112 | _bw.Write((uint)0); 1113 | _bw.Write((uint)0); // frame count 1114 | _bw.Write((uint)0); 1115 | _bw.Write((int)-1); 1116 | _bw.Write((uint)0); 1117 | _bw.Write((ushort)0); 1118 | _bw.Write((ushort)0); 1119 | _bw.Write((ushort)0); // width 1120 | _bw.Write((ushort)0); // height 1121 | 1122 | WriteFourCC("strf"); 1123 | _bw.Write((uint)40); 1124 | _bw.Write((uint)40); 1125 | _bw.Write((uint)0); // width 1126 | _bw.Write((uint)0); // height 1127 | _bw.Write((ushort)1); 1128 | _bw.Write((ushort)24); 1129 | WriteFourCC(CodecFourCC); 1130 | _bw.Write((uint)0); // biSizeImage 1131 | _bw.Write((uint)0); 1132 | _bw.Write((uint)0); 1133 | _bw.Write((uint)0); 1134 | _bw.Write((uint)0); 1135 | 1136 | WriteFourCC("LIST"); 1137 | _bw.Write((uint)0); // chunk size 1138 | WriteFourCC("movi"); 1139 | 1140 | _index = new List(); 1141 | } 1142 | 1143 | public void WriteChunk(byte[] chunk, uint timeStamp, int frameType) { 1144 | int offset, len; 1145 | 1146 | offset = 0; 1147 | len = chunk.Length; 1148 | if (_codecID == 4) { 1149 | offset = 1; 1150 | len -= 1; 1151 | } 1152 | if (_codecID == 5) { 1153 | offset = 4; 1154 | if (len >= 4) { 1155 | int alphaOffset = (int)BitConverterBE.ToUInt32(chunk, 0) & 0xFFFFFF; 1156 | if (!_isAlphaWriter) { 1157 | len = alphaOffset; 1158 | } 1159 | else { 1160 | offset += alphaOffset; 1161 | len -= offset; 1162 | } 1163 | } 1164 | else { 1165 | len = 0; 1166 | } 1167 | } 1168 | len = Math.Max(len, 0); 1169 | len = Math.Min(len, chunk.Length - offset); 1170 | 1171 | _index.Add((frameType == 1) ? (uint)0x10 : (uint)0); 1172 | _index.Add(_moviDataSize + 4); 1173 | _index.Add((uint)len); 1174 | 1175 | if ((_width == 0) && (_height == 0)) { 1176 | GetFrameSize(chunk); 1177 | } 1178 | 1179 | WriteFourCC("00dc"); 1180 | _bw.Write(len); 1181 | _bw.Write(chunk, offset, len); 1182 | 1183 | if ((len % 2) != 0) { 1184 | _bw.Write((byte)0); 1185 | len++; 1186 | } 1187 | _moviDataSize += (uint)len + 8; 1188 | _frameCount++; 1189 | 1190 | if (_alphaWriter != null) { 1191 | _alphaWriter.WriteChunk(chunk, timeStamp, frameType); 1192 | } 1193 | } 1194 | 1195 | private void GetFrameSize(byte[] chunk) { 1196 | if (_codecID == 2) { 1197 | // Reference: flv_h263_decode_picture_header from libavcodec's h263.c 1198 | 1199 | if (chunk.Length < 10) return; 1200 | 1201 | if ((chunk[0] != 0) || (chunk[1] != 0)) { 1202 | return; 1203 | } 1204 | 1205 | ulong x = BitConverterBE.ToUInt64(chunk, 2); 1206 | int format; 1207 | 1208 | if (BitHelper.Read(ref x, 1) != 1) { 1209 | return; 1210 | } 1211 | BitHelper.Read(ref x, 5); 1212 | BitHelper.Read(ref x, 8); 1213 | 1214 | format = BitHelper.Read(ref x, 3); 1215 | switch (format) { 1216 | case 0: 1217 | _width = BitHelper.Read(ref x, 8); 1218 | _height = BitHelper.Read(ref x, 8); 1219 | break; 1220 | case 1: 1221 | _width = BitHelper.Read(ref x, 16); 1222 | _height = BitHelper.Read(ref x, 16); 1223 | break; 1224 | case 2: 1225 | _width = 352; 1226 | _height = 288; 1227 | break; 1228 | case 3: 1229 | _width = 176; 1230 | _height = 144; 1231 | break; 1232 | case 4: 1233 | _width = 128; 1234 | _height = 96; 1235 | break; 1236 | case 5: 1237 | _width = 320; 1238 | _height = 240; 1239 | break; 1240 | case 6: 1241 | _width = 160; 1242 | _height = 120; 1243 | break; 1244 | default: 1245 | return; 1246 | } 1247 | } 1248 | else if ((_codecID == 4) || (_codecID == 5)) { 1249 | // Reference: vp6_parse_header from libavcodec's vp6.c 1250 | 1251 | int skip = (_codecID == 4) ? 1 : 4; 1252 | if (chunk.Length < (skip + 8)) return; 1253 | ulong x = BitConverterBE.ToUInt64(chunk, skip); 1254 | 1255 | int deltaFrameFlag = BitHelper.Read(ref x, 1); 1256 | int quant = BitHelper.Read(ref x, 6); 1257 | int separatedCoeffFlag = BitHelper.Read(ref x, 1); 1258 | int subVersion = BitHelper.Read(ref x, 5); 1259 | int filterHeader = BitHelper.Read(ref x, 2); 1260 | int interlacedFlag = BitHelper.Read(ref x, 1); 1261 | 1262 | if (deltaFrameFlag != 0) { 1263 | return; 1264 | } 1265 | if ((separatedCoeffFlag != 0) || (filterHeader == 0)) { 1266 | BitHelper.Read(ref x, 16); 1267 | } 1268 | 1269 | _height = BitHelper.Read(ref x, 8) * 16; 1270 | _width = BitHelper.Read(ref x, 8) * 16; 1271 | 1272 | // chunk[0] contains the width and height (4 bits each, respectively) that should 1273 | // be cropped off during playback, which will be non-zero if the encoder padded 1274 | // the frames to a macroblock boundary. But if you use this adjusted size in the 1275 | // AVI header, DirectShow seems to ignore it, and it can cause stride or chroma 1276 | // alignment problems with VFW if the width/height aren't multiples of 4. 1277 | if (!_isAlphaWriter) { 1278 | int cropX = chunk[0] >> 4; 1279 | int cropY = chunk[0] & 0x0F; 1280 | if (((cropX != 0) || (cropY != 0)) && !_isAlphaWriter) { 1281 | _warnings.Add(String.Format("Suggested cropping: {0} pixels from right, {1} pixels from bottom.", cropX, cropY)); 1282 | } 1283 | } 1284 | } 1285 | } 1286 | 1287 | private string CodecFourCC { 1288 | get { 1289 | if (_codecID == 2) { 1290 | return "FLV1"; 1291 | } 1292 | if ((_codecID == 4) || (_codecID == 5)) { 1293 | return "VP6F"; 1294 | } 1295 | return "NULL"; 1296 | } 1297 | } 1298 | 1299 | private void WriteIndexChunk() { 1300 | uint indexDataSize = (uint)_frameCount * 16; 1301 | 1302 | WriteFourCC("idx1"); 1303 | _bw.Write(indexDataSize); 1304 | 1305 | for (int i = 0; i < _frameCount; i++) { 1306 | WriteFourCC("00dc"); 1307 | _bw.Write(_index[(i * 3) + 0]); 1308 | _bw.Write(_index[(i * 3) + 1]); 1309 | _bw.Write(_index[(i * 3) + 2]); 1310 | } 1311 | 1312 | _indexChunkSize = indexDataSize + 8; 1313 | } 1314 | 1315 | public void Finish(FractionUInt32 averageFrameRate) { 1316 | WriteIndexChunk(); 1317 | 1318 | _bw.BaseStream.Seek(4, SeekOrigin.Begin); 1319 | _bw.Write((uint)(224 + _moviDataSize + _indexChunkSize - 8)); 1320 | 1321 | _bw.BaseStream.Seek(24 + 8, SeekOrigin.Begin); 1322 | _bw.Write((uint)0); 1323 | _bw.BaseStream.Seek(12, SeekOrigin.Current); 1324 | _bw.Write((uint)_frameCount); 1325 | _bw.BaseStream.Seek(12, SeekOrigin.Current); 1326 | _bw.Write((uint)_width); 1327 | _bw.Write((uint)_height); 1328 | 1329 | _bw.BaseStream.Seek(100 + 28, SeekOrigin.Begin); 1330 | _bw.Write((uint)averageFrameRate.D); 1331 | _bw.Write((uint)averageFrameRate.N); 1332 | _bw.BaseStream.Seek(4, SeekOrigin.Current); 1333 | _bw.Write((uint)_frameCount); 1334 | _bw.BaseStream.Seek(16, SeekOrigin.Current); 1335 | _bw.Write((ushort)_width); 1336 | _bw.Write((ushort)_height); 1337 | 1338 | _bw.BaseStream.Seek(164 + 12, SeekOrigin.Begin); 1339 | _bw.Write((uint)_width); 1340 | _bw.Write((uint)_height); 1341 | _bw.BaseStream.Seek(8, SeekOrigin.Current); 1342 | _bw.Write((uint)(_width * _height * 6)); 1343 | 1344 | _bw.BaseStream.Seek(212 + 4, SeekOrigin.Begin); 1345 | _bw.Write((uint)(_moviDataSize + 4)); 1346 | 1347 | _bw.Close(); 1348 | 1349 | if (_alphaWriter != null) { 1350 | _alphaWriter.Finish(averageFrameRate); 1351 | } 1352 | } 1353 | 1354 | private void WriteFourCC(string fourCC) { 1355 | byte[] bytes = System.Text.Encoding.ASCII.GetBytes(fourCC); 1356 | if (bytes.Length != 4) { 1357 | throw new Exception("Invalid FourCC length."); 1358 | } 1359 | _bw.Write(bytes); 1360 | } 1361 | 1362 | public string Path { 1363 | get { 1364 | return _path; 1365 | } 1366 | } 1367 | } 1368 | 1369 | class TimeCodeWriter { 1370 | string _path; 1371 | StreamWriter _sw; 1372 | 1373 | public TimeCodeWriter(string path) { 1374 | _path = path; 1375 | if (path != null) { 1376 | _sw = new StreamWriter(path, false, Encoding.ASCII); 1377 | _sw.WriteLine("# timecode format v2"); 1378 | } 1379 | } 1380 | 1381 | public void Write(uint timeStamp) { 1382 | if (_sw != null) { 1383 | _sw.WriteLine(timeStamp); 1384 | } 1385 | } 1386 | 1387 | public void Finish() { 1388 | if (_sw != null) { 1389 | _sw.Close(); 1390 | _sw = null; 1391 | } 1392 | } 1393 | 1394 | public string Path { 1395 | get { 1396 | return _path; 1397 | } 1398 | } 1399 | } 1400 | 1401 | public struct FractionUInt32 { 1402 | public uint N; 1403 | public uint D; 1404 | 1405 | public FractionUInt32(uint n, uint d) { 1406 | N = n; 1407 | D = d; 1408 | } 1409 | 1410 | public double ToDouble() { 1411 | return (double)N / (double)D; 1412 | } 1413 | 1414 | public void Reduce() { 1415 | uint gcd = GCD(N, D); 1416 | N /= gcd; 1417 | D /= gcd; 1418 | } 1419 | 1420 | private uint GCD(uint a, uint b) { 1421 | uint r; 1422 | 1423 | while (b != 0) { 1424 | r = a % b; 1425 | a = b; 1426 | b = r; 1427 | } 1428 | 1429 | return a; 1430 | } 1431 | 1432 | public override string ToString() { 1433 | return ToString(true); 1434 | } 1435 | 1436 | public string ToString(bool full) { 1437 | if (full) { 1438 | return ToDouble().ToString() + " (" + N.ToString() + "/" + D.ToString() + ")"; 1439 | } 1440 | else { 1441 | return ToDouble().ToString("0.####"); 1442 | } 1443 | } 1444 | } 1445 | } 1446 | -------------------------------------------------------------------------------- /QiyiFLV2MP4/Library/General.cs: -------------------------------------------------------------------------------- 1 | // **************************************************************************** 2 | // 3 | // FLV Extract 4 | // Copyright (C) 2006-2011 J.D. Purcell (moitah@yahoo.com) 5 | // 6 | // This program is free software; you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation; either version 2 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program; if not, write to the Free Software 18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | // 20 | // **************************************************************************** 21 | 22 | using System; 23 | using System.Reflection; 24 | 25 | namespace QiyiFLV2MP4 26 | { 27 | public static class General { 28 | public static string Version { 29 | get { 30 | Version ver = Assembly.GetExecutingAssembly().GetName().Version; 31 | return ver.Major + "." + ver.Minor + "." + ver.Revision; 32 | } 33 | } 34 | 35 | public static void CopyBytes(byte[] dst, int dstOffset, byte[] src) { 36 | Buffer.BlockCopy(src, 0, dst, dstOffset, src.Length); 37 | } 38 | } 39 | 40 | public static class BitHelper { 41 | public static int Read(ref ulong x, int length) { 42 | int r = (int)(x >> (64 - length)); 43 | x <<= length; 44 | return r; 45 | } 46 | 47 | public static int Read(byte[] bytes, ref int offset, int length) { 48 | int startByte = offset / 8; 49 | int endByte = (offset + length - 1) / 8; 50 | int skipBits = offset % 8; 51 | ulong bits = 0; 52 | for (int i = 0; i <= Math.Min(endByte - startByte, 7); i++) { 53 | bits |= (ulong)bytes[startByte + i] << (56 - (i * 8)); 54 | } 55 | if (skipBits != 0) Read(ref bits, skipBits); 56 | offset += length; 57 | return Read(ref bits, length); 58 | } 59 | 60 | public static void Write(ref ulong x, int length, int value) { 61 | ulong mask = 0xFFFFFFFFFFFFFFFF >> (64 - length); 62 | x = (x << length) | ((ulong)value & mask); 63 | } 64 | 65 | public static byte[] CopyBlock(byte[] bytes, int offset, int length) { 66 | int startByte = offset / 8; 67 | int endByte = (offset + length - 1) / 8; 68 | int shiftA = offset % 8; 69 | int shiftB = 8 - shiftA; 70 | byte[] dst = new byte[(length + 7) / 8]; 71 | if (shiftA == 0) { 72 | Buffer.BlockCopy(bytes, startByte, dst, 0, dst.Length); 73 | } 74 | else { 75 | int i; 76 | for (i = 0; i < endByte - startByte; i++) { 77 | dst[i] = (byte)((bytes[startByte + i] << shiftA) | (bytes[startByte + i + 1] >> shiftB)); 78 | } 79 | if (i < dst.Length) { 80 | dst[i] = (byte)(bytes[startByte + i] << shiftA); 81 | } 82 | } 83 | dst[dst.Length - 1] &= (byte)(0xFF << ((dst.Length * 8) - length)); 84 | return dst; 85 | } 86 | } 87 | 88 | public static class BitConverterBE { 89 | public static ulong ToUInt64(byte[] value, int startIndex) { 90 | return 91 | ((ulong)value[startIndex ] << 56) | 92 | ((ulong)value[startIndex + 1] << 48) | 93 | ((ulong)value[startIndex + 2] << 40) | 94 | ((ulong)value[startIndex + 3] << 32) | 95 | ((ulong)value[startIndex + 4] << 24) | 96 | ((ulong)value[startIndex + 5] << 16) | 97 | ((ulong)value[startIndex + 6] << 8) | 98 | ((ulong)value[startIndex + 7] ); 99 | } 100 | 101 | public static uint ToUInt32(byte[] value, int startIndex) { 102 | return 103 | ((uint)value[startIndex ] << 24) | 104 | ((uint)value[startIndex + 1] << 16) | 105 | ((uint)value[startIndex + 2] << 8) | 106 | ((uint)value[startIndex + 3] ); 107 | } 108 | 109 | public static ushort ToUInt16(byte[] value, int startIndex) { 110 | return (ushort)( 111 | (value[startIndex ] << 8) | 112 | (value[startIndex + 1] )); 113 | } 114 | 115 | public static byte[] GetBytes(ulong value) { 116 | byte[] buff = new byte[8]; 117 | buff[0] = (byte)(value >> 56); 118 | buff[1] = (byte)(value >> 48); 119 | buff[2] = (byte)(value >> 40); 120 | buff[3] = (byte)(value >> 32); 121 | buff[4] = (byte)(value >> 24); 122 | buff[5] = (byte)(value >> 16); 123 | buff[6] = (byte)(value >> 8); 124 | buff[7] = (byte)(value ); 125 | return buff; 126 | } 127 | 128 | public static byte[] GetBytes(uint value) { 129 | byte[] buff = new byte[4]; 130 | buff[0] = (byte)(value >> 24); 131 | buff[1] = (byte)(value >> 16); 132 | buff[2] = (byte)(value >> 8); 133 | buff[3] = (byte)(value ); 134 | return buff; 135 | } 136 | 137 | public static byte[] GetBytes(ushort value) { 138 | byte[] buff = new byte[2]; 139 | buff[0] = (byte)(value >> 8); 140 | buff[1] = (byte)(value ); 141 | return buff; 142 | } 143 | } 144 | 145 | public static class BitConverterLE { 146 | public static byte[] GetBytes(ulong value) { 147 | byte[] buff = new byte[8]; 148 | buff[0] = (byte)(value ); 149 | buff[1] = (byte)(value >> 8); 150 | buff[2] = (byte)(value >> 16); 151 | buff[3] = (byte)(value >> 24); 152 | buff[4] = (byte)(value >> 32); 153 | buff[5] = (byte)(value >> 40); 154 | buff[6] = (byte)(value >> 48); 155 | buff[7] = (byte)(value >> 56); 156 | return buff; 157 | } 158 | 159 | public static byte[] GetBytes(uint value) { 160 | byte[] buff = new byte[4]; 161 | buff[0] = (byte)(value ); 162 | buff[1] = (byte)(value >> 8); 163 | buff[2] = (byte)(value >> 16); 164 | buff[3] = (byte)(value >> 24); 165 | return buff; 166 | } 167 | 168 | public static byte[] GetBytes(ushort value) { 169 | byte[] buff = new byte[2]; 170 | buff[0] = (byte)(value ); 171 | buff[1] = (byte)(value >> 8); 172 | return buff; 173 | } 174 | } 175 | 176 | public static class OggCRC { 177 | static uint[] _lut = new uint[256]; 178 | 179 | static OggCRC() { 180 | for (uint i = 0; i < 256; i++) { 181 | uint x = i << 24; 182 | for (uint j = 0; j < 8; j++) { 183 | x = ((x & 0x80000000U) != 0) ? ((x << 1) ^ 0x04C11DB7) : (x << 1); 184 | } 185 | _lut[i] = x; 186 | } 187 | } 188 | 189 | public static uint Calculate(byte[] buff, int offset, int length) { 190 | uint crc = 0; 191 | for (int i = 0; i < length; i++) { 192 | crc = _lut[((crc >> 24) ^ buff[offset + i]) & 0xFF] ^ (crc << 8); 193 | } 194 | return crc; 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /QiyiFLV2MP4/Library/MP4Box.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4/Library/MP4Box.exe -------------------------------------------------------------------------------- /QiyiFLV2MP4/Library/WAVFile.cs: -------------------------------------------------------------------------------- 1 | // **************************************************************************** 2 | // 3 | // FLV Extract 4 | // Copyright (C) 2006-2011 J.D. Purcell (moitah@yahoo.com) 5 | // 6 | // This program is free software; you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation; either version 2 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program; if not, write to the Free Software 18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | // 20 | // **************************************************************************** 21 | 22 | using System; 23 | using System.IO; 24 | 25 | namespace WAVTools { 26 | public class WAVWriter { 27 | BinaryWriter _bw; 28 | bool _canSeek; 29 | bool _wroteHeaders; 30 | int _bitsPerSample, _channelCount, _sampleRate, _blockAlign; 31 | long _finalSampleLen, _sampleLen; 32 | 33 | public WAVWriter(string path, int bitsPerSample, int channelCount, int sampleRate) : 34 | this(new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read), 35 | bitsPerSample, channelCount, sampleRate) 36 | { 37 | } 38 | 39 | public WAVWriter(Stream stream, int bitsPerSample, int channelCount, int sampleRate) { 40 | _bitsPerSample = bitsPerSample; 41 | _channelCount = channelCount; 42 | _sampleRate = sampleRate; 43 | _blockAlign = _channelCount * ((_bitsPerSample + 7) / 8); 44 | 45 | _bw = new BinaryWriter(stream); 46 | _canSeek = stream.CanSeek; 47 | } 48 | 49 | private void WriteHeaders() { 50 | const uint fccRIFF = 0x46464952; 51 | const uint fccWAVE = 0x45564157; 52 | const uint fccFormat = 0x20746D66; 53 | const uint fccData = 0x61746164; 54 | 55 | uint dataChunkSize = GetDataChunkSize(_finalSampleLen); 56 | 57 | _bw.Write(fccRIFF); 58 | _bw.Write((uint)(dataChunkSize + (dataChunkSize & 1) + 36)); 59 | _bw.Write(fccWAVE); 60 | 61 | _bw.Write(fccFormat); 62 | _bw.Write((uint)16); 63 | _bw.Write((ushort)1); 64 | _bw.Write((ushort)_channelCount); 65 | _bw.Write((uint)_sampleRate); 66 | _bw.Write((uint)(_sampleRate * _blockAlign)); 67 | _bw.Write((ushort)_blockAlign); 68 | _bw.Write((ushort)_bitsPerSample); 69 | 70 | _bw.Write(fccData); 71 | _bw.Write((uint)dataChunkSize); 72 | } 73 | 74 | private uint GetDataChunkSize(long sampleCount) { 75 | const long maxFileSize = 0x7FFFFFFEL; 76 | long dataSize = sampleCount * _blockAlign; 77 | if ((dataSize + 44) > maxFileSize) { 78 | dataSize = ((maxFileSize - 44) / _blockAlign) * _blockAlign; 79 | } 80 | return (uint)dataSize; 81 | } 82 | 83 | public void Close() { 84 | if (((_sampleLen * _blockAlign) & 1) == 1) { 85 | _bw.Write((byte)0); 86 | } 87 | 88 | try { 89 | if (_sampleLen != _finalSampleLen) { 90 | if (_canSeek) { 91 | uint dataChunkSize = GetDataChunkSize(_sampleLen); 92 | _bw.Seek(4, SeekOrigin.Begin); 93 | _bw.Write((uint)(dataChunkSize + (dataChunkSize & 1) + 36)); 94 | _bw.Seek(40, SeekOrigin.Begin); 95 | _bw.Write((uint)dataChunkSize); 96 | } 97 | else { 98 | throw new Exception("Samples written differs from the expected sample count."); 99 | } 100 | } 101 | } 102 | finally { 103 | _bw.Close(); 104 | _bw = null; 105 | } 106 | } 107 | 108 | public long Position { 109 | get { 110 | return _sampleLen; 111 | } 112 | } 113 | 114 | public long FinalSampleCount { 115 | set { 116 | _finalSampleLen = value; 117 | } 118 | } 119 | 120 | public void Write(byte[] buff, int sampleCount) { 121 | if (sampleCount <= 0) return; 122 | 123 | if (!_wroteHeaders) { 124 | WriteHeaders(); 125 | _wroteHeaders = true; 126 | } 127 | 128 | _bw.Write(buff, 0, sampleCount * _blockAlign); 129 | _sampleLen += sampleCount; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /QiyiFLV2MP4/Library/js.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4/Library/js.dll -------------------------------------------------------------------------------- /QiyiFLV2MP4/Library/js32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4/Library/js32.dll -------------------------------------------------------------------------------- /QiyiFLV2MP4/Library/libgpac.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4/Library/libgpac.dll -------------------------------------------------------------------------------- /QiyiFLV2MP4/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Diagnostics; 8 | 9 | namespace QiyiFLV2MP4 10 | { 11 | class Program 12 | { 13 | static bool _autoOverwrite; 14 | private static StringBuilder sortOutput = null; 15 | public static string inputPath = ""; 16 | static int Main(string[] args) 17 | { 18 | int argCount = args.Length; 19 | int argIndex = 0; 20 | bool extractVideo = false; 21 | bool extractAudio = false; 22 | bool extractTimeCodes = false; 23 | string outputDirectory = null; 24 | 25 | Console.WriteLine(); 26 | Console.WriteLine("QiyiFLV2MP4 v" + General.Version); 27 | Console.WriteLine("Copyright 2016 风漠兮"); 28 | Console.WriteLine("http://www.fengmoxi.com/"); 29 | Console.WriteLine(); 30 | 31 | if (!File.Exists("js.dll")) 32 | { 33 | byte[] buffer = Properties.Resources.js; 34 | string path = AppDomain.CurrentDomain.BaseDirectory + "js.dll"; 35 | FileStream FS = new FileStream(path, FileMode.Create); 36 | BinaryWriter BWriter = new BinaryWriter(FS); 37 | BWriter.Write(buffer, 0, buffer.Length); 38 | BWriter.Close(); 39 | } 40 | 41 | if (!File.Exists("js32.dll")) 42 | { 43 | byte[] buffer = Properties.Resources.js32; 44 | string path = AppDomain.CurrentDomain.BaseDirectory + "js32.dll"; 45 | FileStream FS = new FileStream(path, FileMode.Create); 46 | BinaryWriter BWriter = new BinaryWriter(FS); 47 | BWriter.Write(buffer, 0, buffer.Length); 48 | BWriter.Close(); 49 | } 50 | 51 | if (!File.Exists("libgpac.dll")) 52 | { 53 | byte[] buffer = Properties.Resources.libgpac; 54 | string path = AppDomain.CurrentDomain.BaseDirectory + "libgpac.dll"; 55 | FileStream FS = new FileStream(path, FileMode.Create); 56 | BinaryWriter BWriter = new BinaryWriter(FS); 57 | BWriter.Write(buffer, 0, buffer.Length); 58 | BWriter.Close(); 59 | } 60 | 61 | if (!File.Exists("MP4Box.exe")) 62 | { 63 | byte[] buffer = Properties.Resources.MP4Box; 64 | string path = AppDomain.CurrentDomain.BaseDirectory + "MP4Box.exe"; 65 | FileStream FS = new FileStream(path, FileMode.Create); 66 | BinaryWriter BWriter = new BinaryWriter(FS); 67 | BWriter.Write(buffer, 0, buffer.Length); 68 | BWriter.Close(); 69 | } 70 | 71 | try 72 | { 73 | _autoOverwrite = true; 74 | extractVideo = true; 75 | extractAudio = true; 76 | 77 | if (argIndex != (argCount - 1)) 78 | { 79 | throw new Exception(); 80 | } 81 | inputPath = args[argIndex]; 82 | } 83 | catch 84 | { 85 | Console.WriteLine("Arguments: source_path"); 86 | Console.WriteLine(); 87 | return 1; 88 | } 89 | 90 | try 91 | { 92 | using (FLVFile flvFile = new FLVFile(Path.GetFullPath(inputPath))) 93 | { 94 | if (outputDirectory != null) 95 | { 96 | flvFile.OutputDirectory = Path.GetFullPath(outputDirectory); 97 | } 98 | flvFile.ExtractStreams(extractAudio, extractVideo, extractTimeCodes, PromptOverwrite); 99 | if ((flvFile.TrueFrameRate != null) || (flvFile.AverageFrameRate != null)) 100 | { 101 | if (flvFile.TrueFrameRate != null) 102 | { 103 | Console.WriteLine("True Frame Rate: " + flvFile.TrueFrameRate.ToString()); 104 | } 105 | if (flvFile.AverageFrameRate != null) 106 | { 107 | Console.WriteLine("Average Frame Rate: " + flvFile.AverageFrameRate.ToString()); 108 | } 109 | Console.WriteLine(); 110 | } 111 | if (flvFile.Warnings.Length != 0) 112 | { 113 | foreach (string warning in flvFile.Warnings) 114 | { 115 | Console.WriteLine("Warning: " + warning); 116 | } 117 | Console.WriteLine(); 118 | } 119 | } 120 | } 121 | catch (Exception ex) 122 | { 123 | Console.WriteLine("Error: " + ex.Message); 124 | return 1; 125 | } 126 | 127 | Process sortProcess; 128 | sortProcess = new Process(); 129 | sortOutput = new StringBuilder(""); 130 | sortProcess.StartInfo.FileName = "cmd.exe"; 131 | sortProcess.StartInfo.UseShellExecute = false;// 必须禁用操作系统外壳程序 132 | sortProcess.StartInfo.RedirectStandardOutput = true; 133 | sortProcess.StartInfo.RedirectStandardError = true; //重定向错误输出 134 | sortProcess.StartInfo.CreateNoWindow = true; //设置不显示窗口 135 | sortProcess.StartInfo.RedirectStandardInput = true; 136 | sortProcess.StartInfo.Arguments = "/c mp4box.exe -add \"" + inputPath.Substring(0, inputPath.Length - 4) + ".264#trackID=1:par=1:1:name=\" -add \"" + inputPath.Substring(0, inputPath.Length - 4) + ".aac:name=\" -new \"" + inputPath.Substring(0, inputPath.Length - 4) + ".mp4\""; //设定程式执行参数 137 | sortProcess.Start(); 138 | sortProcess.BeginOutputReadLine();// 异步获取命令行内容 139 | sortProcess.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler); 140 | Console.WriteLine("Packaging!Please wait with patience!"); 141 | sortProcess.WaitForExit();//等待程序执行完退出进程 142 | sortProcess.Close(); 143 | if (File.Exists(inputPath.Substring(0, inputPath.Length - 4) + ".aac")) 144 | File.Delete(inputPath.Substring(0, inputPath.Length - 4) + ".aac"); 145 | if (File.Exists(inputPath.Substring(0, inputPath.Length - 4) + ".264")) 146 | File.Delete(inputPath.Substring(0, inputPath.Length - 4) + ".264"); 147 | Console.WriteLine("Congratulations! " + inputPath + " has been converted to " + inputPath.Substring(0, inputPath.Length - 4) + ".mp4 successfully!"); 148 | return 0; 149 | } 150 | 151 | private static bool PromptOverwrite(string path) 152 | { 153 | if (_autoOverwrite) return true; 154 | bool? overwrite = null; 155 | Console.Write("Output file \"" + Path.GetFileName(path) + "\" already exists, overwrite? (y/n): "); 156 | while (overwrite == null) 157 | { 158 | char c = Console.ReadKey(true).KeyChar; 159 | if (c == 'y') overwrite = true; 160 | if (c == 'n') overwrite = false; 161 | } 162 | Console.WriteLine(overwrite.Value ? "y" : "n"); 163 | Console.WriteLine(); 164 | return overwrite.Value; 165 | } 166 | private static void SortOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) 167 | { 168 | if (!String.IsNullOrEmpty(outLine.Data)) 169 | { 170 | Console.WriteLine(outLine.Data + Environment.NewLine); 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /QiyiFLV2MP4/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("爱奇艺FLV重封装工具")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("QiyiFLV2MP4")] 13 | [assembly: AssemblyCopyright("Copyright © 风漠兮 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | //将 ComVisible 设置为 false 将使此程序集中的类型 18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("a964cb68-e117-4cb7-b526-cd6076aae07e")] 24 | 25 | // 程序集的版本信息由下列四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: : 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.1.0")] 36 | [assembly: AssemblyFileVersion("1.0.1.0")] 37 | -------------------------------------------------------------------------------- /QiyiFLV2MP4/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace QiyiFLV2MP4.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// 一个强类型的资源类,用于查找本地化的字符串等。 17 | /// 18 | // 此类是由 StronglyTypedResourceBuilder 19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen 21 | // (以 /str 作为命令选项),或重新生成 VS 项目。 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// 返回此类使用的缓存的 ResourceManager 实例。 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("QiyiFLV2MP4.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// 使用此强类型资源类,为所有资源查找 51 | /// 重写当前线程的 CurrentUICulture 属性。 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// 查找 System.Byte[] 类型的本地化资源。 65 | /// 66 | internal static byte[] js { 67 | get { 68 | object obj = ResourceManager.GetObject("js", resourceCulture); 69 | return ((byte[])(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// 查找 System.Byte[] 类型的本地化资源。 75 | /// 76 | internal static byte[] js32 { 77 | get { 78 | object obj = ResourceManager.GetObject("js32", resourceCulture); 79 | return ((byte[])(obj)); 80 | } 81 | } 82 | 83 | /// 84 | /// 查找 System.Byte[] 类型的本地化资源。 85 | /// 86 | internal static byte[] libgpac { 87 | get { 88 | object obj = ResourceManager.GetObject("libgpac", resourceCulture); 89 | return ((byte[])(obj)); 90 | } 91 | } 92 | 93 | /// 94 | /// 查找 System.Byte[] 类型的本地化资源。 95 | /// 96 | internal static byte[] MP4Box { 97 | get { 98 | object obj = ResourceManager.GetObject("MP4Box", resourceCulture); 99 | return ((byte[])(obj)); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /QiyiFLV2MP4/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Library\js.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 123 | 124 | 125 | ..\Library\js32.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 126 | 127 | 128 | ..\Library\libgpac.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 129 | 130 | 131 | ..\Library\MP4Box.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 132 | 133 | -------------------------------------------------------------------------------- /QiyiFLV2MP4/QiyiFLV2MP4.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {A964CB68-E117-4CB7-B526-CD6076AAE07E} 8 | Exe 9 | Properties 10 | QiyiFLV2MP4 11 | QiyiFLV2MP4 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | True 53 | True 54 | Resources.resx 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ResXFileCodeGenerator 63 | Resources.Designer.cs 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #QiyiFLV2MP4 2 | 3 | QSV文件格式是奇艺影音特有的视频文件格式,尽管可以使用QSV2FLV转换成FLV格式,但是转换FLV时间轴不对,大部分常用播放器会出现时间变长、播放卡顿等现象,使用本工具可以重新封装QSV2FLV生成的FLV文件为MP4文件,以使常用播放器正常播放。 4 | 5 | 使用方法:在命令提示符中切换到FLV所在文件夹,输入QiyiFLV2MP4 文件名即可。 --------------------------------------------------------------------------------