diff --git a/.gitignore b/.gitignore index 80039820..46c7062d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,5 @@ pkg ## PROJECT::SPECIFIC script/* tmp/* -Gemfile.lock \ No newline at end of file +Gemfile.lock +*.gem \ No newline at end of file diff --git a/lib/ffmpeg/encoding_options.rb b/lib/ffmpeg/encoding_options.rb index 73bbb6fd..ad1e6a08 100644 --- a/lib/ffmpeg/encoding_options.rb +++ b/lib/ffmpeg/encoding_options.rb @@ -23,11 +23,17 @@ def to_s end def width - self[:resolution].split("x").first.to_i rescue nil + self["resolution"].split("x").first.to_i rescue nil end def height - self[:resolution].split("x").last.to_i rescue nil + self["resolution"].split("x").last.to_i rescue nil + end + + def reverse_resolution + return "Resolution not found" if height.nil? || width.nil? + @reverse_resolution = height.to_s + "x" + width.to_s + self["resolution"] = @reverse_resolution end private @@ -154,6 +160,50 @@ def convert_watermark(value) "-i #{value}" end + #--------------------------------- + + def convert_cpu_used(value) + "-cpu-used #{value}" + end + + def convert_quality(value) + "-deadline #{value}" + end + + def convert_qmin(value) + "-qmin #{value}" + end + + def convert_qmax(value) + "-qmax #{value}" + end + + def convert_pass(value) + "-pass #{value}" + end + + def convert_passlogfile(value) + "-passlogfile #{value}" + end + + def convert_video_profile(value) + "-profile:v #{value}" + end + + def convert_crf(value) + "-crf #{value}" + end + + def convert_strict(value) + "-strict #{value}" + end + + def convert_transpose(value) + "-vf transpose=#{value}" + end + + #--------------------------------- + def convert_watermark_filter(value) case value[:position].to_s when "LT" diff --git a/lib/ffmpeg/movie.rb b/lib/ffmpeg/movie.rb index a52c3bf4..0d015d25 100644 --- a/lib/ffmpeg/movie.rb +++ b/lib/ffmpeg/movie.rb @@ -8,6 +8,8 @@ class Movie attr_reader :audio_stream, :audio_codec, :audio_bitrate, :audio_sample_rate, :audio_channels attr_reader :container + UNSUPPORTED_CODEC_PATTERN = /^Unsupported codec with id (\d+) for input stream (\d+)$/ + def initialize(path) raise Errno::ENOENT, "the file '#{path}' does not exist" unless File.exist?(path) @@ -50,9 +52,9 @@ def initialize(path) @bitrate = metadata[:format][:bit_rate].to_i - unless video_streams.empty? - # TODO: Handle multiple video codecs (is that possible?) - video_stream = video_streams.first + # TODO: Handle multiple video codecs (is that possible?) + video_stream = video_streams.first + unless video_stream.nil? @video_codec = video_stream[:codec_name] @colorspace = video_stream[:pix_fmt] @width = video_stream[:width] @@ -76,9 +78,9 @@ def initialize(path) end end - unless audio_streams.empty? - # TODO: Handle multiple audio codecs - audio_stream = audio_streams.first + # TODO: Handle multiple audio codecs + audio_stream = audio_streams.first + unless audio_stream.nil? @audio_channels = audio_stream[:channels].to_i @audio_codec = audio_stream[:codec_name] @audio_sample_rate = audio_stream[:sample_rate].to_i @@ -89,12 +91,24 @@ def initialize(path) end + unsupported_stream_ids = unsupported_streams(std_error) + nil_or_unsupported = -> (stream) { stream.nil? || unsupported_stream_ids.include?(stream[:index]) } + + @invalid = true if nil_or_unsupported.(video_stream) && nil_or_unsupported.(audio_stream) @invalid = true if metadata.key?(:error) - @invalid = true if std_error.include?("Unsupported codec") @invalid = true if std_error.include?("is not supported") @invalid = true if std_error.include?("could not find codec parameters") end + def unsupported_streams(std_error) + [].tap do |stream_indices| + std_error.each_line do |line| + match = line.match(UNSUPPORTED_CODEC_PATTERN) + stream_indices << match[2].to_i if match + end + end + end + def valid? not @invalid end diff --git a/lib/ffmpeg/transcoder.rb b/lib/ffmpeg/transcoder.rb index 636def42..ce6e18a3 100644 --- a/lib/ffmpeg/transcoder.rb +++ b/lib/ffmpeg/transcoder.rb @@ -24,11 +24,12 @@ def initialize(movie, output_file, options = EncodingOptions.new, transcoder_opt else raise ArgumentError, "Unknown options format '#{options.class}', should be either EncodingOptions, Hash or String." end - + @transcoder_options = transcoder_options @errors = [] apply_transcoder_options + apply_transpose end def run(&block) @@ -102,19 +103,25 @@ def validate_output_file(&block) def apply_transcoder_options # if true runs #validate_output_file @transcoder_options[:validate] = @transcoder_options.fetch(:validate) { true } - return if @movie.calculated_aspect_ratio.nil? case @transcoder_options[:preserve_aspect_ratio].to_s when "width" new_height = @raw_options.width / @movie.calculated_aspect_ratio new_height = new_height.ceil.even? ? new_height.ceil : new_height.floor new_height += 1 if new_height.odd? # needed if new_height ended up with no decimals in the first place - @raw_options[:resolution] = "#{@raw_options.width}x#{new_height}" + @raw_options["resolution"] = "#{@raw_options.width}x#{new_height}" when "height" new_width = @raw_options.height * @movie.calculated_aspect_ratio new_width = new_width.ceil.even? ? new_width.ceil : new_width.floor new_width += 1 if new_width.odd? - @raw_options[:resolution] = "#{new_width}x#{@raw_options.height}" + @raw_options["resolution"] = "#{new_width}x#{@raw_options.height}" + end + end + + def apply_transpose + case @raw_options["transpose"].to_i + when 1, 3 + @raw_options.reverse_resolution end end diff --git a/spec/ffmpeg/movie_spec.rb b/spec/ffmpeg/movie_spec.rb index de5587b6..17218092 100644 --- a/spec/ffmpeg/movie_spec.rb +++ b/spec/ffmpeg/movie_spec.rb @@ -27,9 +27,7 @@ module FFMPEG end context "given a non movie file" do - let(:movie) do - movie = Movie.new(__FILE__) - end + let(:movie) { Movie.new(__FILE__) } it "should not be valid" do movie.should_not be_valid @@ -109,6 +107,14 @@ module FFMPEG end end + context "given an ios9 mov file (with superfluous data streams)" do + let(:movie) { Movie.new("#{fixture_path}/movies/ios9.mov") } + + it "should be valid" do + movie.should be_valid + end + end + context "given a broken mp4 file" do let(:movie) { Movie.new("#{fixture_path}/movies/broken.mp4") } @@ -195,6 +201,19 @@ module FFMPEG Movie.new(__FILE__) end + it "should be valid" do + movie.should be_valid + end + end + + context "given a file with non supported audio and video" do + let(:fixture_file) { 'file_with_non_supported_audio_and_video_stdout.txt' } + let(:movie) do + fake_stderr = StringIO.new(File.read("#{fixture_path}/outputs/file_with_non_supported_audio_and_video_stderr.txt")) + Open3.stub(:popen3).and_yield(nil,fake_output,fake_stderr) + Movie.new(__FILE__) + end + it "should not be valid" do movie.should_not be_valid end diff --git a/spec/fixtures/movies/ios9.mov b/spec/fixtures/movies/ios9.mov new file mode 100644 index 00000000..7778bb88 Binary files /dev/null and b/spec/fixtures/movies/ios9.mov differ diff --git a/spec/fixtures/outputs/file_with_non_supported_audio_and_video_stderr.txt b/spec/fixtures/outputs/file_with_non_supported_audio_and_video_stderr.txt new file mode 100644 index 00000000..451de92a --- /dev/null +++ b/spec/fixtures/outputs/file_with_non_supported_audio_and_video_stderr.txt @@ -0,0 +1,28 @@ +ffprobe version 2.1.1-static Copyright (c) 2007-2013 the FFmpeg developers + built on Feb 4 2014 13:01:52 with gcc 4.6 (Ubuntu/Linaro 4.6.3-1ubuntu5) + configuration: --prefix=/home/ryan/Source/ffmpeg-static/target --extra-cflags='-I/home/ryan/Source/ffmpeg-static/target/include -static' --extra-ldflags='-L/home/ryan/Source/ffmpeg-static/target/lib -lm -static' --extra-version=static --disable-debug --disable-shared --enable-static --extra-cflags=--static --disable-ffplay --disable-ffserver --disable-doc --enable-gpl --enable-pthreads --enable-postproc --enable-gray --enable-runtime-cpudetect --disable-libfaac --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libx264 --enable-libxvid --enable-bzlib --enable-zlib --enable-nonfree --enable-version3 --enable-libvpx --disable-devices --disable-decoder=aac + libavutil 52. 48.101 / 52. 48.101 + libavcodec 55. 39.101 / 55. 39.101 + libavformat 55. 19.104 / 55. 19.104 + libavdevice 55. 5.100 / 55. 5.100 + libavfilter 3. 90.100 / 3. 90.100 + libswscale 2. 5.101 / 2. 5.101 + libswresample 0. 17.104 / 0. 17.104 + libpostproc 52. 3.100 / 52. 3.100 +Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'bigbucksbunny_trailer_720p.mov': + Metadata: + major_brand : qt + minor_version : 537199360 + compatible_brands: qt + creation_time : 2008-03-18 12:45:48 + Duration: 00:00:33.00, start: 0.000000, bitrate: 4318 kb/s + Stream #0:0(eng): Video: h264 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709), 1280x720, 3945 kb/s, 25 fps, 25 tbr, 600 tbn, 1200 tbc (default) + Metadata: + creation_time : 2008-03-18 12:45:48 + handler_name : Apple Alias Data Handler + Stream #0:1(eng): Audio: aac (mp4a / 0x6134706D), 48000 Hz, 5.1(side), 428 kb/s (default) + Metadata: + creation_time : 2008-03-18 12:45:48 + handler_name : Apple Alias Data Handler +Unsupported codec with id 37923 for input stream 0 +Unsupported codec with id 86018 for input stream 1 diff --git a/spec/fixtures/outputs/file_with_non_supported_audio_and_video_stdout.txt b/spec/fixtures/outputs/file_with_non_supported_audio_and_video_stdout.txt new file mode 100644 index 00000000..e5f95ab5 --- /dev/null +++ b/spec/fixtures/outputs/file_with_non_supported_audio_and_video_stdout.txt @@ -0,0 +1,104 @@ +{ + "streams": [ + { + "index": 0, + "codec_name": "h264", + "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", + "profile": "Main", + "codec_type": "video", + "codec_time_base": "1/1200", + "codec_tag_string": "avc1", + "codec_tag": "0x31637661", + "width": 1280, + "height": 720, + "has_b_frames": 0, + "sample_aspect_ratio": "0:1", + "display_aspect_ratio": "0:1", + "pix_fmt": "yuv420p", + "level": 31, + "r_frame_rate": "25/1", + "avg_frame_rate": "25/1", + "time_base": "1/600", + "start_pts": 0, + "start_time": "0.000000", + "duration_ts": 19488, + "duration": "32.480000", + "bit_rate": "3945184", + "nb_frames": "812", + "disposition": { + "default": 1, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0 + }, + "tags": { + "creation_time": "2008-03-18 12:45:48", + "language": "eng", + "handler_name": "Apple Alias Data Handler" + } + }, + { + "index": 1, + "codec_type": "audio", + "codec_time_base": "0/1", + "codec_tag_string": "mp4a", + "codec_tag": "0x6134706d", + "sample_rate": "48000", + "channels": 6, + "channel_layout": "5.1(side)", + "bits_per_sample": 0, + "r_frame_rate": "0/0", + "avg_frame_rate": "0/0", + "time_base": "1/48000", + "start_pts": 0, + "start_time": "0.000000", + "duration_ts": 1584128, + "duration": "33.002667", + "bit_rate": "428605", + "nb_frames": "1547", + "disposition": { + "default": 1, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0 + }, + "tags": { + "creation_time": "2008-03-18 12:45:48", + "language": "eng", + "handler_name": "Apple Alias Data Handler" + } + } + ], + "format": { + "filename": "bigbucksbunny_trailer_720p.mov", + "nb_streams": 2, + "nb_programs": 0, + "format_name": "mov,mp4,m4a,3gp,3g2,mj2", + "format_long_name": "QuickTime / MOV", + "start_time": "0.000000", + "duration": "32.995000", + "size": "17810888", + "bit_rate": "4318445", + "probe_score": 100, + "tags": { + "major_brand": "qt ", + "minor_version": "537199360", + "compatible_brands": "qt ", + "creation_time": "2008-03-18 12:45:48" + } + } +}