пятница, 14 сентября 2012 г.

Xuggler, iPhone / iPad video rotation

Имеется веб-приложение использующее Xuggler для конвертации видео в flv и видео-файл записанный iPhone в портетном режиме. Видео конвертируется успешно, но в результате повернуто на 90 градусов. В таких видео с мобильных устройств фиксируется информация об ориентации записи (угол поворота камеры). QuickTime при воспроизведении определяет и поворачивает видео как нужно. Другие опробованные плееры (VLC, Kaffeine, MPlayer) автоматически видео не поворачивают. Попробовал вывести в эхо печать все метатеги и проперти имеющегося видеофайла. Потом погуглил, поискал.

Нашел что такая инфа относится к EXIF data и используется она не только в изображениях, но и в видео. В видеофайлах EXIF можно прочитать с помощью замечательной утилиты MediaInfo. Она то и показала, что параметр rotation (угол поворота в градусах) хранится в медиатегах видеопотока, а я то искал в контейнере файла :).

Осталось только повернуть кадры на нужный угол rotation (в данном примере я делаю еще и ресайз под нужный размер с учетом пропорций исходного видео). Надеюсь кому-нить еще пригодится:

public class MultimediaContentConverterVideo{
    public void convertOriginal(String urlIn, String urlOut, boolean debug) throws IOException {

        String workingPath = FilenameUtils.getFullPath(urlIn);
        String filenamePrefix = FilenameUtils.getBaseName(urlIn);

        // create a media reader
        IMediaReader reader = ToolFactory.makeReader(urlIn);

        // stipulate that we want BufferedImages created in BGR 24bit color space
        reader.setBufferedImageTypeToGenerate(BufferedImage.TYPE_3BYTE_BGR);

        // create a writer which receives the decoded media from
        // reader, encodes it and writes it out to the specified file
        IMediaWriter writer = ToolFactory.makeWriter(urlOut, reader);

        // add a debug listener to the writer to see media writer events
        if (debug) {
            writer.addListener(ToolFactory.makeDebugListener());
        }

        // read and decode packets from the source file and
        // then encode and write out data to the output file
        VideoRotator rotator = new VideoRotator();
        reader.addListener(rotator);
        rotator.addListener(writer);

        while (reader.readPacket() == null);

    }

    private class VideoRotator extends MediaToolAdapter {

        private int rotate = 0;

        @Override
        public void onVideoPicture(IVideoPictureEvent event) {

            if (rotate != 0) {
                BufferedImage img = event.getImage();
                // rotate
                double theta = Math.PI / 180 * rotate;
                int widthRotated = (img.getWidth() >= img.getHeight()) ? img.getWidth() : img.getHeight();
                int heightRotated = (img.getWidth() >= img.getHeight()) ? img.getWidth() : img.getHeight();
                BufferedImage imgRotated = new BufferedImage(widthRotated, heightRotated, img.getType());
                Graphics2D gRotate = imgRotated.createGraphics();
                AffineTransform transform = new AffineTransform();

                transform.rotate(theta, widthRotated / 2, heightRotated / 2);
                transform.translate((widthRotated - img.getWidth()) / 2, (heightRotated - img.getHeight()) / 2);
                gRotate.drawImage(img, transform, null);
                gRotate.dispose();
                // resize
                int widthResized = widthRotated;
                int heightResized = heightRotated;
                if (heightResized > img.getHeight()) {
                    widthResized = Math.round((1.0f * img.getHeight() / heightResized) * widthResized);
                    heightResized = img.getHeight();
                } else if (widthResized > img.getWidth()) {
                    heightResized = Math.round((1.0f * img.getWidth() / widthResized) * heightResized);
                    widthResized = img.getWidth();
                }
                Graphics2D g = img.createGraphics();
                g.setColor(Color.BLACK);
                g.fillRect(0, 0, img.getWidth(), img.getHeight());
                g.drawImage(imgRotated, (img.getWidth() - widthResized) / 2, (img.getHeight() - heightResized) / 2, widthResized, heightResized, null);
                g.dispose();
            }
            super.onVideoPicture(event);
        }

        @Override
        public void onAddStream(IAddStreamEvent event) {
            int streamIndex = event.getStreamIndex();
            IStream stream = event.getSource().getContainer().getStream(streamIndex);
            IStreamCoder streamCoder = event.getSource().getContainer().getStream(streamIndex).getStreamCoder();
            if (streamCoder.getCodecType() == ICodec.Type.CODEC_TYPE_AUDIO) {
                streamCoder.setSampleRate(44100);
            } else if (streamCoder.getCodecType() == ICodec.Type.CODEC_TYPE_VIDEO) {
                String metaRotate = stream.getMetaData().getValue(META_KEY_ROTATE);
                if (metaRotate != null && metaRotate.matches("\\d+")) {
                    rotate = Integer.valueOf(metaRotate);
                }
            }
            super.onAddStream(event);
        }
    }
}


PS. Скорее всего, видео с других мобильных устройств потребует аналогичной обработки для правильного определения угла поворота видео.

3 комментария: