diff --git a/controllers/preview_controller/preview_controller.go b/controllers/preview_controller/preview_controller.go
index 85fd2e6703a7a0a4b44b96340c081407df70c97e..f3128e089432d7de8d01fa7b8fc2cabf0c83d340 100644
--- a/controllers/preview_controller/preview_controller.go
+++ b/controllers/preview_controller/preview_controller.go
@@ -10,10 +10,10 @@ import (
 	"github.com/turt2live/matrix-media-repo/common"
 	"github.com/turt2live/matrix-media-repo/common/globals"
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
-	"github.com/turt2live/matrix-media-repo/controllers/preview_controller/preview_types"
 	"github.com/turt2live/matrix-media-repo/storage"
 	"github.com/turt2live/matrix-media-repo/storage/stores"
 	"github.com/turt2live/matrix-media-repo/types"
+	"github.com/turt2live/matrix-media-repo/url_previewers"
 	"github.com/turt2live/matrix-media-repo/util"
 )
 
@@ -55,7 +55,7 @@ func GetPreview(urlStr string, onHost string, forUserId string, atTs int64, lang
 			return nil, common.ErrInvalidHost
 		}
 		parsedUrl.Fragment = "" // Remove fragment because it's not important for servers
-		urlToPreview := &preview_types.UrlPayload{
+		urlToPreview := &url_previewers.UrlPayload{
 			UrlString: urlStr,
 			ParsedUrl: parsedUrl,
 		}
diff --git a/controllers/preview_controller/preview_resource_handler.go b/controllers/preview_controller/preview_resource_handler.go
index 3230caa12be950f40f4d148ce1edeec185a3eea6..119df0d80ff2d68843a2caa5cca06c751111491c 100644
--- a/controllers/preview_controller/preview_resource_handler.go
+++ b/controllers/preview_controller/preview_resource_handler.go
@@ -5,6 +5,7 @@ import (
 	"sync"
 
 	"github.com/getsentry/sentry-go"
+	url_previewers2 "github.com/turt2live/matrix-media-repo/url_previewers"
 	"github.com/turt2live/matrix-media-repo/util/stream_util"
 
 	"github.com/disintegration/imaging"
@@ -12,8 +13,6 @@ import (
 	"github.com/turt2live/matrix-media-repo/common"
 	"github.com/turt2live/matrix-media-repo/common/config"
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
-	"github.com/turt2live/matrix-media-repo/controllers/preview_controller/preview_types"
-	"github.com/turt2live/matrix-media-repo/controllers/preview_controller/previewers"
 	"github.com/turt2live/matrix-media-repo/controllers/upload_controller"
 	"github.com/turt2live/matrix-media-repo/storage"
 	"github.com/turt2live/matrix-media-repo/storage/datastore"
@@ -27,7 +26,7 @@ type urlResourceHandler struct {
 }
 
 type urlPreviewRequest struct {
-	urlPayload     *preview_types.UrlPayload
+	urlPayload     *url_previewers2.UrlPayload
 	forUserId      string
 	onHost         string
 	languageHeader string
@@ -81,33 +80,33 @@ func urlPreviewWorkFn(request *resource_handler.WorkRequest) (resp *urlPreviewRe
 
 	db := storage.GetDatabase().GetUrlStore(ctx)
 
-	var preview preview_types.PreviewResult
-	err := preview_types.ErrPreviewUnsupported
+	var preview url_previewers2.PreviewResult
+	err := url_previewers2.ErrPreviewUnsupported
 
 	// Try oEmbed first
 	if info.allowOEmbed {
 		ctx = ctx.LogWithFields(logrus.Fields{"worker_previewer": "oEmbed"})
 		ctx.Log.Info("Trying oEmbed previewer")
-		preview, err = previewers.GenerateOEmbedPreview(info.urlPayload, info.languageHeader, ctx)
+		preview, err = url_previewers2.GenerateOEmbedPreview(info.urlPayload, info.languageHeader, ctx)
 	}
 
 	// Then try OpenGraph
-	if err == preview_types.ErrPreviewUnsupported {
+	if err == url_previewers2.ErrPreviewUnsupported {
 		ctx = ctx.LogWithFields(logrus.Fields{"worker_previewer": "OpenGraph"})
 		ctx.Log.Info("oEmbed preview for this URL is unsupported or disabled - treating it as a OpenGraph")
-		preview, err = previewers.GenerateOpenGraphPreview(info.urlPayload, info.languageHeader, ctx)
+		preview, err = url_previewers2.GenerateOpenGraphPreview(info.urlPayload, info.languageHeader, ctx)
 	}
 
 	// Finally try scraping
-	if err == preview_types.ErrPreviewUnsupported {
+	if err == url_previewers2.ErrPreviewUnsupported {
 		ctx = ctx.LogWithFields(logrus.Fields{"worker_previewer": "File"})
 		ctx.Log.Info("OpenGraph preview for this URL is unsupported - treating it as a file")
-		preview, err = previewers.GenerateCalculatedPreview(info.urlPayload, info.languageHeader, ctx)
+		preview, err = url_previewers2.GenerateCalculatedPreview(info.urlPayload, info.languageHeader, ctx)
 	}
 
 	if err != nil {
 		// Transparently convert "unsupported" to "not found" for processing
-		if err == preview_types.ErrPreviewUnsupported {
+		if err == url_previewers2.ErrPreviewUnsupported {
 			err = common.ErrMediaNotFound
 		}
 
@@ -177,7 +176,7 @@ func urlPreviewWorkFn(request *resource_handler.WorkRequest) (resp *urlPreviewRe
 	return resp
 }
 
-func (h *urlResourceHandler) GeneratePreview(urlPayload *preview_types.UrlPayload, forUserId string, onHost string, languageHeader string, allowOEmbed bool) chan *urlPreviewResponse {
+func (h *urlResourceHandler) GeneratePreview(urlPayload *url_previewers2.UrlPayload, forUserId string, onHost string, languageHeader string, allowOEmbed bool) chan *urlPreviewResponse {
 	resultChan := make(chan *urlPreviewResponse)
 	go func() {
 		reqId := fmt.Sprintf("preview_%s", urlPayload.UrlString) // don't put the user id or host in the ID string
diff --git a/pipelines/_steps/url_preview/upload_image.go b/pipelines/_steps/url_preview/upload_image.go
index 4694c925b15aba0cdf604a78ea780cd976798943..26dd92e5713069a374e9d8607a02804eff672df4 100644
--- a/pipelines/_steps/url_preview/upload_image.go
+++ b/pipelines/_steps/url_preview/upload_image.go
@@ -5,15 +5,15 @@ import (
 
 	"github.com/getsentry/sentry-go"
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
-	"github.com/turt2live/matrix-media-repo/controllers/preview_controller/preview_types"
 	"github.com/turt2live/matrix-media-repo/database"
 	"github.com/turt2live/matrix-media-repo/datastores"
 	"github.com/turt2live/matrix-media-repo/pipelines/pipeline_upload"
 	"github.com/turt2live/matrix-media-repo/thumbnailing"
+	"github.com/turt2live/matrix-media-repo/url_previewers"
 	"github.com/turt2live/matrix-media-repo/util"
 )
 
-func UploadImage(ctx rcontext.RequestContext, image *preview_types.PreviewImage, onHost string, userId string, forRecord *database.DbUrlPreview) {
+func UploadImage(ctx rcontext.RequestContext, image *url_previewers.PreviewImage, onHost string, userId string, forRecord *database.DbUrlPreview) {
 	if image == nil {
 		return
 	}
diff --git a/pipelines/pipeline_preview/pipeline.go b/pipelines/pipeline_preview/pipeline.go
index 330428ac308ce0fc432f544eae1591e2133efee3..9f1156a9ab394d207d0e892872b05857be3b6fb2 100644
--- a/pipelines/pipeline_preview/pipeline.go
+++ b/pipelines/pipeline_preview/pipeline.go
@@ -8,10 +8,9 @@ import (
 	"github.com/getsentry/sentry-go"
 	"github.com/turt2live/matrix-media-repo/common"
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
-	"github.com/turt2live/matrix-media-repo/controllers/preview_controller/preview_types"
-	"github.com/turt2live/matrix-media-repo/controllers/preview_controller/previewers"
 	"github.com/turt2live/matrix-media-repo/database"
 	"github.com/turt2live/matrix-media-repo/pipelines/_steps/url_preview"
+	url_previewers2 "github.com/turt2live/matrix-media-repo/url_previewers"
 	"github.com/turt2live/matrix-media-repo/util"
 	"golang.org/x/sync/singleflight"
 )
@@ -53,34 +52,34 @@ func Execute(ctx rcontext.RequestContext, onHost string, previewUrl string, user
 
 	// Step 4: Join the singleflight queue
 	r, err, _ := sf.Do(fmt.Sprintf("%s:%s_%d/%s", onHost, previewUrl, opts.Timestamp, opts.LanguageHeader), func() (interface{}, error) {
-		payload := &preview_types.UrlPayload{
+		payload := &url_previewers2.UrlPayload{
 			UrlString: previewUrl,
 			ParsedUrl: parsedUrl,
 		}
-		var preview preview_types.PreviewResult
-		err = preview_types.ErrPreviewUnsupported
+		var preview url_previewers2.PreviewResult
+		err = url_previewers2.ErrPreviewUnsupported
 
 		// Step 5: Try oEmbed
 		if ctx.Config.UrlPreviews.OEmbed {
 			ctx.Log.Debug("Trying oEmbed previewer")
-			preview, err = previewers.GenerateOEmbedPreview(payload, opts.LanguageHeader, ctx)
+			preview, err = url_previewers2.GenerateOEmbedPreview(payload, opts.LanguageHeader, ctx)
 		}
 
 		// Step 6: Try OpenGraph
-		if err == preview_types.ErrPreviewUnsupported {
+		if err == url_previewers2.ErrPreviewUnsupported {
 			ctx.Log.Debug("Trying OpenGraph previewer")
-			preview, err = previewers.GenerateOpenGraphPreview(payload, opts.LanguageHeader, ctx)
+			preview, err = url_previewers2.GenerateOpenGraphPreview(payload, opts.LanguageHeader, ctx)
 		}
 
 		// Step 7: Try scraping
-		if err == preview_types.ErrPreviewUnsupported {
+		if err == url_previewers2.ErrPreviewUnsupported {
 			ctx.Log.Debug("Trying built-in previewer")
-			preview, err = previewers.GenerateCalculatedPreview(payload, opts.LanguageHeader, ctx)
+			preview, err = url_previewers2.GenerateCalculatedPreview(payload, opts.LanguageHeader, ctx)
 		}
 
 		// Step 8: Finish processing
 		if err != nil {
-			if err == preview_types.ErrPreviewUnsupported {
+			if err == url_previewers2.ErrPreviewUnsupported {
 				err = common.ErrMediaNotFound
 			}
 
diff --git a/controllers/preview_controller/acl/acl.go b/url_previewers/acl.go
similarity index 69%
rename from controllers/preview_controller/acl/acl.go
rename to url_previewers/acl.go
index 109648684eba62138beb509fbba70d02e12131ce..682c65e0cc034bad2a61b6f18816ed3b22ec7408 100644
--- a/controllers/preview_controller/acl/acl.go
+++ b/url_previewers/acl.go
@@ -1,20 +1,19 @@
-package acl
+package url_previewers
 
 import (
-	"fmt"
-	"github.com/getsentry/sentry-go"
 	"net"
 
-	"github.com/sirupsen/logrus"
+	"github.com/getsentry/sentry-go"
+
 	"github.com/turt2live/matrix-media-repo/common"
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
 )
 
-func GetSafeAddress(addr string, ctx rcontext.RequestContext) (net.IP, string, error) {
-	ctx.Log.Info("Checking address: " + addr)
+func getSafeAddress(addr string, ctx rcontext.RequestContext) (net.IP, string, error) {
+	ctx.Log.Debug("Checking address: " + addr)
 	realHost, p, err := net.SplitHostPort(addr)
 	if err != nil {
-		ctx.Log.Warn("Error parsing host and port: ", err.Error())
+		ctx.Log.Debug("Error parsing host and port: ", err)
 		sentry.CaptureException(err)
 		realHost = addr
 	}
@@ -23,7 +22,7 @@ func GetSafeAddress(addr string, ctx rcontext.RequestContext) (net.IP, string, e
 	if realHost != "localhost" {
 		addrs, err := net.LookupIP(realHost)
 		if err != nil {
-			ctx.Log.Warn("Error looking up DNS record for preview - assuming invalid host:", err)
+			ctx.Log.Debug("Error looking up DNS record for preview - assuming invalid host:", err)
 			return nil, "", common.ErrInvalidHost
 		}
 		if len(addrs) == 0 {
@@ -52,28 +51,23 @@ func GetSafeAddress(addr string, ctx rcontext.RequestContext) (net.IP, string, e
 }
 
 func isAllowed(ip net.IP, allowed []string, disallowed []string, ctx rcontext.RequestContext) bool {
-	ctx = ctx.LogWithFields(logrus.Fields{
-		"checkHost":       ip,
-		"allowedHosts":    fmt.Sprintf("%v", allowed),
-		"disallowedHosts": fmt.Sprintf("%v", allowed),
-	})
-	ctx.Log.Info("Validating host")
+	ctx.Log.Debug("Validating host")
 
 	// First check if the IP fits the blacklist. This should be a much shorter list, and therefore
 	// much faster to check.
-	ctx.Log.Info("Checking blacklist for host...")
+	ctx.Log.Debug("Checking blacklist for host...")
 	if inRange(ip, disallowed, ctx) {
-		ctx.Log.Warn("Host found on blacklist - rejecting")
+		ctx.Log.Debug("Host found on blacklist - rejecting")
 		return false
 	}
 
 	// Now check the allowed list just to make sure the IP is actually allowed
 	if inRange(ip, allowed, ctx) {
-		ctx.Log.Info("Host allowed due to whitelist")
+		ctx.Log.Debug("Host allowed due to whitelist")
 		return true
 	}
 
-	ctx.Log.Warn("Host is not on either whitelist or blacklist, considering blacklisted")
+	ctx.Log.Debug("Host is not on either whitelist or blacklist, considering blacklisted")
 	return false
 }
 
@@ -82,7 +76,7 @@ func inRange(ip net.IP, cidrs []string, ctx rcontext.RequestContext) bool {
 		cidr := cidrs[i]
 		_, network, err := net.ParseCIDR(cidr)
 		if err != nil {
-			ctx.Log.Error("Error checking host: " + err.Error())
+			ctx.Log.Debug("Error checking host: ", err)
 			sentry.CaptureException(err)
 			return false
 		}
diff --git a/controllers/preview_controller/previewers/calculated_previewer.go b/url_previewers/calculated_previewer.go
similarity index 76%
rename from controllers/preview_controller/previewers/calculated_previewer.go
rename to url_previewers/calculated_previewer.go
index a40672a38975e9984fd7cfd0125ab61ca95f8784..9df34458efdf57d746020aebca8f0ee0932a6e8f 100644
--- a/controllers/preview_controller/previewers/calculated_previewer.go
+++ b/url_previewers/calculated_previewer.go
@@ -1,4 +1,4 @@
-package previewers
+package url_previewers
 
 import (
 	bytes2 "bytes"
@@ -7,27 +7,26 @@ import (
 	"github.com/ryanuber/go-glob"
 	"github.com/turt2live/matrix-media-repo/common"
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
-	"github.com/turt2live/matrix-media-repo/controllers/preview_controller/preview_types"
 	"github.com/turt2live/matrix-media-repo/metrics"
 	"github.com/turt2live/matrix-media-repo/util/stream_util"
 )
 
-func GenerateCalculatedPreview(urlPayload *preview_types.UrlPayload, languageHeader string, ctx rcontext.RequestContext) (preview_types.PreviewResult, error) {
+func GenerateCalculatedPreview(urlPayload *UrlPayload, languageHeader string, ctx rcontext.RequestContext) (PreviewResult, error) {
 	bytes, filename, contentType, contentLength, err := downloadRawContent(urlPayload, ctx.Config.UrlPreviews.FilePreviewTypes, languageHeader, ctx)
 	if err != nil {
 		ctx.Log.Error("Error downloading content: " + err.Error())
 
 		// Make sure the unsupported error gets passed through
-		if err == preview_types.ErrPreviewUnsupported {
-			return preview_types.PreviewResult{}, preview_types.ErrPreviewUnsupported
+		if err == ErrPreviewUnsupported {
+			return PreviewResult{}, ErrPreviewUnsupported
 		}
 
 		// We'll consider it not found for the sake of processing
-		return preview_types.PreviewResult{}, common.ErrMediaNotFound
+		return PreviewResult{}, common.ErrMediaNotFound
 	}
 
 	stream := stream_util.BufferToStream(bytes2.NewBuffer(bytes))
-	img := &preview_types.PreviewImage{
+	img := &PreviewImage{
 		Data:                stream,
 		ContentType:         contentType,
 		Filename:            filename,
@@ -48,7 +47,7 @@ func GenerateCalculatedPreview(urlPayload *preview_types.UrlPayload, languageHea
 		description = ""
 	}
 
-	result := &preview_types.PreviewResult{
+	result := &PreviewResult{
 		Type:        "", // intentionally empty
 		Url:         urlPayload.ParsedUrl.String(),
 		Title:       summarize(filename, ctx.Config.UrlPreviews.NumTitleWords, ctx.Config.UrlPreviews.MaxTitleLength),
diff --git a/controllers/preview_controller/previewers/http.go b/url_previewers/http.go
similarity index 85%
rename from controllers/preview_controller/previewers/http.go
rename to url_previewers/http.go
index a7aed30e5980121e47eb687f450a96f4416c0da4..a6763d1b4d76d5d6154d518b9951ab874efd3d83 100644
--- a/controllers/preview_controller/previewers/http.go
+++ b/url_previewers/http.go
@@ -1,4 +1,4 @@
-package previewers
+package url_previewers
 
 import (
 	"context"
@@ -14,13 +14,11 @@ import (
 	"github.com/ryanuber/go-glob"
 	"github.com/turt2live/matrix-media-repo/common"
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
-	"github.com/turt2live/matrix-media-repo/controllers/preview_controller/acl"
-	"github.com/turt2live/matrix-media-repo/controllers/preview_controller/preview_types"
 	"github.com/turt2live/matrix-media-repo/util"
 	"github.com/turt2live/matrix-media-repo/util/stream_util"
 )
 
-func doHttpGet(urlPayload *preview_types.UrlPayload, languageHeader string, ctx rcontext.RequestContext) (*http.Response, error) {
+func doHttpGet(urlPayload *UrlPayload, languageHeader string, ctx rcontext.RequestContext) (*http.Response, error) {
 	var client *http.Client
 
 	dialer := &net.Dialer{
@@ -34,7 +32,7 @@ func doHttpGet(urlPayload *preview_types.UrlPayload, languageHeader string, ctx
 			return nil, errors.New("invalid network: expected tcp")
 		}
 
-		safeIp, safePort, err := acl.GetSafeAddress(addr, ctx)
+		safeIp, safePort, err := getSafeAddress(addr, ctx)
 		if err != nil {
 			return nil, err
 		}
@@ -122,7 +120,7 @@ func doHttpGet(urlPayload *preview_types.UrlPayload, languageHeader string, ctx
 	return client.Do(req)
 }
 
-func downloadRawContent(urlPayload *preview_types.UrlPayload, supportedTypes []string, languageHeader string, ctx rcontext.RequestContext) ([]byte, string, string, string, error) {
+func downloadRawContent(urlPayload *UrlPayload, supportedTypes []string, languageHeader string, ctx rcontext.RequestContext) ([]byte, string, string, string, error) {
 	ctx.Log.Info("Fetching remote content...")
 	resp, err := doHttpGet(urlPayload, languageHeader, ctx)
 	if err != nil {
@@ -153,7 +151,7 @@ func downloadRawContent(urlPayload *preview_types.UrlPayload, supportedTypes []s
 	contentType := resp.Header.Get("Content-Type")
 	for _, supportedType := range supportedTypes {
 		if !glob.Glob(supportedType, contentType) {
-			return nil, "", "", "", preview_types.ErrPreviewUnsupported
+			return nil, "", "", "", ErrPreviewUnsupported
 		}
 	}
 
@@ -167,7 +165,7 @@ func downloadRawContent(urlPayload *preview_types.UrlPayload, supportedTypes []s
 	return bytes, filename, contentType, resp.Header.Get("Content-Length"), nil
 }
 
-func downloadHtmlContent(urlPayload *preview_types.UrlPayload, supportedTypes []string, languageHeader string, ctx rcontext.RequestContext) (string, error) {
+func downloadHtmlContent(urlPayload *UrlPayload, supportedTypes []string, languageHeader string, ctx rcontext.RequestContext) (string, error) {
 	raw, _, contentType, _, err := downloadRawContent(urlPayload, supportedTypes, languageHeader, ctx)
 	html := ""
 	if raw != nil {
@@ -176,7 +174,7 @@ func downloadHtmlContent(urlPayload *preview_types.UrlPayload, supportedTypes []
 	return html, err
 }
 
-func downloadImage(urlPayload *preview_types.UrlPayload, languageHeader string, ctx rcontext.RequestContext) (*preview_types.PreviewImage, error) {
+func downloadImage(urlPayload *UrlPayload, languageHeader string, ctx rcontext.RequestContext) (*PreviewImage, error) {
 	ctx.Log.Info("Getting image from " + urlPayload.ParsedUrl.String())
 	resp, err := doHttpGet(urlPayload, languageHeader, ctx)
 	if err != nil {
@@ -187,7 +185,7 @@ func downloadImage(urlPayload *preview_types.UrlPayload, languageHeader string,
 		return nil, errors.New("error during transfer")
 	}
 
-	image := &preview_types.PreviewImage{
+	image := &PreviewImage{
 		ContentType:         resp.Header.Get("Content-Type"),
 		Data:                resp.Body,
 		ContentLength:       resp.ContentLength,
diff --git a/controllers/preview_controller/previewers/oembed_previewer.go b/url_previewers/oembed_previewer.go
similarity index 82%
rename from controllers/preview_controller/previewers/oembed_previewer.go
rename to url_previewers/oembed_previewer.go
index a5f13c6cdd041e630d2da139637042388ccb4b9c..1658ed4de4a85ed1b7cf34f43a6e249afa54a9de 100644
--- a/controllers/preview_controller/previewers/oembed_previewer.go
+++ b/url_previewers/oembed_previewer.go
@@ -1,4 +1,4 @@
-package previewers
+package url_previewers
 
 import (
 	"bytes"
@@ -14,7 +14,6 @@ import (
 	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/common/config"
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
-	"github.com/turt2live/matrix-media-repo/controllers/preview_controller/preview_types"
 	"github.com/turt2live/matrix-media-repo/metrics"
 )
 
@@ -42,10 +41,10 @@ func getOembed() *oembed.Oembed {
 	return oembedInstance
 }
 
-func GenerateOEmbedPreview(urlPayload *preview_types.UrlPayload, languageHeader string, ctx rcontext.RequestContext) (preview_types.PreviewResult, error) {
+func GenerateOEmbedPreview(urlPayload *UrlPayload, languageHeader string, ctx rcontext.RequestContext) (PreviewResult, error) {
 	item := getOembed().FindItem(urlPayload.ParsedUrl.String())
 	if item == nil {
-		return preview_types.PreviewResult{}, preview_types.ErrPreviewUnsupported
+		return PreviewResult{}, ErrPreviewUnsupported
 	}
 
 	info, err := item.FetchOembed(oembed.Options{
@@ -54,7 +53,7 @@ func GenerateOEmbedPreview(urlPayload *preview_types.UrlPayload, languageHeader
 	})
 	if err != nil {
 		ctx.Log.Error("Error getting oEmbed: " + err.Error())
-		return preview_types.PreviewResult{}, err
+		return PreviewResult{}, err
 	}
 
 	if info.Type == "rich" {
@@ -63,7 +62,7 @@ func GenerateOEmbedPreview(urlPayload *preview_types.UrlPayload, languageHeader
 		info.ThumbnailURL = info.URL
 	}
 
-	graph := &preview_types.PreviewResult{
+	graph := &PreviewResult{
 		Type:        info.Type,
 		Url:         info.URL,
 		Title:       info.Title,
@@ -80,7 +79,7 @@ func GenerateOEmbedPreview(urlPayload *preview_types.UrlPayload, languageHeader
 		}
 
 		imgAbsUrl := urlPayload.ParsedUrl.ResolveReference(imgUrl)
-		imgUrlPayload := &preview_types.UrlPayload{
+		imgUrlPayload := &UrlPayload{
 			UrlString: imgAbsUrl.String(),
 			ParsedUrl: imgAbsUrl,
 		}
diff --git a/controllers/preview_controller/previewers/opengraph_previewer.go b/url_previewers/opengraph_previewer.go
similarity index 87%
rename from controllers/preview_controller/previewers/opengraph_previewer.go
rename to url_previewers/opengraph_previewer.go
index 88f12e5ddc27a869dc9c0c252d8b1aad3713f541..878a70399ae08d1cde7463866d3f8fc6aa493be2 100644
--- a/controllers/preview_controller/previewers/opengraph_previewer.go
+++ b/url_previewers/opengraph_previewer.go
@@ -1,4 +1,4 @@
-package previewers
+package url_previewers
 
 import (
 	"net/url"
@@ -13,31 +13,30 @@ import (
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/turt2live/matrix-media-repo/common"
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
-	"github.com/turt2live/matrix-media-repo/controllers/preview_controller/preview_types"
 	"github.com/turt2live/matrix-media-repo/metrics"
 )
 
 var ogSupportedTypes = []string{"text/*"}
 
-func GenerateOpenGraphPreview(urlPayload *preview_types.UrlPayload, languageHeader string, ctx rcontext.RequestContext) (preview_types.PreviewResult, error) {
+func GenerateOpenGraphPreview(urlPayload *UrlPayload, languageHeader string, ctx rcontext.RequestContext) (PreviewResult, error) {
 	html, err := downloadHtmlContent(urlPayload, ogSupportedTypes, languageHeader, ctx)
 	if err != nil {
 		ctx.Log.Error("Error downloading content: " + err.Error())
 
 		// Make sure the unsupported error gets passed through
-		if err == preview_types.ErrPreviewUnsupported {
-			return preview_types.PreviewResult{}, preview_types.ErrPreviewUnsupported
+		if err == ErrPreviewUnsupported {
+			return PreviewResult{}, ErrPreviewUnsupported
 		}
 
 		// We'll consider it not found for the sake of processing
-		return preview_types.PreviewResult{}, common.ErrMediaNotFound
+		return PreviewResult{}, common.ErrMediaNotFound
 	}
 
 	og := opengraph.NewOpenGraph()
 	err = og.ProcessHTML(strings.NewReader(html))
 	if err != nil {
 		ctx.Log.Error("Error getting OpenGraph: " + err.Error())
-		return preview_types.PreviewResult{}, err
+		return PreviewResult{}, err
 	}
 
 	if og.Title == "" {
@@ -54,7 +53,7 @@ func GenerateOpenGraphPreview(urlPayload *preview_types.UrlPayload, languageHead
 	og.Title = summarize(og.Title, ctx.Config.UrlPreviews.NumTitleWords, ctx.Config.UrlPreviews.MaxTitleLength)
 	og.Description = summarize(og.Description, ctx.Config.UrlPreviews.NumWords, ctx.Config.UrlPreviews.MaxLength)
 
-	graph := &preview_types.PreviewResult{
+	graph := &PreviewResult{
 		Type:        og.Type,
 		Url:         og.URL,
 		Title:       og.Title,
@@ -71,7 +70,7 @@ func GenerateOpenGraphPreview(urlPayload *preview_types.UrlPayload, languageHead
 		}
 
 		imgAbsUrl := urlPayload.ParsedUrl.ResolveReference(imgUrl)
-		imgUrlPayload := &preview_types.UrlPayload{
+		imgUrlPayload := &UrlPayload{
 			UrlString: imgAbsUrl.String(),
 			ParsedUrl: imgAbsUrl,
 		}
diff --git a/controllers/preview_controller/preview_types/types.go b/url_previewers/types.go
similarity index 95%
rename from controllers/preview_controller/preview_types/types.go
rename to url_previewers/types.go
index 0f33ea6ba7a971f767b4eb6670f8c342666a5424..2d245c99d241fe73a251b078e410ab7989771c38 100644
--- a/controllers/preview_controller/preview_types/types.go
+++ b/url_previewers/types.go
@@ -1,4 +1,4 @@
-package preview_types
+package url_previewers
 
 import (
 	"errors"
diff --git a/controllers/preview_controller/previewers/util.go b/url_previewers/util.go
similarity index 97%
rename from controllers/preview_controller/previewers/util.go
rename to url_previewers/util.go
index 4b017483feece2b81b4078ddf4d9dd42391c5952..844ed551a86926cfea40eba1a9cf4bb9cff8fa4b 100644
--- a/controllers/preview_controller/previewers/util.go
+++ b/url_previewers/util.go
@@ -1,4 +1,4 @@
-package previewers
+package url_previewers
 
 import (
 	"regexp"