com.att.aro.core.packetanalysis.impl.VideoUsageAnalysisImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.att.aro.core.packetanalysis.impl.VideoUsageAnalysisImpl.java

Source

/*
 *  Copyright 2017 AT&T
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/
package com.att.aro.core.packetanalysis.impl;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Level;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;

import com.att.aro.core.ILogger;
import com.att.aro.core.bestpractice.pojo.VideoUsage;
import com.att.aro.core.commandline.IExternalProcessRunner;
import com.att.aro.core.fileio.IFileManager;
import com.att.aro.core.model.InjectLogger;
import com.att.aro.core.packetanalysis.IHttpRequestResponseHelper;
import com.att.aro.core.packetanalysis.IVideoUsageAnalysis;
import com.att.aro.core.packetanalysis.pojo.AbstractTraceResult;
import com.att.aro.core.packetanalysis.pojo.ByteRange;
import com.att.aro.core.packetanalysis.pojo.HttpDirection;
import com.att.aro.core.packetanalysis.pojo.HttpRequestResponseInfo;
import com.att.aro.core.packetanalysis.pojo.PacketInfo;
import com.att.aro.core.packetanalysis.pojo.Session;
import com.att.aro.core.packetreader.pojo.Packet;
import com.att.aro.core.preferences.IPreferenceHandler;
import com.att.aro.core.preferences.impl.PreferenceHandlerImpl;
import com.att.aro.core.settings.Settings;
import com.att.aro.core.util.IStringParse;
import com.att.aro.core.util.StringParse;
import com.att.aro.core.util.Util;
import com.att.aro.core.videoanalysis.IVideoAnalysisConfigHelper;
import com.att.aro.core.videoanalysis.IVideoEventDataHelper;
import com.att.aro.core.videoanalysis.IVideoTabHelper;
import com.att.aro.core.videoanalysis.pojo.AROManifest;
import com.att.aro.core.videoanalysis.pojo.ManifestDash;
import com.att.aro.core.videoanalysis.pojo.ManifestDashPlayReady;
import com.att.aro.core.videoanalysis.pojo.ManifestHLS;
import com.att.aro.core.videoanalysis.pojo.ManifestSSM;
import com.att.aro.core.videoanalysis.pojo.VideoData;
import com.att.aro.core.videoanalysis.pojo.VideoEvent;
import com.att.aro.core.videoanalysis.pojo.VideoEvent.VideoType;
import com.att.aro.core.videoanalysis.pojo.VideoEventData;
import com.att.aro.core.videoanalysis.pojo.VideoFormat;
import com.att.aro.core.videoanalysis.pojo.VideoUsagePrefs;
import com.att.aro.core.videoanalysis.pojo.amazonvideo.MPDAmz;
import com.att.aro.core.videoanalysis.pojo.amazonvideo.SSMAmz;
import com.att.aro.core.videoanalysis.pojo.amazonvideo.XmlManifestHelper;
import com.att.aro.core.videoanalysis.pojo.config.VideoAnalysisConfig;
import com.att.aro.core.videoanalysis.pojo.mpdplayerady.MPDPlayReady;

/**
 * Video usage analysis
 *
 * @Barry
 */
public class VideoUsageAnalysisImpl implements IVideoUsageAnalysis {
    @Autowired
    private IFileManager filemanager;

    @Autowired
    private Settings settings;

    @InjectLogger
    private static ILogger log;

    private IExternalProcessRunner extrunner;

    @Autowired
    public void setExternalProcessRunner(IExternalProcessRunner runner) {
        this.extrunner = runner;
    }

    @Autowired
    private IVideoAnalysisConfigHelper voConfigHelper;

    @Autowired
    private IVideoEventDataHelper voEventDataHelper;

    @Autowired
    private IVideoTabHelper videoTabHelper;

    @Autowired
    public void setHttpRequestResponseHelper(IHttpRequestResponseHelper reqhelper) {
        this.reqhelper = reqhelper;
    }

    @Autowired
    private IStringParse stringParse;
    Pattern extensionPattern = Pattern.compile("^\\b[a-zA-Z0-9]*\\b([\\.a-zA-Z0-9]*\\b)");
    IPreferenceHandler prefs = PreferenceHandlerImpl.getInstance();
    private IHttpRequestResponseHelper reqhelper;
    private String tracePath;
    private boolean imageExtractionRequired = false;
    private String videoPath;
    private String imagePath;
    private boolean absTimeFlag = false;
    private String htmlPath;
    private TreeMap<Double, AROManifest> aroManifestMap;
    private AROManifest aroManifest = null;
    private final String fileVideoSegments = "video_segments";
    private VideoUsagePrefs videoUsagePrefs;
    private VideoAnalysisConfig vConfig;
    private TreeMap<Long, HttpRequestResponseInfo> reqMap;
    private VideoUsage videoUsage;
    private double timeScale = 1;
    private String lastContentType;
    private byte[] altImage;

    /**
     * <pre>
     * Loads a replacement for missing thumbnails, blocked by DRM. The default
     * is the VO App icon image User defined replacement needs to be a PNG image
     * and should be in the VideoOptimizerLibrary and named broken_thumbnail.png
     *
     * @return byte[] of a png image
     */
    private byte[] loadDefaultThumbnail() {
        byte[] data = null;
        String brokenThumbnailPath = Util.getVideoOptimizerLibrary() + Util.FILE_SEPARATOR + "broken_thumbnail.png";
        if (filemanager.fileExist(brokenThumbnailPath)) {
            try {
                Path path = Paths.get(brokenThumbnailPath);
                data = Files.readAllBytes(path);
            } catch (IOException e) {
                log.debug("getThumnail IOException:" + e.getMessage());
            }
        } else {
            String iconName = "aro_24.png";
            String appIconPath = Util.getVideoOptimizerLibrary() + Util.FILE_SEPARATOR + iconName;
            if (!filemanager.fileExist(appIconPath)) {
                Util.makeLibFilesFromJar(iconName);
            }
            if (filemanager.fileExist(appIconPath)) {
                try {
                    Path path = Paths.get(appIconPath);
                    data = Files.readAllBytes(path);
                } catch (IOException e) {
                    log.debug("getIconThumnail IOException:" + e.getMessage());
                }
            }
        }
        return data;
    }

    /**
     * Load VideoUsage Preferences
     */
    @Override
    public void loadPrefs() {
        ObjectMapper mapper = new ObjectMapper();
        String temp = prefs.getPref(VideoUsagePrefs.VIDEO_PREFERENCE);
        if (temp != null && !temp.equals("null")) {
            try {
                videoUsagePrefs = mapper.readValue(temp, VideoUsagePrefs.class);
            } catch (IOException e) {
                log.error("VideoUsagePrefs failed to de-serialize :" + e.getMessage());
            }
        } else {
            try {
                videoUsagePrefs = new VideoUsagePrefs();
                temp = mapper.writeValueAsString(videoUsagePrefs);
                prefs.setPref(VideoUsagePrefs.VIDEO_PREFERENCE, temp);
            } catch (IOException e) {
                log.error("VideoUsagePrefs failed to serialize :" + e.getMessage());
            }
        }
    }

    @Override
    public IPreferenceHandler getPrefs() {
        return prefs;
    }

    @Override
    public VideoUsagePrefs getVideoUsagePrefs() {
        return videoUsagePrefs;
    }

    @Override
    public VideoUsage clearData() {
        if (videoUsage != null && videoUsage.getFilteredSegments() != null) {
            videoUsage.setFilteredSegments(null);
        }
        if (videoUsage != null && videoUsage.getAllSegments() != null) {
            videoUsage.setAllSegments(null);
        }
        videoTabHelper.resetRequestMapList();
        return new VideoUsage("");
    }

    @Override
    public VideoUsage analyze(AbstractTraceResult result, List<Session> sessionlist) {
        Level originalLevel = null;
        if (settings.checkAttributeValue("video", "info")) {
            originalLevel = log.setLevel(Level.INFO);
            log.info("Original logger level :" + originalLevel);
        }
        loadPrefs();
        tracePath = result.getTraceDirectory() + Util.FILE_SEPARATOR;
        log.elevatedInfo("VideoAnalysis for :" + tracePath);
        imagePath = tracePath + "Image" + Util.FILE_SEPARATOR;
        htmlPath = tracePath + "HTML" + Util.FILE_SEPARATOR;
        if (!filemanager.directoryExistAndNotEmpty(htmlPath)) {
            filemanager.mkDir(htmlPath);
            for (final Session session : sessionlist) {
                for (final HttpRequestResponseInfo req : session.getRequestResponseInfo()) {
                    if (req.getDirection() == HttpDirection.RESPONSE && req.getContentType() != null
                            && req.getContentType().contains("text/html")) {
                        extractHtmlContent(session, req);
                    }
                }
            }
        }

        altImage = loadDefaultThumbnail();
        videoPath = tracePath + fileVideoSegments + Util.FILE_SEPARATOR;
        if (!filemanager.directoryExist(videoPath)) {
            filemanager.mkDir(videoPath);
        } else {
            filemanager.directoryDeleteInnerFiles(videoPath);
        }
        if (!filemanager.directoryExist(imagePath)) {
            imageExtractionRequired = true;
            filemanager.mkDir(imagePath);
        } else {
            // Not Required but needed if in case the method is called a second time after initialization
            imageExtractionRequired = false;
        }
        // clear out old objects
        aroManifest = null;
        videoUsage = new VideoUsage(result.getTraceDirectory());
        videoUsage.setVideoUsagePrefs(videoUsagePrefs);
        aroManifestMap = videoUsage.getAroManifestMap();
        loadExternalManifests();
        reqMap = collectReqByTS(sessionlist);
        String imgExtension = null;
        String filename = null;
        for (HttpRequestResponseInfo req : reqMap.values()) {
            log.info(req.toString());
            if (req.getAssocReqResp() == null) {
                continue;
            }
            String oName = req.getObjNameWithoutParams();
            log.info("oName :" + req.getObjNameWithoutParams() + "\theader :" + req.getAllHeaders() + "\tresponse :"
                    + req.getAssocReqResp().getAllHeaders());
            String fullName = extractFullNameFromRRInfo(req);
            String extn = extractExtensionFromName(req.getFileName());
            if (extn == null || StringUtils.isEmpty(extn)) {
                if (oName.contains(".ism/")) {
                    if (fullName.equals("manifest")) {
                        aroManifest = extractManifestDash(req);
                        continue;
                    } else {
                        if (fullName.contains("video")) {
                            filename = truncateString(fullName, "_");
                            extractVideo(req, aroManifest, filename);
                        }
                        continue;
                    }
                }
                continue;
            }
            switch (extn) {
            // DASH - Amazon, Hulu, Netflix, Fullscreen
            case ".mpd": // Dash Manifest
                imgExtension = ".mp4";
                aroManifest = extractManifestDash(req);
                String message = aroManifest != null ? aroManifest.getVideoName() : "extractManifestDash FAILED";
                log.info("extract :" + message);
                break;
            case ".ism": // Dash/MPEG
                log.info("SSM");
            case ".mp4": // Dash/MPEG
            case ".m4v":
            case ".m4i":
            case ".dash":
                fullName = extractFullNameFromRRInfo(req);
                if (fullName.contains("_audio")) {
                    continue;
                }
                vConfig = voConfigHelper.findConfig(req.getObjUri().toString());
                filename = extractNameFromRRInfo(req);
                extractVideo(req, aroManifest, filename);
                break;
            case ".m4a": // audio
                break;
            // DTV
            case ".m3u8": // HLS-DTV Manifest
                imgExtension = ".ts";
                if (!oName.contains("cc")) {
                    aroManifest = extractManifestHLS(req);
                }
                break;
            case ".ts": // HLS video
                filename = "video" + imgExtension;
                extractVideo(req, aroManifest, filename);
                break;
            case ".vtt": // closed caption
                // filename = "videoTT.vtt";
                // extractVideo(req, aroManifest, filename);
                break;
            // Images
            case ".jpg":
            case ".gif":
            case ".tif":
            case ".png":
            case ".jpeg":
                if (imageExtractionRequired) {
                    fullName = extractFullNameFromRRInfo(req);
                    extractImage(req, aroManifest, fullName);
                }
                break;
            case ".css":
            case ".json":
            case ".html":
                continue;
            default:// items found here may need to be handled OR ignored as the above
                log.info("============ failed with extention :" + extn);
                break;
            }
            if (filename != null) {
                log.debug("filename :" + filename);
            }
        }

        updateDuration();
        updateSegments();
        validateManifests();
        if (!filemanager.directoryExistAndNotEmpty(imagePath)) {
            for (Session session : sessionlist) {
                for (HttpRequestResponseInfo reqResp : session.getRequestResponseInfo()) {
                    if (reqResp.getDirection() == HttpDirection.RESPONSE && reqResp.getContentType() != null
                            && reqResp.getContentType().contains("image/")) {
                        if (reqResp.getAssocReqResp().getObjName() != null) {
                            String imageObject = reqResp.getAssocReqResp().getObjName();
                            if (imageObject.indexOf("?v=") >= 0) {
                                String fullName = Util.extractFullNameFromLink(imageObject);
                                String extension = "." + reqResp.getContentType().substring(
                                        reqResp.getContentType().indexOf("image/") + 6,
                                        reqResp.getContentType().length());
                                extractImage(session, reqResp, fullName + extension);
                            }
                        }
                    }
                }
            }
        }
        // Always delete the temp segment folder
        if (!checkDevMode()) {
            filemanager.directoryDeleteInnerFiles(videoPath);
            filemanager.deleteFile(videoPath);
        }
        if (originalLevel != null) {
            log.setLevel(originalLevel);
        }
        log.elevatedInfo("Video usage scan done");
        return identifyInvalidManifest(videoUsage);
    }

    private void extractImage(Session session, HttpRequestResponseInfo response, String imageFileName) {
        if (response != null) {
            byte[] content = null;
            String fullpath;
            try {
                content = reqhelper.getContent(response, session);
                fullpath = getImagePath() + imageFileName;
                filemanager.saveFile(new ByteArrayInputStream(content), fullpath);
            } catch (Exception e) {
                // videoUsage.addFailedRequestMap(request);
                log.info("Failed to extract " + getTimeString(response) + imageFileName + " response: "
                        + e.getMessage());
                return;
            }
        }
    }

    private void loadExternalManifests() {
        // Go through trace-directory/download folder to load external manifests, if exist
        String downloadPath = tracePath + "downloads" + Util.FILE_SEPARATOR;
        if (!filemanager.directoryExist(downloadPath)) {
            filemanager.mkDir(downloadPath);
        }
        String[] files = filemanager.list(downloadPath, null);
        // make sure files has manifest file extension before adding it to aroManifestMap
        double keyTimestamp = 0;
        AROManifest extManifest = null;
        for (String file : files) {
            String extension = extractExtensionFromName(file);
            File manifestFile = new File(downloadPath + file);
            byte[] content;
            if (extension != null) {
                switch (extension) {
                case ".mpd":
                    content = fileToByteArray(manifestFile);
                    aroManifest = new ManifestDash(null, content, videoPath);
                    videoUsage.add(keyTimestamp, aroManifest);
                    keyTimestamp = keyTimestamp + 1000; // increment by 1ms
                    break;
                case ".m3u8":
                    content = fileToByteArray(manifestFile);
                    if (extManifest == null) {
                        try {
                            extManifest = new ManifestHLS(null, content, videoPath);
                            videoUsage.add(keyTimestamp, extManifest);
                            keyTimestamp = keyTimestamp + 1000; // increment by 1ms
                            if (aroManifest == null) {
                                aroManifest = extManifest;
                            }
                        } catch (Exception e) {
                            log.error("Failed to parse manifest data, Name:" + manifestFile.getName());
                        }
                    } else {
                        try {
                            ((ManifestHLS) extManifest).parseManifestData(content);
                        } catch (Exception e) {
                            log.error("Failed to parse manifest data");
                        }
                    }
                    break;
                default:// items found here may need to be handled OR ignored as the above
                    log.debug("failed with extention :" + extension);
                    break;
                }
            }
        }
    }

    private void validateManifests() {
        if (videoUsage != null) {
            TreeMap<Double, AROManifest> manifestMap = videoUsage.getAroManifestMap();
            Set<Double> manifestKeySet = manifestMap.keySet();
            for (Double key : manifestKeySet) {
                AROManifest manifest = manifestMap.get(key);
                manifest.setValid(true);
                if (manifest.getVideoEventList().isEmpty()) {
                    manifest.setValid(false);
                }
                for (VideoEvent event : manifest.getVideoEventList().values()) {
                    if (event.getSegment() < 0) {
                        manifest.setValid(false);
                    }
                }
            }
        }
    }

    private void extractHtmlContent(Session session, HttpRequestResponseInfo response) {
        if (response != null) {
            byte[] content = null;
            try {
                content = reqhelper.getContent(response, session);
                if (content != null && content.length > 0) {
                    if (!filemanager.directoryExist(htmlPath)) {
                        filemanager.mkDir(htmlPath);
                    }
                    filemanager.saveFile(new ByteArrayInputStream(content),
                            htmlPath + Long.toString(System.currentTimeMillis()) + ".html");
                }
            } catch (Exception e) {
                videoUsage.addFailedRequestMap(response);
                log.info("Failed to extract HTML " + e.getMessage());
                return;
            }
        }
    }

    private void updateSegments() {
        log.info("updateSegments()");
        TreeMap<String, Integer> segmentList;
        Integer segment;
        if (videoUsage != null) {
            for (AROManifest manifest : videoUsage.getManifests()) {
                if (manifest.getDuration() > 0 || !manifest.getSegmentEventList().isEmpty()) {
                    segmentList = manifest.getSegmentList();
                    if (segmentList != null && !segmentList.isEmpty()) {
                        for (VideoEvent videoEvent : manifest.getVideoEventList().values()) {
                            // key = generateVideoEventKey(segment, timestamp, videoEvent.getQuality());
                            if (videoEvent.getSegment() < 0) {
                                String key = "";
                                if (videoEvent.getVed().getDateTime() != null) {
                                    key = String.format("%s.%s", videoEvent.getVed().getDateTime(),
                                            videoEvent.getVed().getExtension());
                                } else if (videoEvent.getVed().getSegmentReference() != null) {
                                    key = videoEvent.getVed().getSegmentReference();
                                }
                                segment = segmentList.get(key);
                                if (segment != null) {
                                    videoEvent.setSegment(segment);
                                }
                            }
                            if (videoEvent.getDuration() <= 0) {
                                videoEvent.setDuration(manifest.getDuration());
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * <pre>
     * Final pass to fix update values if not set already.
     * Examine startTime for each segment compared with next segment to determine duration when not set.
     *
     * Problems occur when there is a missing segment and on the last segment.
     *  Missing segments cause an approximation by dividing the duration by the number of missing segments+1
     *  The last segment simply repeats the previous duration, this should not skew results by much.
     */
    private void updateDuration() {
        log.info("updateDuration()");
        if (videoUsage != null) {
            for (AROManifest manifest : videoUsage.getManifests()) {
                if (manifest != null) {
                    NavigableMap<String, VideoEvent> eventMap = manifest.getSegmentEventList();
                    if (manifest instanceof ManifestDash && !eventMap.isEmpty()) {
                        int seg = 0;
                        Entry<String, VideoEvent> lastEntry = eventMap.lastEntry();
                        double lastSeg = lastEntry != null ? lastEntry.getValue().getSegment() : 0;
                        String key = manifest.generateVideoEventKey(0, 0, "z");
                        Entry<String, VideoEvent> val;
                        Entry<String, VideoEvent> valn;
                        double duration = 0;
                        VideoEvent event;
                        String segNextKey = null;
                        for (seg = 1; seg <= lastSeg; seg++) {
                            segNextKey = manifest.generateVideoEventKey(seg, 0, "z");
                            val = eventMap.higherEntry(key);
                            valn = eventMap.higherEntry(segNextKey);
                            if (val == null || valn == null) {
                                break;
                            }
                            event = val.getValue();
                            VideoEvent eventNext = valn.getValue();
                            duration = eventNext.getSegmentStartTime() - event.getSegmentStartTime();
                            double deltaSegment = eventNext.getSegment() - event.getSegment();
                            if (deltaSegment > 1) {
                                duration /= deltaSegment;
                            }
                            updateSegmentDuration(eventMap, key, segNextKey, duration);
                            key = segNextKey;
                        }
                        // handle any segments at the end
                        val = eventMap.higherEntry(key);
                        if (val != null && segNextKey != null) {
                            updateSegmentDuration(eventMap, key, segNextKey, duration);
                        }
                    }
                }
            }
        }
    }

    /**
     * Apply duration values to all events of the same segment number.
     *
     * @param eventMap   Map of VideoEvents
     * @param key      starting key
     * @param segNextKey   ending key
     * @param duration - value to be applied to all VideoEvents
     */
    public void updateSegmentDuration(NavigableMap<String, VideoEvent> eventMap, String key, String segNextKey,
            double duration) {
        for (VideoEvent subEvent : eventMap.subMap(key, segNextKey).values()) {
            if (subEvent.getDuration() <= 0) {
                subEvent.setDuration(duration);
            }
        }
    }

    private boolean checkDevMode() {
        return settings.checkAttributeValue("env", "dev");
    }

    /*
     * Create a TreeMap of all pertinent Requests keyed by timestamp plus tie-breaker
     *
     * @param sessionlist
     * @return Map of Requests
     */
    private TreeMap<Long, HttpRequestResponseInfo> collectReqByTS(List<Session> sessionlist) {
        int counter = 0;
        TreeMap<Long, HttpRequestResponseInfo> reqMap = new TreeMap<>();
        for (Session session : sessionlist) {
            List<HttpRequestResponseInfo> rri = session.getRequestResponseInfo();
            for (HttpRequestResponseInfo rrInfo : rri) {
                if (rrInfo.getDirection().equals(HttpDirection.REQUEST) && rrInfo.getRequestType() != null
                        && rrInfo.getRequestType().equals(HttpRequestResponseInfo.HTTP_GET)
                        && rrInfo.getObjNameWithoutParams().contains(".")) {
                    rrInfo.setSession(session);
                    Long key = getReqInfoKey(rrInfo, 0);
                    if (reqMap.containsKey(key)) {
                        do {
                            key = getReqInfoKey(rrInfo, ++counter);
                        } while (reqMap.containsKey(key));
                        counter = 0;
                    }
                    reqMap.put(key, rrInfo);
                }
            }
            // Set a forward link for all packets in session to the next packet (within the session).
            // The last packet in session will not link anywhere of course!
            List<PacketInfo> packets = session.getPackets();
            for (int i = 0; i < packets.size() - 1; i++) {
                packets.get(i).getPacket().setNextPacketInSession(packets.get(i + 1).getPacket());
            }
        }
        return reqMap;
    }

    /*
     * build key for storing requests in order with tie-breaker support
     */
    private Long getReqInfoKey(HttpRequestResponseInfo rrInfo, int counter) {
        long key = Math.round(rrInfo.getTimeStamp() * 1000000 + counter);
        return key;
    }

    private String getDebugPath() {
        return videoPath;
    }

    private String getImagePath() {
        return imagePath;
    }

    /**
     * Parse filename out of URI in HttpRequestResponseInfo
     *
     * @param rrInfo
     *            HttpRequestResponseInfo
     * @return
     */
    private String extractFullNameFromRRInfo(HttpRequestResponseInfo rrInfo) {
        String URI = rrInfo.getObjNameWithoutParams();
        int pos = URI.lastIndexOf("/");
        String fullName = URI.substring(pos + 1, URI.length());
        return fullName;
    }

    /**
     * <pre>
     * if and extention then Match on name and exten, then append. else return
     * all after last '/'
     *
     * @param rrInfo
     * @return
     */
    private String extractNameFromRRInfo(HttpRequestResponseInfo rrInfo) {
        String[] results = null;
        try {
            results = stringParse.parse(rrInfo.getFileName(), "([a-zA-Z0-9\\-]*)[_\\.]");
            if (results == null || results.length == 0) {
                String fullName = extractFullNameFromRRInfo(rrInfo);
                int pos = fullName.indexOf('_');
                return pos == -1 ? fullName : fullName.substring(0, pos);
            }
            return results[0];
        } catch (Exception e) {
            log.error("Exception :" + e.getMessage());
        }
        StringBuilder name = new StringBuilder("");
        if (results != null) {
            for (String part : results) {
                name.append(part);
            }
        } else {
            return "unknown";
        }
        return name.toString();
    }

    /**
     * Returns string from the target to the end of the string
     *
     * @param src
     * @param target
     * @return
     */
    private String truncateString(String src, String target) {
        int pos = src.indexOf(target);
        if (pos > -1) {
            return src.substring(pos);
        }
        return src;
    }

    /**
     * Locate and return extension from filename
     *
     * @param src
     * @return String extension with the dot(.)
     */
    private String extractExtensionFromName(String src) {
        String[] matched = stringParse.parse(src, extensionPattern);
        if (matched != null && !matched[0].isEmpty()) {
            return matched[0];
        }
        String extension = null;
        int pos = src.lastIndexOf('.');
        extension = (pos == -1 ? null : src.substring(pos));
        return extension;
    }

    /**
     * Extract a video from traffic data
     *
     * @param request
     * @param session
     * @param vManifest
     * @param fileName
     */
    public void extractVideo(HttpRequestResponseInfo request, AROManifest vManifest, String fileName) {
        HttpRequestResponseInfo response = request.getAssocReqResp();
        log.info("-------");
        log.info(request.getObjUri().toString());
        log.info(request.getAllHeaders());
        log.info(request.getAssocReqResp().getAllHeaders());
        log.elevatedInfo(String.format("extractVideo %.4f", request.getTimeStamp()));
        String quality = "";
        ArrayList<ByteRange> rangeList = new ArrayList<>();
        double bitrate = 0;
        double segment = 0;
        double duration = 0;

        String fullName = extractFullNameFromRRInfo(request);
        byte[] content = null;
        String fullpath;

        VideoEventData ved = parseRequestResponse(request);
        if (ved.getSegment() == null && ved.getSegmentReference() == null) {
            log.elevatedInfo("parse failure :" + request.getObjUri().toString() + ved);
            ved.setSegment(-4);
        }
        VideoFormat vFormat = findVideoFormat(ved.getExtension());

        log.debug("aroManifest :" + vManifest);

        try {
            content = extractContent(request);
            if (content.length == 0) {
                return;
            }
            vManifest = matchManifest(request, vManifest, ved, vFormat);
            vManifest.adhocSegment(ved);
        } catch (Exception e) {
            videoUsage.addFailedRequestMap(request);
            log.debug("Failed to extract " + getTimeString(response) + fileName + ", range: " + ved.getByteRange()
                    + ", error: " + e.getMessage());
            return;
        }
        quality = ved.getQuality() == null ? "unknown" : ved.getQuality();
        rangeList.add(ved.getByteRange());
        segment = ved.getSegment() != null && ved.getSegment() >= 0 ? ved.getSegment()
                : vManifest.parseSegment(fullName, ved);
        // set format handled by aroManifest
        if (VideoFormat.UNKNOWN.equals(vManifest.getVideoFormat())) {
            vManifest.setVideoFormat(vFormat);
        }
        double segmentStartTime = 0;
        if (vManifest instanceof ManifestDash) {
            bitrate = ((ManifestDash) vManifest).getBandwith(truncateString(fullName, "_"));
            timeScale = vManifest.getTimeScale();
        } else if (vManifest instanceof ManifestDashPlayReady) {
            if (segment > 0) {
                bitrate = vManifest.getBitrate(ved.getQuality());
                timeScale = vManifest.getTimeScale();
            }
        } else if (vManifest instanceof ManifestHLS) {
            if (ved.getDuration() != null) {
                try {
                    duration = Double.valueOf(ved.getDuration());
                } catch (NumberFormatException e) {
                    duration = 0;
                }
            }
            if (duration == 0) {
                try {
                    duration = Double.valueOf(vManifest.getDuration((int) segment));
                } catch (NumberFormatException e) {
                    duration = 0;
                }
                if (duration == 0) {
                    duration = vManifest.getDuration();
                }
            }
            timeScale = 1;
            try {
                Double temp = vManifest.getBitrate(quality);
                if (temp == null) {
                    temp = vManifest.getBitrate("HLS" + quality);
                }
                bitrate = temp != null ? temp : 0D;
            } catch (Exception e) {
                log.info("invalid quality :" + quality);
            }
        } else {
            // TODO some other way
        }
        log.debug("trunk " + fileName + ", getTimeString(response) :" + getTimeString(response));
        byte[] thumbnail = null;
        Integer[] segmentMetaData = new Integer[2];
        String segName = null;
        segmentMetaData[0] = content.length;
        if (vManifest instanceof ManifestDash) {
            segmentMetaData = parsePayload(content);
        } else if (vManifest instanceof ManifestHLS) {
            segmentStartTime = 0;
        }
        if (segment < 0 && segmentMetaData[1] != null) {
            segment = segmentMetaData[1];
        }
        fullpath = constructDebugName(request, ved);
        log.debug(fileName + ", content.length :" + content.length);
        if (segment == 0 && vManifest.isVideoType(VideoType.DASH)) {
            VideoData vData = new VideoData(vManifest.getEventType(), quality, content);
            vManifest.addVData(vData);
        } else {
            String seg = String.format("%08d", ((Double) segment).intValue());
            segName = getDebugPath() + seg + '_' + ved.getId() + '.' + ved.getExtension();
            thumbnail = extractThumbnail(vManifest, content, ved);
        }
        try {
            filemanager.saveFile(new ByteArrayInputStream(content), fullpath);
        } catch (IOException e) {
            log.error("Failed to save segment " + getTimeString(response) + fileName + ", range: "
                    + ved.getByteRange() + ", error: " + e.getMessage());
            return;
        }
        TreeMap<String, Double> metaData = null;
        if (thumbnail != null) {
            metaData = extractMetadata(segName);
            if (metaData != null) {
                Double val = metaData.get("bitrate");
                if (val != null) {
                    bitrate = val;
                }
                val = metaData.get("Duration");
                if (val != null) {
                    duration = val;
                }
                val = metaData.get("SegmentStart");
                if (val != null) {
                    segmentStartTime = val;
                }
            }
        }
        if (segment > 0) {
            segmentStartTime = segmentMetaData[1] != null ? segmentMetaData[1].doubleValue() / timeScale : 0;
        }
        // a negative value indicates segment startTime
        // later will scan over segments & set times based on next segmentStartTime
        if (vManifest instanceof ManifestDash || duration == 0) {
            duration -= segmentStartTime;
        }
        if (thumbnail == null) {
            thumbnail = altImage;
        }
        VideoEvent vEvent = new VideoEvent(ved, thumbnail, vManifest, segment, quality, rangeList, bitrate,
                duration, segmentStartTime, segmentMetaData[0], response);
        vManifest.addVideoEvent(segment, response.getTimeStamp(), vEvent);

        aroManifest = vManifest;
    }

    private VideoFormat findVideoFormat(String extension) {
        if ("mp4".equals(extension) || "mpeg4".equals(extension) || extension.startsWith("m4")) {
            return VideoFormat.MPEG4;
        } else if ("ts".equals(extension)) {
            return VideoFormat.TS;
        }
        return VideoFormat.UNKNOWN;
    }

    /**
     * @param request
     * @return
     */
    private VideoEventData parseRequestResponse(HttpRequestResponseInfo request) {
        String[] voValues;
        vConfig = voConfigHelper.findConfig(request.getObjUri().toString());
        VideoEventData ved = null;
        if (vConfig != null) {
            log.info(String.format("vConfig :%s", vConfig));
            voValues = voConfigHelper.match(vConfig, request.getObjUri().toString(), request.getAllHeaders(),
                    request.getAssocReqResp().getAllHeaders());
            ved = voEventDataHelper.create(vConfig, voValues);
            log.info(ved.toString());
        } else {
            ved = voEventDataHelper.create(extractNameFromRRInfo(request),
                    extractExtensionFromName(extractFullNameFromRRInfo(request)));
            ved = voEventDataHelper.create(extractNameFromRRInfo(request),
                    extractExtensionFromName(request.getFileName()));
        }
        return ved;
    }

    @SuppressWarnings("unused")
    private AROManifest locateManifestForVideo(AROManifest aroManifest, VideoAnalysisConfig vConfig,
            VideoEventData ved, HttpRequestResponseInfo request) {
        log.debug(String.format("diff :%s != %s\n", ved.getId(), aroManifest.getVideoName()));
        for (AROManifest manifest : aroManifestMap.values()) {
            if (ved.getId().replaceAll("\\.", "_").equals(aroManifest.getVideoName())) {
                return manifest;
            }
        }
        AROManifest manifest = null;
        if (vConfig == null) {
            manifest = new AROManifest(VideoType.UNKNOWN, request, videoPath);
        } else if (vConfig.getVideoType().equals(VideoType.DASH)) {
            manifest = new ManifestDash(null, request, videoPath);
        } else {
            manifest = new ManifestHLS(request, videoPath);
        }
        manifest.singletonSetVideoName(ved.getId());
        videoUsage.add(request.getTimeStamp(), manifest);
        return manifest;
    }

    private String constructDebugName(HttpRequestResponseInfo request, VideoEventData ved) {
        String fullpath;
        StringBuffer fname = new StringBuffer(getDebugPath());
        fname.append(ved.getId());
        fname.append(String.format("_%08d", ved.getSegment()));
        if (ved.getByteRange() != null) {
            fname.append("_R_");
            fname.append(ved.getByteRange());
        }
        fname.append("_dl_");
        fname.append(getTimeString(request.getAssocReqResp()));
        fname.append("_S_");
        fname.append(String.format("%08.0f", request.getSession().getSessionStartTime() * 1000));
        fname.append("_Q_");
        fname.append(ved.getQuality());
        fname.append('.');
        fname.append(ved.getExtension());
        fullpath = fname.toString();
        return fullpath;
    }

    /**<PRE>
     * Creates a TreeMap with keys:
     *    bitrate
     *    Duration
     *    SegmentStart
     *
     * @param srcpath
     * @return
     */
    private TreeMap<String, Double> extractMetadata(String srcpath) {
        TreeMap<String, Double> results = new TreeMap<>();
        String cmd = Util.getFFMPEG() + " -i " + "\"" + srcpath + "\"";
        String lines = extrunner.executeCmd(cmd);
        if (lines.indexOf("No such file") == -1) {
            double bitrate = getBitrate("bitrate: ", lines);
            results.put("bitrate", bitrate);
            Double duration = 0D;
            String[] time = StringParse.findLabeledDataFromString("Duration: ", ",", lines).split(":"); // 00:00:05.80
            Double start = StringParse.findLabeledDoubleFromString(", start:", ",", lines); // 2.711042
            if (time.length == 3) {
                try {
                    duration = Integer.parseInt(time[0]) * 3600 + Integer.parseInt(time[1]) * 60
                            + Double.parseDouble(time[2]);
                } catch (NumberFormatException e) {
                    log.error("failed to parse duration from :"
                            + StringParse.findLabeledDataFromString("Duration: ", ",", lines));
                    duration = 0D;
                }
            } else if (time.length > 0) {
                try {
                    duration = Double.parseDouble(time[0]);
                } catch (NumberFormatException e) {
                    log.error("failed to parse duration from :" + time[0]);
                    duration = 0D;
                }
            }
            results.put("Duration", duration);
            results.put("SegmentStart", start);
        }
        return results;
    }

    /**
     * verify video belongs with a manifest
     *
     * @param request
     * @param aroManifest
     * @param vFormat
     * @param content
     * @return correct AroManifest
     */
    private AROManifest matchManifest(HttpRequestResponseInfo request, AROManifest aroManifest, VideoEventData ved,
            VideoFormat vFormat) {
        HttpRequestResponseInfo response = request.getAssocReqResp();
        String videoName = "";
        AROManifest newManifest = null;
        //FIXME need to update if reconstructed manifest ( not yet updated by a real manifest )
        if (aroManifest == null) {
            newManifest = videoUsage.findVideoInManifest(ved.getId());
            if (newManifest == null) {
                if (ved.getId().contains("_video_")) {
                    newManifest = new ManifestDash(null, request, videoPath);
                    videoUsage.add(request.getTimeStamp(), newManifest);
                } else if (ved.getId().contains(".ts")
                        || ved.getExtension().equalsIgnoreCase(VideoFormat.TS.toString())) {
                    newManifest = new ManifestHLS(request, ved, videoPath);
                    videoUsage.add(request.getTimeStamp(), newManifest);
                } else {
                    // TODO - future plans - create and use class ManifestUnknown instead
                    newManifest = new AROManifest(VideoType.UNKNOWN, request, ved, videoPath);
                    videoUsage.add(request.getTimeStamp(), newManifest);
                }
            } else {
                newManifest = new AROManifest(VideoType.UNKNOWN, response, ved, videoPath);
                videoUsage.add(request.getTimeStamp(), newManifest);
                ved.setSegment(0);
                ved.setQuality("unknown");
            }
            newManifest.singletonSetVideoName(ved.getId());
            videoUsage.add(request.getTimeStamp(), newManifest);
        } else {
            newManifest = aroManifest;
        }
        videoName = newManifest.getVideoName();
        // if (videoName != null && !videoName.isEmpty() && ved.getId() != null && !ved.getId().contains(videoName)) {
        if (!ved.getId().equals(videoName)) {
            log.info(String.format("%s -> %s", videoName, ved.getId()));
            newManifest = videoUsage.findVideoInManifest(ved.getId());
            if (!newManifest.getVideoFormat().equals(VideoFormat.UNKNOWN)
                    && !newManifest.getVideoFormat().equals(vFormat)) {
                newManifest = new AROManifest(VideoType.UNKNOWN, response, videoPath);
                ved.setSegment(0);
                ved.setQuality("unknown");
                videoUsage.add(request.getTimeStamp(), newManifest);
            }
        }
        return newManifest;
    }

    /**
     * Parse mp4 chunk/segment that contains one moof and one mdat.
     *
     * @param content
     * @return double[] mdat payload length, time sequence
     */
    private Integer[] parsePayload(byte[] content) {
        byte[] buf = new byte[4];
        int mdatSize = 0;
        ByteBuffer bbc = ByteBuffer.wrap(content);
        // get moof size
        double moofSize = bbc.getInt();
        bbc.get(buf);
        String moofName = new String(buf);
        int timeSequence = 0;
        if (moofName.equals("moof")) {
            // skip past mfhd
            double mfhdSize = bbc.getInt();
            bbc.get(buf);
            String mfhdName = new String(buf);
            if (mfhdName.equals("mfhd")) {
                bbc.position((int) mfhdSize + bbc.position() - 8);
                // parse into traf
                // double trafSize =
                bbc.getInt(); // skip over
                bbc.get(buf);
                String trafName = new String(buf);
                if (trafName.equals("traf")) {
                    // skip tfhd
                    double tfhdSize = bbc.getInt();
                    bbc.get(buf);
                    String tfhdName = new String(buf);
                    if (tfhdName.equals("tfhd")) {
                        // skip past this atom
                        bbc.position((int) tfhdSize + bbc.position() - 8);
                    }
                    // parse tfdt
                    // double tfdtSize =
                    bbc.getInt(); // skip over
                    bbc.get(buf);
                    String tfdtName = new String(buf);
                    if (tfdtName.equals("tfdt")) {
                        bbc.getInt(); // skip over always 16k
                        bbc.getInt(); // skip over always 0
                        timeSequence = bbc.getInt();
                    }
                }
            }
        } else {
            return new Integer[] { 0, 0 };
        }
        // parse mdat
        bbc.position((int) moofSize);
        mdatSize = bbc.getInt();
        bbc.get(buf, 0, 4);
        String mdatName = new String(buf);
        if (mdatName.equals("mdat")) {
            mdatSize -= 8;
        } else {
            mdatSize = 0;
        }
        return new Integer[] { mdatSize, timeSequence };
    }

    /**
     * Extract a Thumbnail image from the first frame of a video
     *
     * @param aroManifest
     * @param content
     *
     * @param srcpath
     * @param segmentName
     * @param quality
     * @param videoData
     * @return
     */
    private byte[] extractThumbnail(AROManifest aroManifest, byte[] content, VideoEventData ved) {

        String //segName = getDebugPath() + String.format("%08d", ved.getIntSegment()) + '_' + ved.getId() + '.' + ved.getExtension();
        segName = (new StringBuilder(getDebugPath())).append(String.format("%08d", ved.getSegment())).append('_')
                .append(ved.getId()).append('.').append(ved.getExtension()).toString();

        byte[] data = null;
        filemanager.deleteFile(ved.getId());
        byte[] movie = null;
        if (aroManifest.isVideoType(VideoType.DASH)) {
            VideoData vData = aroManifest.getVData(ved.getQuality());
            if (vData == null) {
                return null;
            } // join mbox0 with segment
            byte[] mbox0 = vData.getContent();
            movie = new byte[mbox0.length + content.length];
            System.arraycopy(mbox0, 0, movie, 0, mbox0.length);
            System.arraycopy(content, 0, movie, mbox0.length, content.length);
        } else {
            movie = content;
        }
        try {
            filemanager.saveFile(new ByteArrayInputStream(movie), segName);
        } catch (IOException e1) {
            log.error("IOException:" + e1.getMessage());
        }
        data = extractVideoFrameShell(segName);
        if (data == null) {
            filemanager.deleteFile(segName);
        }
        return data;
    }

    private byte[] extractVideoFrameShell(String segmentName) {
        byte[] data = null;
        String thumbnail = getDebugPath() + "thumbnail.png";
        filemanager.deleteFile(thumbnail);
        String cmd = Util.getFFMPEG() + " -y -i " + "\"" + segmentName + "\"" + " -ss 00:00:00   -vframes 1 " + "\""
                + thumbnail + "\"";
        String ff_lines = extrunner.executeCmd(cmd);
        log.debug("ff_lines :" + ff_lines);
        if (filemanager.fileExist(thumbnail)) {
            Path path = Paths.get(thumbnail);
            try {
                data = Files.readAllBytes(path);
            } catch (IOException e) {
                log.debug("getThumnail IOException:" + e.getMessage());
            }
        }
        return data;
    }

    /**
     * <pre>
     * get a bitrate where the raw data will have a value such as: 150 kb/s 150 mb/s
     *
     * @param key
     * @param ff_lines
     * @return
     */
    private double getBitrate(String key, String ff_lines) {
        double bitrate = 0;
        String valbr = getValue(key, "\n", ff_lines);
        if (valbr != null) {
            String[] temp = valbr.split(" ");
            try {
                bitrate = Double.valueOf(temp[0]);
            } catch (NumberFormatException e) {
                log.debug("Bit rate not available for key:" + key);
                return 0;
            }
            if (temp[1].equals("kb/s")) {
                bitrate *= 1024;
            } else if (temp[1].equals("mb/s")) {
                bitrate *= 1048576;
            }
        }
        return bitrate;
    }

    /**
     * Get the value following the key up to the delimiter. return null if not found
     *
     * @param key
     * @param delimeter
     * @param ff_lines
     * @return value or null if no key found
     */
    private String getValue(String key, String delimeter, String ff_lines) {
        String val = null;
        int pos1 = ff_lines.indexOf(key);
        if (pos1 > -1) {
            pos1 += key.length();
            int pos2 = ff_lines.substring(pos1).indexOf(delimeter);
            if (pos2 == -1 || pos2 == 0) {
                val = ff_lines.substring(pos1);
            } else {
                val = ff_lines.substring(pos1, pos1 + pos2);
            }
        }
        return val;
    }

    /**
     * Obtain timestamp from request formated into a string. Primarily for debugging purposes.
     *
     * @param response
     * @return
     */
    private String getTimeString(HttpRequestResponseInfo response) {
        StringBuffer strTime = new StringBuffer();
        try {
            if (absTimeFlag) {
                Packet packet = response.getFirstDataPacket().getPacket(); // request
                strTime.append(String.format("%d.%06d", packet.getSeconds(), packet.getMicroSeconds()));
            } else {
                strTime.append(String.format("%09.0f", (float) response.getTimeStamp() * 1000));
            }
        } catch (Exception e) {
            log.error("Failed to get time from request: " + e.getMessage());
            strTime.append("Failed to get time from response->request: " + response);
        }
        return strTime.toString();
    }

    /**
     * <pre>
     * Extract a HLS manifest from traffic data
     *
     * Types:
     *  * movie
     *  * livetv
     *
     * @param request
     * @param session
     *            that the response belongs to.
     * @return AROManifest
     */
    public AROManifest extractManifestHLS(HttpRequestResponseInfo request) {
        HttpRequestResponseInfo response = request.getAssocReqResp();
        VideoEventData ved = parseRequestResponse(request);
        log.elevatedInfo(String.format("extractManifestHLS %.4f", request.getTimeStamp()));
        if (ved.getName() == null) {
            ved.setName(regexNameFromRequestHLS(request));
        }

        byte[] content = null;
        try {
            content = extractContent(request);
            if (content == null || content.length == 0) {
                return aroManifest;
            }

            log.info("Manifest content.length :" + content.length);
            StringBuffer fname = new StringBuffer(getDebugPath());
            fname.append(getTimeString(response));
            fname.append('_');
            fname.append(ved.getName());
            fname.append("_ManifestHLS.m3u8");
            filemanager.saveFile(new ByteArrayInputStream(content), fname.toString());

        } catch (Exception e) {
            videoUsage.addFailedRequestMap(request);
            log.error("Failed to get content from DTV Manifest; response: " + e.getMessage());
        }

        String reqName = request.getObjNameWithoutParams();
        // return if identical content, don't parse it again
        if (aroManifest != null && !aroManifest.checkContent(content)) {
            return aroManifest;
        }

        log.info(reqName);
        String sanityName = reqName.replaceAll("\\.", "_");
        if (aroManifest == null
                || (!"playlist".equals(ved.getName()) && !ved.getName().equals(aroManifest.getVideoName()))
                || (!sanityName.contains(aroManifest.getVideoName())
                        && !ved.getName().equals(aroManifest.getVideoName()))
                || (reqName.contains("channel") && !sanityName.contains(aroManifest.getVideoName()))
                || (reqName.contains("livetv") && reqName.contains("latest"))
                || (reqName.contains("NFL") && reqName.contains("latest")) // /NFL/10/000573/03/latest.m3u8
                || (reqName.contains("NFL") && !reqName.contains("_") && !reqName.contains("playlist.m3u8")) // /NFL/10/000573/03/playlist.m3u8
        ) {
            // NFL - to this point
            ManifestHLS manifest = null;
            try {
                manifest = new ManifestHLS(response, ved, content, videoPath);
                // manifest.setDelay(videoUsagePrefs.getStartupDelay());
                log.info("aroManifest :" + aroManifest);

                videoUsage.add(request.getTimeStamp(), manifest);
                return manifest;

            } catch (Exception e) {
                log.error("Failed to parse manifest data, absTimeStamp:" + request.getAbsTimeStamp().getTime()
                        + ", Name:" + reqName);
            }

        } else {
            try {
                ((ManifestHLS) aroManifest).parseManifestData(content);
                log.elevatedInfo("aroManifest parsed in data:" + aroManifest);
            } catch (Exception e) {
                log.error("Failed to parse manifest data into ManifestHLS:" + e.getMessage());
            }
        }
        return aroManifest;
    }

    /**
     * @param request
     * @param response
     * @param session
     * @return
     * @throws Exception
     */
    private byte[] extractContent(HttpRequestResponseInfo request) throws Exception {
        byte[] content;
        try {
            content = reqhelper.getContent(request.getAssocReqResp(), request.getSession());
        } catch (Exception e) {
            content = null;
        }
        if (content == null || content.length == 0) {
            videoUsage.addFailedRequestMap(request);
        } // else {
        videoUsage.addRequest(request);
        // }
        return content;
    }

    private String regexNameFromRequestHLS(HttpRequestResponseInfo request) {
        String videoName = null;

        // TODO future create regex files for manifest matching like with video segments
        String regex[] = { "\\/NFL\\/\\d+\\/(\\d+)\\/latest\\.m3u8" //  /NFL/10/000573/latest.m3u8
                , "livetv\\/\\d+\\/([a-zA-Z0-9]*)\\/latest\\.m3u8" //  /livetv/30/8249/latest.m3u8
                , "livetv\\/\\d+\\/([a-zA-Z0-9]*)\\/\\d+\\/playlist\\.m3u8" //  /livetv/30/8249/03/playlist.m3u8
                , "\\/aav\\/.+\\/([A|B]\\d+U)\\d\\.m3u8" //  /aav/30/B001573958U3/B001573958U3.m3u8
                , "\\/aav\\/.+\\/HLS\\d\\/([A|B]\\d+U)\\d\\_\\d.m3u8" //  /aav/30/B001844891U3/HLS2/B001844891U0_2.m3u8
                , "\\/aav\\/.+\\/WebVTT\\d\\/([A|B]\\d+U)\\d\\_\\d.m3u8" //  /aav/30/B001844891U3/WebVTT1/B001844891U0_7.m3u8
                , "\\/movie\\/.+\\/([A|B]\\d+U)\\d\\.m3u8" //  /c3/30/movie/2016_12/B002021484/B002021484U3/B002021484U3.m3u8
                , "\\/channel\\((.+)\\)\\/\\d+.m3u8" //  /Content/HLS_hls.pr/Live/channel(FNCHD.gmott.1080.mobile)/05.m3u8
                , "\\/channel\\((.+)\\)\\/index.m3u8" //  /Content/HLS_hls.pr/Live/channel(FNCHD.gmott.1080.mobile)/index.m3u8
                , "AEG.+\\/(AEG).+index.m3u8" //  /seg/vol2/s/AEG_CP/dyjr1008967500000002/2017-03-24-13-11-31/DYJR1008967500000003/AEG_01_128/AEG_01_128-index.m3u8
                , "AEG.+\\/(AUD).+index.m3u8" //  /seg/vol2/s/AEG_CP/dyjr1008967500000002/2017-03-24-13-11-31/DYJR1008967500000003/AUD_01_128/AEG_01_128-index.m3u8
                , "\\/([a-zA-Z0-9]*).m3u8" //  /B002021484U3.m3u8
        };

        String[] results = null;
        String tempName = request.getObjNameWithoutParams();
        for (int i = 0; i < regex.length; i++) {
            results = stringParse.parse(tempName, regex[i]);
            if (results != null) {
                videoName = results[0].replaceAll("\\.", "_");
                return videoName;
            }
        }
        if (results == null) {
            videoName = "unknown";
        }
        return videoName;
    }

    /**
     * Extract a DASH manifest from traffic data
     *
     * @param request
     * @param session
     *            session that the response belongs to.
     * @return AROManifest
     * @throws java.lang.Exception
     */
    public AROManifest extractManifestDash(HttpRequestResponseInfo request) {
        HttpRequestResponseInfo response = request.getAssocReqResp();

        VideoEventData ved = parseRequestResponse(request);
        log.elevatedInfo(String.format("extractManifestDash %.4f", request.getTimeStamp()));
        byte[] content = null;
        try {
            content = extractContent(request);
            if (content == null || content.length == 0) {
                return aroManifest;
            }
            videoUsage.addRequest(request);
            log.info("Manifest content.length :" + content.length);

        } catch (Exception e) {
            videoUsage.addFailedRequestMap(request);
            log.error("Failed to parse manifest data, absTimeStamp:" + request.getAbsTimeStamp().getTime()
                    + ", Name:" + request.getObjNameWithoutParams());
            return null;
        }
        XmlManifestHelper mani = new XmlManifestHelper(content);
        ved.setManifestType(mani.getManifestType().toString());
        // debug - save to debug folder
        saveManifestFile(request, ved, content);
        AROManifest manifest = null;
        if (mani.getManifestType().equals(XmlManifestHelper.ManifestFormat.MPD_PlayReady)) {
            if (aroManifest != null && ((lastContentType != null && lastContentType.equals(ved.getContentType()))
                    || ved.getName().equals(aroManifest.getVideoName()))) {
                aroManifest.updateManifest(mani.getManifest());
                log.info("created new manifest :" + manifest);
                return aroManifest;
            } else {
                lastContentType = ved.getContentType();
                manifest = new ManifestDashPlayReady((MPDPlayReady) mani.getManifest(), response, videoPath);
            }
        } else if (mani.getManifestType().equals(XmlManifestHelper.ManifestFormat.SmoothStreamingMedia)) {
            manifest = new ManifestSSM((SSMAmz) mani.getManifest(), response, videoPath);
        } else {
            if (aroManifest != null && aroManifest.getVideoName().equals(ved.getName())) {
                log.info("Deactivate :" + aroManifest);
                aroManifest.setActiveState(false);
            }
            manifest = new ManifestDash((MPDAmz) mani.getManifest(), response, videoPath);
            log.info("created new manifest :" + manifest);
        }

        if (manifest != null) {
            videoUsage.add(request.getTimeStamp(), manifest);
        }

        return manifest;
    }

    /**
     * Saves byte[] to a file
     *
     * @param request
     * @param ved
     * @param content
     * @throws IOException
     */
    private void saveManifestFile(HttpRequestResponseInfo request, VideoEventData ved, byte[] content) {// throws IOException {
        StringBuffer fname = new StringBuffer(getDebugPath());
        String temp = ved.getManifestType() != null ? ved.getManifestType() : extractNameFromRRInfo(request);
        fname.append(temp.equals("manifest") ? "_SSM_manifest" : temp);
        fname.append("__");
        fname.append(getTimeString(request.getAssocReqResp()));
        if (request.getObjNameWithoutParams().endsWith("manifest")) {
            fname.append("_SSMedia.xml");
        } else {
            fname.append("_ManifestDash.mpd");
        }
        String path = fname.toString();
        try {
            filemanager.saveFile(new ByteArrayInputStream(content), path);
        } catch (IOException e) {
            log.error("Failed to save Manifest :" + path + ", error :" + e.getMessage());
        }
    }

    private byte[] fileToByteArray(File file) {
        byte[] content = new byte[(int) file.length()];
        FileInputStream fileInputStream;
        try {
            fileInputStream = new FileInputStream(file);
            fileInputStream.read(content);
        } catch (IOException e) {
            log.error("File to byte array conversion exception" + e.getMessage());
        }
        return content;
    }

    public void extractImage(HttpRequestResponseInfo request, AROManifest aroManifest, String imageFileName) {
        HttpRequestResponseInfo response = request.getAssocReqResp();
        Session session = request.getSession();
        if (response != null) {
            byte[] content = null;
            String fullpath;
            try {
                content = reqhelper.getContent(response, session);
                fullpath = getImagePath() + imageFileName;
                filemanager.saveFile(new ByteArrayInputStream(content), fullpath);
            } catch (Exception e) {
                videoUsage.addFailedRequestMap(request);
                log.info("Failed to extract " + getTimeString(response) + imageFileName + " response: "
                        + e.getMessage());
                return;
            }
        }
    }

    @Override
    public VideoUsage getVideoUsage() {
        return videoUsage;
    }

    @Override
    public TreeMap<Long, HttpRequestResponseInfo> getReqMap() {
        return reqMap;
    }

    /**
     * Iterate videoUsage, flagging manifest as invalid if no segments or segment number is less than 0
     * @param videoUsage
     * @return
     */
    public VideoUsage identifyInvalidManifest(VideoUsage videoUsage) {
        if (videoUsage != null) {
            for (AROManifest manifest : videoUsage.getManifests()) {
                if (manifest != null) {
                    boolean selected = manifest.getVideoEventsBySegment() != null ? true : false;
                    if (!selected) {
                        manifest.setSelected(false);
                        continue;
                    }
                    if (manifest.getVideoEventsBySegment().isEmpty()
                            || ((VideoEvent) manifest.getVideoEventsBySegment().toArray()[0]).getSegment() < 0) {
                        manifest.setSelected(false);
                    }
                }
            }
        }
        return videoUsage;
    }
}// end class