diff --git a/src/ffmpeg_api/api.rs b/src/ffmpeg_api/api.rs
index b1de9f291250d3ad46d5381bf67ac960a73578d6..a5ab7c27b7e4fea8c7420ca749d805deafea06bc 100644
--- a/src/ffmpeg_api/api.rs
+++ b/src/ffmpeg_api/api.rs
@@ -5,7 +5,7 @@ use std::marker::PhantomData;
 use fraction::Fraction;
 
 use crate::ffmpeg_api::enums::*;
-use crate::media_time;
+use crate::util::media_time;
 
 pub struct AVFormatContext {
     base: *mut ffi::AVFormatContext,
@@ -293,11 +293,11 @@ impl<'a> AVStream<'a> {
         )
     }
 
-    pub fn timestamp(self: &AVStream<'a>, timestamp: i64) -> media_time::MediaTime {
+    pub fn timestamp(self: &AVStream<'a>, timestamp: i64) -> Result<media_time::MediaTime, failure::Error> {
         media_time::MediaTime::from_rational(timestamp, self.time_base())
     }
 
-    pub fn duration(&self) -> media_time::MediaTime {
+    pub fn duration(&self) -> Result<media_time::MediaTime, failure::Error> {
         self.timestamp(self.base.duration)
     }
 
diff --git a/src/main.rs b/src/main.rs
index df06960d065567667e1b58f8d37d35f1b8f80a4a..10f2842bec690c67173c0d534da92ee9e7a61c86 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,140 +1,18 @@
 #![allow(dead_code)]
 
 pub(crate) mod ffmpeg_api;
-pub(crate) mod webvtt;
-pub(crate) mod media_time;
-pub(crate) mod spritesheet;
+pub(crate) mod thumbnail;
+pub(crate) mod util;
 
-use crate::ffmpeg_api::enums::*;
-use crate::ffmpeg_api::api::*;
-use crate::media_time::MediaTime;
+use crate::util::media_time::MediaTime;
 
-fn main() -> Result<(), std::io::Error> {
-    let mut before = std::time::SystemTime::now();
-
-    let input = "/home/janne/Workspace/justflix/data/video.mp4";
-    let output = "/home/janne/Workspace/justflix/data/spritesheets";
-
-    let mut avformat_context = AVFormatContext::new().unwrap_or_else(|error| {
-        panic!("Could not allocate a context to process the video: {:?}", error)
-    });
-    avformat_context.open_input(input).unwrap_or_else(|error| {
-        panic!("Could not open video input: {:?}", error)
-    });
-
-    let mut spritesheet_manager = spritesheet::SpritesheetManager::new(
-        160,
-        5, 5,
+fn main() -> Result<(), failure::Error> {
+    thumbnail::extract::extract(
+        160, 5, 5,
         MediaTime::from_seconds(10),
-        output,
-    );
-
-    let mut stream: AVStream = avformat_context.find_stream(|stream| {
-        stream.codec_parameters().codec_type() == AVMediaType::Video
-    }).unwrap_or_else(|| {
-        panic!("Could not find video stream")
-    });
-
-    stream.set_discard(AVDiscard::NonKey);
-
-    let codec_parameters = stream.codec_parameters();
-    let local_codec = codec_parameters.find_decoder();
-
-    println!(
-        "Stream #{}, type: {:#?}, codec: {:#?}",
-        stream.index(),
-        codec_parameters.codec_type(),
-        local_codec.name()
-    );
-
-    let mut output_frame = AVFrame::new().unwrap_or_else(|error| {
-        panic!("Could not create output frame: {:?}", error)
-    });
-
-    if codec_parameters.codec_type() == AVMediaType::Video {
-        let mut codec_context = AVCodecContext::new(&local_codec).unwrap_or_else(|error| {
-            panic!("Could not init codec context: {:?}", error)
-        });
-        codec_context.set_parameters(&codec_parameters);
-        codec_context.open(&local_codec);
-
-        codec_context.set_skip_loop_filter(AVDiscard::NonKey);
-        codec_context.set_skip_idct(AVDiscard::NonKey);
-        codec_context.set_skip_frame(AVDiscard::NonKey);
-
-        let mut packet = AVPacket::new().unwrap_or_else(|error| {
-            panic!("Could not init temporary packet: {:?}", error)
-        });
-
-        let mut frame = AVFrame::new().unwrap_or_else(|error| {
-            panic!("Could not create input frame: {:?}", error)
-        });
-
-        println!("Time: {:#?}", before.elapsed().unwrap());
-        before = std::time::SystemTime::now();
-
-        let mut scale_context = SwsContext::new();
-
-        while avformat_context.read_frame(&mut packet).is_ok() {
-            if packet.stream_index() == stream.index() {
-                codec_context.in_packet(&mut packet).unwrap_or_else(|error| {
-                    panic!("Could not load packet: {:?}", error)
-                });
-                while codec_context.out_frame(&mut frame).is_ok() {
-                    println!(
-                        "Frame {}: {} @ {}",
-                        frame.coded_picture_number(),
-                        stream.timestamp(frame.pts()),
-                        frame.key_frame()
-                    );
-                    println!("Reading Time: {:#?}", before.elapsed().unwrap());
-                    before = std::time::SystemTime::now();
-
-                    if spritesheet_manager.fulfils_frame_interval(stream.timestamp(frame.pts())) {
-                        if !spritesheet_manager.initialized() {
-                            spritesheet_manager.initialize(frame.width() as u32, frame.height() as u32);
-                            output_frame.init(
-                                spritesheet_manager.sprite_width() as i32,
-                                spritesheet_manager.sprite_height() as i32,
-                                AVPixelFormat::RGB24,
-                            ).unwrap_or_else(|error| {
-                                panic!("Could not init output frame: {:?}", error)
-                            });
-                            scale_context.reinit(
-                                &frame,
-                                &output_frame,
-                                SwsScaler::FastBilinear,
-                            ).unwrap_or_else(|error| {
-                                panic!("Could not reinit scale context: {:?}", error)
-                            });
-                        }
-
-                        scale_context.scale(&frame, &mut output_frame);
-
-                        println!("Processing Time: {:#?}", before.elapsed().unwrap());
-                        before = std::time::SystemTime::now();
-
-                        spritesheet_manager.add_image(
-                            stream.timestamp(frame.pts()),
-                            image::ImageBuffer::from_raw(
-                                output_frame.width() as u32,
-                                output_frame.height() as u32,
-                                output_frame.data(0).to_vec(),
-                            ).unwrap_or_else(|| {
-                                panic!("Could not process frame")
-                            }),
-                        );
-
-                        println!("Writing Time: {:#?}", before.elapsed().unwrap());
-                        before = std::time::SystemTime::now();
-                    }
-                }
-            }
-        }
-
-        spritesheet_manager.end_frame(stream.duration());
-        spritesheet_manager.save();
-    }
+        "/home/janne/Workspace/justflix/data/video.mp4",
+        "/home/janne/Workspace/justflix/data/spritesheets"
+    )?;
 
     Ok(())
 }
diff --git a/src/thumbnail/extract.rs b/src/thumbnail/extract.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7ee06a8da2b5fe3c94f95dd7c8df353fac6f0d0c
--- /dev/null
+++ b/src/thumbnail/extract.rs
@@ -0,0 +1,126 @@
+use crate::ffmpeg_api::api::*;
+use crate::ffmpeg_api::enums::*;
+use crate::util::media_time::*;
+use crate::thumbnail::spritesheet::*;
+
+use failure::format_err;
+
+pub fn extract<T: AsRef<str>, U: AsRef<str>>(
+    max_size: u32,
+    num_horizontal: u32, num_vertical: u32,
+    frame_interval: MediaTime,
+    input_file: T,
+    output_folder: U,
+) -> Result<(), failure::Error> {
+    let mut avformat_context = AVFormatContext::new().map_err(|error| {
+        format_err!("Could not allocate a context to process the video: {:?}", error)
+    })?;
+    avformat_context.open_input(input_file.as_ref()).map_err(|error| {
+        format_err!("Could not open video input: {:?}", error)
+    })?;
+
+    let mut spritesheet_manager = SpritesheetManager::new(
+        max_size,
+        num_horizontal, num_vertical,
+        frame_interval,
+        output_folder,
+    );
+
+    let mut stream: AVStream = avformat_context.find_stream(|stream| {
+        stream.codec_parameters().codec_type() == AVMediaType::Video
+    }).ok_or_else(|| {
+        format_err!("Could not find video stream")
+    })?;
+
+    stream.set_discard(AVDiscard::NonKey);
+
+    let codec_parameters = stream.codec_parameters();
+    let local_codec = codec_parameters.find_decoder();
+
+    println!(
+        "Stream #{}, type: {:#?}, codec: {:#?}",
+        stream.index(),
+        codec_parameters.codec_type(),
+        local_codec.name()
+    );
+
+    let mut output_frame = AVFrame::new().map_err(|error| {
+        format_err!("Could not create output frame: {:?}", error)
+    })?;
+
+    if codec_parameters.codec_type() == AVMediaType::Video {
+        let mut codec_context = AVCodecContext::new(&local_codec).map_err(|error| {
+            format_err!("Could not init codec context: {:?}", error)
+        })?;
+        codec_context.set_parameters(&codec_parameters);
+        codec_context.open(&local_codec);
+
+        codec_context.set_skip_loop_filter(AVDiscard::NonKey);
+        codec_context.set_skip_idct(AVDiscard::NonKey);
+        codec_context.set_skip_frame(AVDiscard::NonKey);
+
+        let mut packet = AVPacket::new().map_err(|error| {
+            format_err!("Could not init temporary packet: {:?}", error)
+        })?;
+
+        let mut frame = AVFrame::new().map_err(|error| {
+            format_err!("Could not create input frame: {:?}", error)
+        })?;
+
+        let mut scale_context = SwsContext::new();
+
+        while avformat_context.read_frame(&mut packet).is_ok() {
+            if packet.stream_index() == stream.index() {
+                codec_context.in_packet(&mut packet).map_err(|error| {
+                    format_err!("Could not load packet: {:?}", error)
+                })?;
+                while codec_context.out_frame(&mut frame).is_ok() {
+                    println!(
+                        "Frame {}: {} @ {}",
+                        frame.coded_picture_number(),
+                        stream.timestamp(frame.pts())?,
+                        frame.key_frame()
+                    );
+
+                    if spritesheet_manager.fulfils_frame_interval(stream.timestamp(frame.pts())?) {
+                        if !spritesheet_manager.initialized() {
+                            spritesheet_manager.initialize(frame.width() as u32, frame.height() as u32);
+                            output_frame.init(
+                                spritesheet_manager.sprite_width() as i32,
+                                spritesheet_manager.sprite_height() as i32,
+                                AVPixelFormat::RGB24,
+                            ).map_err(|error| {
+                                format_err!("Could not init output frame: {:?}", error)
+                            })?;
+                            scale_context.reinit(
+                                &frame,
+                                &output_frame,
+                                SwsScaler::FastBilinear,
+                            ).map_err(|error| {
+                                format_err!("Could not reinit scale context: {:?}", error)
+                            })?;
+                        }
+
+                        scale_context.scale(&frame, &mut output_frame);
+
+                        spritesheet_manager.add_image(
+                            stream.timestamp(frame.pts())?,
+                            image::ImageBuffer::from_raw(
+                                output_frame.width() as u32,
+                                output_frame.height() as u32,
+                                output_frame.data(0).to_vec(),
+                            ).ok_or_else(|| {
+                                format_err!("Could not process frame")
+                            })?
+                        )?;
+                    }
+                }
+            }
+        }
+
+        spritesheet_manager.end_frame(stream.duration()?);
+        spritesheet_manager.save()?;
+    }
+
+    Ok(())
+}
\ No newline at end of file
diff --git a/src/thumbnail/mod.rs b/src/thumbnail/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..59bb3356ff31b492b41b552db2252d486c1442f2
--- /dev/null
+++ b/src/thumbnail/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod extract;
+pub(crate) mod spritesheet;
\ No newline at end of file
diff --git a/src/spritesheet.rs b/src/thumbnail/spritesheet.rs
similarity index 86%
rename from src/spritesheet.rs
rename to src/thumbnail/spritesheet.rs
index 1e0114abb1a17b7d50e4264e207c4efa60bc0ac8..1065df69f07e2a8824de9d294621f0e216816bb6 100644
--- a/src/spritesheet.rs
+++ b/src/thumbnail/spritesheet.rs
@@ -1,6 +1,8 @@
 use image::{RgbImage, ImageBuffer};
-use crate::media_time::MediaTime;
-use crate::webvtt::{WebVTTFile, WebVTTCue};
+use failure::{bail, format_err};
+
+use crate::util::media_time::MediaTime;
+use crate::util::webvtt::{WebVTTFile, WebVTTCue};
 
 pub struct SpritesheetManager {
     num_horizontal: u32,
@@ -88,9 +90,9 @@ impl SpritesheetManager {
         self.current_image == 0 || timestamp - self.last_timestamp > self.frame_interval
     }
 
-    pub fn add_image(&mut self, timestamp: MediaTime, image: RgbImage) {
+    pub fn add_image(&mut self, timestamp: MediaTime, image: RgbImage) -> Result<(), failure::Error> {
         if image.width() != self.sprite_width || image.height() != self.sprite_height {
-            panic!(
+            bail!(
                 "Wrong image size: {}x{}, but expected {}x{}",
                 image.width(), image.height(),
                 self.sprite_width, self.sprite_height
@@ -113,8 +115,10 @@ impl SpritesheetManager {
         self.current_image += 1;
 
         if self.sprite_index(self.current_image) == 0 {
-            self.save();
+            self.save()?;
         }
+
+        Ok(())
     }
 
     pub fn end_frame(&mut self, timestamp: MediaTime) {
@@ -132,19 +136,21 @@ impl SpritesheetManager {
         ));
     }
 
-    fn save_spritesheet(&mut self) {
+    fn save_spritesheet(&mut self) -> Result<(), failure::Error> {
         self.spritesheet.save(
             format!("{}/spritesheet_{}.png", self.output_path, self.spritesheet_index(self.current_image))
-        ).unwrap_or_else(|error| {
-            panic!("Could not write spritesheet: {}", error)
-        });
+        ).map_err(|error| {
+            format_err!("Could not write spritesheet: {}", error)
+        })?;
         self.reinit_buffer();
+        Ok(())
     }
 
-    pub fn save(&mut self) {
-        self.save_spritesheet();
-        self.metadata.save(format!("{}/spritesheet.vtt", self.output_path)).unwrap_or_else(|error| {
-            panic!("Could not write spritesheet metadata: {}", error)
-        });
+    pub fn save(&mut self) -> Result<(), failure::Error> {
+        self.save_spritesheet()?;
+        self.metadata.save(format!("{}/spritesheet.vtt", self.output_path)).map_err(|error| {
+            format_err!("Could not write spritesheet metadata: {}", error)
+        })?;
+        Ok(())
     }
 }
\ No newline at end of file
diff --git a/src/media_time.rs b/src/util/media_time.rs
similarity index 74%
rename from src/media_time.rs
rename to src/util/media_time.rs
index a5d07f22c3d7e009d162b07d508a09ed91548681..34396093f2d48336cfc36e956df2b04dfa6de03d 100644
--- a/src/media_time.rs
+++ b/src/util/media_time.rs
@@ -1,20 +1,21 @@
 use fraction::Fraction;
+use failure::format_err;
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct MediaTime(time::Duration);
 
 impl MediaTime {
-    pub fn from_rational(timestamp: i64, base: Fraction) -> MediaTime {
-        let num: u64 = *base.numer().unwrap_or_else(|| {
-            panic!("time base of unusable format")
-        });
-        let den: u64 = *base.denom().unwrap_or_else(|| {
-            panic!("time base of unusable format")
-        });
-
-        MediaTime(time::Duration::milliseconds(
+    pub fn from_rational(timestamp: i64, base: Fraction) -> Result<MediaTime, failure::Error> {
+        let num: u64 = *base.numer().ok_or_else(|| {
+            format_err!("time base of unusable format")
+        })?;
+        let den: u64 = *base.denom().ok_or_else(|| {
+            format_err!("time base of unusable format")
+        })?;
+
+        Ok(MediaTime(time::Duration::milliseconds(
             1000 * timestamp * num as i64 / den as i64
-        ))
+        )))
     }
 
     pub fn from_millis(timestamp: i64) -> MediaTime {
diff --git a/src/util/mod.rs b/src/util/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f6c6f2a78a72b7245ce465a7c047a29dcff45608
--- /dev/null
+++ b/src/util/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod webvtt;
+pub(crate) mod media_time;
\ No newline at end of file
diff --git a/src/webvtt.rs b/src/util/webvtt.rs
similarity index 96%
rename from src/webvtt.rs
rename to src/util/webvtt.rs
index 88deb4db1bf8dc8140ead83072424243ab402c4b..64f56034c7b0b92efbee8ff28ee9ea316cbb6790 100644
--- a/src/webvtt.rs
+++ b/src/util/webvtt.rs
@@ -1,8 +1,9 @@
-use crate::media_time::MediaTime;
 use std::fs::File;
 use std::io::prelude::*;
 use std::io::LineWriter;
 
+use crate::util::media_time::MediaTime;
+
 pub struct WebVTTFile {
     cues: Vec<WebVTTCue>
 }